{"record":{"id":"a864cafc81a01ae1","repo":"grafana/k6","slug":"parsing-waitforselector-q-options-w","errorCode":null,"errorMessage":"parsing waitForSelector %q options: %w","messagePattern":"parsing waitForSelector %q options: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/browser/element_handle_mapping.go","lineNumber":294,"sourceCode":"\t\t\t\treturn nil, fmt.Errorf(\"parsing uncheck options: %w\", err)\n\t\t\t}\n\t\t\treturn promise(vu, func() (any, error) {\n\t\t\t\treturn nil, eh.Uncheck(popts) //nolint:wrapcheck\n\t\t\t}), nil\n\t\t},\n\t\t\"waitForElementState\": func(state string, opts sobek.Value) (*sobek.Promise, error) {\n\t\t\tpopts := common.NewElementHandleWaitForElementStateOptions(eh.DefaultTimeout())\n\t\t\tif err := popts.Parse(vu.Context(), opts); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"parsing waitForElementState options: %w\", err)\n\t\t\t}\n\t\t\treturn promise(vu, func() (any, error) {\n\t\t\t\treturn nil, eh.WaitForElementState(state, popts) //nolint:wrapcheck\n\t\t\t}), nil\n\t\t},\n\t\t\"waitForSelector\": func(selector string, opts sobek.Value) (*sobek.Promise, error) {\n\t\t\tpopts := common.NewFrameWaitForSelectorOptions(eh.DefaultTimeout())\n\t\t\tif err := popts.Parse(vu.Context(), opts); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"parsing waitForSelector %q options: %w\", selector, err)\n\t\t\t}\n\t\t\treturn promise(vu, func() (any, error) {\n\t\t\t\teh, err := eh.WaitForSelector(selector, popts)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err //nolint:wrapcheck\n\t\t\t\t}\n\t\t\t\treturn mapElementHandle(vu, eh), nil\n\t\t\t}), nil\n\t\t},\n\t}\n\tmaps[\"$\"] = func(selector string) *sobek.Promise {\n\t\treturn promise(vu, func() (any, error) {\n\t\t\teh, err := eh.Query(selector, common.StrictModeOff)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err //nolint:wrapcheck\n\t\t\t}\n\t\t\t// ElementHandle can be null when the selector does not match any elements.\n\t\t\t// We do not want to map nil elementHandles since the expectation is a","sourceCodeStart":276,"sourceCodeEnd":312,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/browser/browser/element_handle_mapping.go#L276-L312","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nawait el.waitForSelector('.item', { state: 'displayed' });\n\n// after\nawait el.waitForSelector('.item', { state: 'visible', timeout: 5000 });","handlingStrategy":"validation","validationCode":"const STATES = new Set(['attached', 'detached', 'visible', 'hidden']);\nfunction validateWaitForSelectorOpts(selector, opts) {\n  if (opts?.state !== undefined && !STATES.has(opts.state)) {\n    throw new Error(`waitForSelector(${selector}): invalid state '${opts.state}'; use attached|detached|visible|hidden`);\n  }\n  if (opts?.timeout !== undefined && !Number.isFinite(Number(opts.timeout)) && !(opts.timeout instanceof RegExp) &&\n      typeof opts.timeout !== 'function' && opts.timeout !== null) {\n    throw new Error('waitForSelector(): timeout must be ms number or a function/RegExp predicate');\n  }\n  return opts;\n}","typeGuard":"function isWaitForSelectorOpts(o) {\n  if (o == null) return true;\n  const stateOk = o.state === undefined || ['attached', 'detached', 'visible', 'hidden'].includes(o.state);\n  const strictOk = o.strict === undefined || typeof o.strict === 'boolean';\n  const timeoutOk = o.timeout === undefined || Number.isFinite(o.timeout) ||\n    typeof o.timeout === 'function' || o.timeout instanceof RegExp;\n  return stateOk && strictOk && timeoutOk;\n}","tryCatchPattern":"try {\n  const found = await el.waitForSelector(selector, opts);\n} catch (e) {\n  if (/is not a valid DOM state/.test(String(e.message))) {\n    console.log(`fix state: ${e.message}`); // state must be attached|detached|visible|hidden\n  } else throw e; // timeout waiting for the selector is a different failure\n}","preventionTips":["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"],"tags":["k6","browser","element-handle","wait-for-selector","option-parsing"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}