grafana/k6 · error
parsing waitForSelector %q options: %w
Error message
parsing waitForSelector %q options: %w
What it means
Thrown synchronously by ElementHandle.waitForSelector() when its options fail to parse. It parses common.FrameWaitForSelectorOptions (state, strict, timeout), and the one real failure path in frame_options.go is the 'state' key: a value not present in domElementStateToID ('attached', 'detached', 'visible', 'hidden') returns '<state> is not a valid DOM state', wrapped here together with the selector. This is one of the few parse errors in this mapping that fires routinely.
Source
Thrown at internal/js/modules/k6/browser/browser/element_handle_mapping.go:294
return nil, fmt.Errorf("parsing uncheck options: %w", err)
}
return promise(vu, func() (any, error) {
return nil, eh.Uncheck(popts) //nolint:wrapcheck
}), nil
},
"waitForElementState": func(state string, opts sobek.Value) (*sobek.Promise, error) {
popts := common.NewElementHandleWaitForElementStateOptions(eh.DefaultTimeout())
if err := popts.Parse(vu.Context(), opts); err != nil {
return nil, fmt.Errorf("parsing waitForElementState options: %w", err)
}
return promise(vu, func() (any, error) {
return nil, eh.WaitForElementState(state, popts) //nolint:wrapcheck
}), nil
},
"waitForSelector": func(selector string, opts sobek.Value) (*sobek.Promise, error) {
popts := common.NewFrameWaitForSelectorOptions(eh.DefaultTimeout())
if err := popts.Parse(vu.Context(), opts); err != nil {
return nil, fmt.Errorf("parsing waitForSelector %q options: %w", selector, err)
}
return promise(vu, func() (any, error) {
eh, err := eh.WaitForSelector(selector, popts)
if err != nil {
return nil, err //nolint:wrapcheck
}
return mapElementHandle(vu, eh), nil
}), nil
},
}
maps["$"] = func(selector string) *sobek.Promise {
return promise(vu, func() (any, error) {
eh, err := eh.Query(selector, common.StrictModeOff)
if err != nil {
return nil, err //nolint:wrapcheck
}
// ElementHandle can be null when the selector does not match any elements.
// We do not want to map nil elementHandles since the expectation is aView on GitHub (pinned to 93accf6570)
Solutions
- Use one of: state: 'attached' | 'detached' | 'visible' | 'hidden'
- If you meant a lifecycle wait, use goto/waitForNavigation with waitUntil instead
- Validate state against the allowed set before the call, especially for data-driven values
- Wrap in try/catch; the throw is synchronous, before the promise is created
Example fix
// before
await el.waitForSelector('.item', { state: 'displayed' });
// after
await el.waitForSelector('.item', { state: 'visible', timeout: 5000 }); Defensive patterns
Strategy: validation
Validate before calling
const STATES = new Set(['attached', 'detached', 'visible', 'hidden']);
function validateWaitForSelectorOpts(selector, opts) {
if (opts?.state !== undefined && !STATES.has(opts.state)) {
throw new Error(`waitForSelector(${selector}): invalid state '${opts.state}'; use attached|detached|visible|hidden`);
}
if (opts?.timeout !== undefined && !Number.isFinite(Number(opts.timeout)) && !(opts.timeout instanceof RegExp) &&
typeof opts.timeout !== 'function' && opts.timeout !== null) {
throw new Error('waitForSelector(): timeout must be ms number or a function/RegExp predicate');
}
return opts;
} Type guard
function isWaitForSelectorOpts(o) {
if (o == null) return true;
const stateOk = o.state === undefined || ['attached', 'detached', 'visible', 'hidden'].includes(o.state);
const strictOk = o.strict === undefined || typeof o.strict === 'boolean';
const timeoutOk = o.timeout === undefined || Number.isFinite(o.timeout) ||
typeof o.timeout === 'function' || o.timeout instanceof RegExp;
return stateOk && strictOk && timeoutOk;
} Try / catch
try {
const found = await el.waitForSelector(selector, opts);
} catch (e) {
if (/is not a valid DOM state/.test(String(e.message))) {
console.log(`fix state: ${e.message}`); // state must be attached|detached|visible|hidden
} else throw e; // timeout waiting for the selector is a different failure
} Prevention
- The four valid states are attached, detached, visible, hidden; 'load' and 'domcontentloaded' are waitUntil values for navigation, not states
- Validate data-driven state values against the allowed set before the browser session starts
- Match on 'is not a valid DOM state' in catch blocks to separate option errors from selector timeouts
When it happens
Trigger: el.waitForSelector('#x', { state: 'displayed' }) or { state: 'load' } or any typo like 'visable' produces "<state>" is not a valid DOM state. strict and timeout are lenient coercions and never fail.
Common situations: Confusing waitForSelector state values with waitUntil lifecycle values ('load', 'domcontentloaded') used by page.goto/waitForNavigation; porting Playwright tests where waitForSelector accepts the same four states but data-driven scripts inject invalid ones; typos from fixture files.
Related errors
- parsing check options: %w
- parsing element click options: %w
- parsing element double click options: %w
- parsing element fill options: %w
- parsing element hover options: %w
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/a864cafc81a01ae1.
Report an issue: GitHub.