grafana/k6 · error
waiting for %q: %w
Error message
waiting for %q: %w
What it means
Thrown by Locator.WaitFor() when the wrapped frame.waitFor call fails after up to 20 internal retry rounds. Strict mode is forced on, so the selector must resolve to exactly one element. The dominant cause is a timeout: the element never reaches the requested state (attached/visible/hidden) before opts.Timeout expires; multiple matches also fail with a strict-mode violation.
Source
Thrown at internal/js/modules/k6/browser/common/locator.go:653
opts.Strict = true
if err := l.frame.dispatchEvent(l.selector, typ, eventInit, opts); err != nil {
return fmt.Errorf("dispatching locator event %q to %q: %w", typ, l.selector, err)
}
applySlowMo(l.ctx)
return nil
}
// WaitFor waits for the element matching the locator's selector with strict mode on.
func (l *Locator) WaitFor(opts *FrameWaitForSelectorOptions) error {
l.log.Debugf("Locator:WaitFor", "fid:%s furl:%q sel:%q opts:%+v", l.frame.ID(), l.frame.URL(), l.selector, opts)
opts.Strict = true
_, err := l.frame.waitFor(l.selector, opts, 20)
if err != nil {
return fmt.Errorf("waiting for %q: %w", l.selector, err)
}
return nil
}
// DefaultTimeout returns the default timeout for the locator.
// This is an internal API and should not be used by users.
func (l *Locator) DefaultTimeout() time.Duration {
return l.frame.defaultTimeout()
}
// FrameLocator represent a way to find element(s) in an iframe.
type FrameLocator struct {
selector string
frame *Frame
ctx context.ContextView on GitHub (pinned to 93accf6570)
Solutions
- Verify the selector manually (devtools) and make it match exactly one element
- Increase opts.timeout to cover slow CI/load-test environments
- Choose the right state: 'attached' for existence, 'visible' for rendered, 'hidden' for disappearance
- If the element only exists after an action, put waitFor after that action, not before
Example fix
// before
await page.locator('.results-row').waitFor(); // default timeout too short in CI
// after
await page.locator('.results-row').waitFor({ state: 'visible', timeout: '60s' }); Defensive patterns
Strategy: try-catch
Validate before calling
// fail fast with a clearer message than the default timeout wrap
const count = await page.locator(sel).count();
if (count > 1) throw new Error(`strict mode: ${sel} matches ${count} elements`);
await page.locator(sel).waitFor({ state: 'visible', timeout: '60s' }); Type guard
async function isSingleMatch(page, sel) {
const n = await page.locator(sel).count();
return n === 1;
} Try / catch
try {
await page.locator(sel).waitFor({ timeout: '60s' });
} catch (e) {
if (/timeout/i.test(e.message)) {
throw new Error(`element never appeared: ${sel} (page url: ${page.url()})`);
}
throw e;
} Prevention
- Pick state ('attached'|'visible'|'hidden') deliberately for each wait
- Scale timeouts with environment slowness (CI > local)
- Verify selectors in devtools against the rendered DOM, not the source
When it happens
Trigger: locator.waitFor({state, timeout}) where exactly one element never appears/visibilize before the timeout; selector matches multiple elements (strict mode violation); waiting for state:'hidden' on an element that never disappears.
Common situations: Waiting for late SPA content with the default 30s timeout on slow or overloaded environments; selectors that match multiple nodes; asserting disappearance (state:'hidden') of elements that persist; wrong selector after a re-render.
Related errors
- waiting for element state %q: %w
- waiting for selector %q: %w
- unchecking %q: %w
- checking element is checked %q: %w
- double clicking on %q: %w
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/cd6b72841bb07558.
Report an issue: GitHub.