grafana/k6 · error
waiting for selector
Error message
waiting for selector %q: %w
What it means
Thrown by the k6 browser module's ElementHandle.waitForSelector(). It wraps failure of the internal frame-scoped wait: no matching element appeared before the timeout, the selector was rejected during compilation, or the frame was navigated away/closed mid-wait. Note: if the wait succeeds but the element is not attached (e.g., state:'attached' semantics), a nil handle can be returned without error — this error is specifically the failure branch.
Solutions
- Verify the selector actually matches within this element's subtree — test in devtools with document.querySelector from the element, not from document.
- Increase the timeout: el.waitForSelector('.child', { timeout: '60s' }).
- Use state: 'attached' when visibility is not required, or state: 'hidden' when waiting for removal (removal waits also use this API).
- If the containing frame navigates, re-acquire the frame and element first.
Example fix
// before
const row = await page.$('.order-row');
const badge = await row.waitForSelector('.shipped', { timeout: '2s' }); // data arrives later
// after
const row = await page.waitForSelector('.order-row');
const badge = await row.waitForSelector('.shipped', { state: 'visible', timeout: '30s' }); Defensive patterns
Strategy: validation
Validate before calling
// verify the selector matches within this element's subtree before waiting
const matches = await el.$$eval('*', nodes => nodes.some(n => n.matches('.shipped')));
if (!matches) { throw new Error('selector never matches inside this element'); }
await el.waitForSelector('.shipped', { timeout: '30s' }); Try / catch
try {
child = await el.waitForSelector('.child', { state: 'visible', timeout: '30s' });
} catch (e) {
if (String(e).includes('waiting for selector')) {
await page.screenshot({ path: 'selector-timeout.png' }); // inspect actual DOM
}
throw e;
} Prevention
- Test scoped selectors in devtools from the element, not from document.
- Use state:'attached' when existence is all you need; visibility waits cost more and fail more.
When it happens
Trigger: await el.waitForSelector('.child', { timeout }) where the child never renders (wrong selector, typo, scoped inside an element that is the wrong ancestor); waiting inside an iframe-embedded element after the iframe navigates; invalid selector syntax failing querySelector compilation; timeout shorter than app render time.
Common situations: Selectors that work in devtools but not scoped to the queried element (waitForSelector searches within the element's subtree); SPAs that render conditionally after async data arrives; slow backends in load tests making default timeouts too tight.
Related errors
- an error occurred communicating with k6 Cloud
- " " does not support " " matcher
- Error while parsing selector
- Error while parsing selector
- Error while parsing selector
AI-assisted analysis of grafana/k6@3fcf5388d7 (2026-08-18).
Data as JSON: /api/errors/a384878e96b10960.
Report an issue: GitHub.
Appendix: source
Thrown at internal/js/modules/k6/browser/common/element_handle.go:1586
return nil
}
// WaitForElementState waits for the element to reach the given state.
func (h *ElementHandle) WaitForElementState(state string, opts *ElementHandleWaitForElementStateOptions) error {
_, err := h.waitForElementState(h.ctx, []string{state}, opts.Timeout)
if err != nil {
return fmt.Errorf("waiting for element state %q: %w", state, err)
}
return nil
}
// WaitForSelector waits for the selector to appear in the DOM.
func (h *ElementHandle) WaitForSelector(selector string, opts *FrameWaitForSelectorOptions) (*ElementHandle, error) {
handle, err := h.waitForSelector(h.ctx, selector, opts)
if err != nil {
return nil, fmt.Errorf("waiting for selector %q: %w", selector, err)
}
return handle, nil
}
// evalWithScript evaluates the given js code in the scope of this ElementHandle and returns the result.
// The js code can call helper functions from injected_script.js.
func (h *ElementHandle) evalWithScript(
ctx context.Context,
opts evalOptions, js string, args ...any,
) (any, error) {
script, err := h.execCtx.getInjectedScript(h.ctx)
if err != nil {
return nil, fmt.Errorf("getting injected script: %w", err)
}
return h.eval(ctx, opts, js, append([]any{script}, args...)...)
}
View on GitHub (pinned to 3fcf5388d7)