grafana/k6 · error

waiting for selector %q: %w

Error message

waiting for selector %q: %w

What it means

Thrown by Frame.WaitForSelector when waitForSelectorRetry (retried up to maxRetry = 1, so essentially one attempt plus one retry) fails. The wrapped error is usually a timeout ('timeout ... exceeded' from the action loop), a strict-mode violation (multiple matches), or an invalid selector. This is the single most common k6 browser error: the selector never satisfied the wait state within the timeout.

Source

Thrown at internal/js/modules/k6/browser/common/frame.go:2197

			e := &k6ext.UserFriendlyError{
				Err:     err,
				Timeout: opts.Timeout,
			}
			if opts.URL != "" {
				return fmt.Errorf("waiting for navigation to URL matching %q: %w", opts.URL, e)
			}
			return fmt.Errorf("waiting for navigation: %w", e)
		}

		return nil
	}
}

// WaitForSelector waits for the given selector to match the waiting criteria.
func (f *Frame) WaitForSelector(selector string, popts *FrameWaitForSelectorOptions) (*ElementHandle, error) {
	handle, err := f.waitForSelectorRetry(selector, popts, maxRetry)
	if err != nil {
		return nil, fmt.Errorf("waiting for selector %q: %w", selector, err)
	}

	return handle, nil
}

// WaitForTimeout waits the specified amount of milliseconds.
func (f *Frame) WaitForTimeout(timeout int64) {
	to := time.Duration(timeout) * time.Millisecond

	f.log.Debugf("Frame:WaitForTimeout", "fid:%s furl:%q timeout:%s", f.ID(), f.URL(), to)
	defer f.log.Debugf("Frame:WaitForTimeout:return", "fid:%s furl:%q timeout:%s", f.ID(), f.URL(), to)

	select {
	case <-f.ctx.Done():
	case <-time.After(to):
	}
}

View on GitHub (pinned to 93accf6570)

Solutions

  1. Verify the selector in browser DevTools on the exact page state: document.querySelectorAll(sel).length should be >= 1
  2. Increase timeout: page.waitForSelector(sel, { timeout: 60000 })
  3. If multiple elements match, make the selector unique or pass strict: false
  4. Confirm you are querying the right frame — for iframes get the frame first and wait on it

Example fix

// before
page.waitForSelector('.order-row'); // 0 matches, times out at 30s

// after
page.waitForSelector('#orders .order-row:first-child', { timeout: 60_000, state: 'attached' });
Defensive patterns

Strategy: validation

Validate before calling

const n = await page.evaluate((s) => document.querySelectorAll(s).length, sel);
if (n === 0) throw new Error(`selector ${sel} matches nothing; fix before waiting`);
if (n > 1) throw new Error(`selector ${sel} matches ${n} nodes; strict mode will fail`);

Type guard

function isSelectorTimeout(e) {
  return e instanceof Error && /waiting for selector/.test(e.message);
}

Try / catch

try {
  await page.waitForSelector(sel, { timeout: 60_000 });
} catch (e) {
  if (isSelectorTimeout(e)) { /* assert why: auth failure? route not reached? */ }
  throw e;
}

Prevention

When it happens

Trigger: page.waitForSelector(sel) where the element never attaches; selector matches nothing because the app rendered a different DOM; strict: true (default in modern k6) with multiple matches; selector syntax error; frame detached while waiting.

Common situations: Selectors recorded from a different environment/build of the app; waiting for elements behind authentication when the login step failed silently; CI timing — element appears later than the default 30s timeout; iframes requiring frame.waitForSelector instead of page-level waits.

Related errors


AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15). Data as JSON: /api/errors/cb70d16eba359dfd. Report an issue: GitHub.