grafana/k6 · error

waiting for element state

Error message

waiting for element state: %w

What it means

Second stage of the k6 browser module's pointer-action pipeline (used by click, dblclick, hover, tap and similar). After scrolling into view, unless opts.Force is set, the element must reach the states visible, stable and enabled via waitForElementState. This error means that wait failed: the timeout expired with the element still hidden/animating/disabled, the element detached, or the state evaluation returned a DOM error.

Solutions

  1. Perform the prerequisite step that enables/settles the element (fill the form, accept terms) before the pointer action.
  2. Increase the action timeout: el.click({ timeout: '60s' }).
  3. Disable animations in the page under test (add a test CSS override) so 'stable' passes instantly.
  4. Use { force: true } to skip visible/stable/enabled checks entirely when you deliberately act on a non-settled element — then handle the app-level consequences yourself.

Example fix

// before
const btn = await page.$('#submit');
await btn.click({ timeout: '5s' }); // disabled until async validation

// after
await page.fill('#email', 'a@b.c');
const btn = await page.waitForSelector('#submit');
await btn.waitForElementState('enabled', { timeout: '30s' });
await btn.click();
Defensive patterns

Strategy: try-catch

Validate before calling

const [visible, enabled] = await Promise.all([
  el.isVisible().catch(() => false),
  el.isEnabled().catch(() => false),
]);
if (visible && enabled) { await el.click(); }

Try / catch

try {
  await el.click({ timeout: '30s' });
} catch (e) {
  const msg = String(e);
  if (msg.includes('waiting for element state') && msg.includes('timed out')) {
    await el.click({ force: true }); // deliberate bypass with known consequences
  } else { throw e; }
}

Prevention

When it happens

Trigger: click() on a button that stays disabled until form validation completes; on an element with a perpetual CSS animation so 'stable' never passes; on elements re-rendered (unstable) by frameworks like React during the wait; detached elements.

Common situations: Buttons gated behind async validation or terms acceptance; spinners/breadcrumbs with continuous animations; CI machines where rendering jank extends instability past the timeout.

Related errors


AI-assisted analysis of grafana/k6@3fcf5388d7 (2026-08-18). Data as JSON: /api/errors/ac108059fdfbef8c. Report an issue: GitHub.

Appendix: source

Thrown at internal/js/modules/k6/browser/common/element_handle.go:1716

	// 4. Enabled
	// 5. Receives events
	pointerFn := func(apiCtx context.Context, sopts *ScrollIntoViewOptions) (res any, err error) {
		// We need to scroll the element into view first, otherwise we can
		// end up in a situation where the element is not in the correct state
		// (visible and stable, but could be enabled).
		err = h.scrollRectIntoViewIfNeeded(apiCtx, nil)
		if err != nil {
			return nil, fmt.Errorf("scrolling element into view: %w", err)
		}

		// Check if we should run actionability checks
		if !opts.Force {
			// As mentioned above, if we didn't scroll first, we could end
			// up stuck waiting indefinitely for the element to be in the
			// correct state.
			states := []string{"visible", "stable", "enabled"}
			if _, err = h.waitForElementState(apiCtx, states, opts.Timeout); err != nil {
				return nil, fmt.Errorf("waiting for element state: %w", err)
			}
		}

		// Decide position where a mouse down should happen if needed by action
		p := opts.Position

		// Change scrolling action depending on the scrolling options
		if sopts == nil {
			var rect *dom.Rect
			if p != nil {
				rect = &dom.Rect{X: p.X, Y: p.Y}
			}
			err = h.scrollRectIntoViewIfNeeded(apiCtx, rect)
		} else {
			_, err = h.eval(
				apiCtx,
				evalOptions{forceCallable: true, returnByValue: false},
				js.ScrollIntoView,

View on GitHub (pinned to 3fcf5388d7)