grafana/k6 · error

checking state %q of element %q

Error message

checking state %q of element %q

What it means

checkElementState evaluates injected.checkElementState(node, state) and expects either a string 'error:*' or a bool. If the JS side returns anything else (null/undefined/number), k6 reports 'checking state <state> of element <Go type>' — an internal contract violation between k6 and its injected script, usually triggered when the element or frame became invalid during evaluation.

Source

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

			return injected.checkElementState(node, state);
		}
	`
	opts := evalOptions{
		forceCallable: true,
		returnByValue: true,
	}
	result, err := h.evalWithScript(h.ctx, opts, fn, state)
	if err != nil {
		return nil, err
	}
	switch v := result.(type) {
	case string: // An error happened (returned as "error:..." from JS)
		return nil, errorFromDOMError(v)
	case bool:
		return &v, nil
	}

	return nil, fmt.Errorf(
		"checking state %q of element %q", state, reflect.TypeOf(result))
}

func (h *ElementHandle) click(p *Position, opts *MouseClickOptions) error {
	return h.frame.page.Mouse.click(p.X, p.Y, opts)
}

// This will get the clickable point of the element relative to the page even
// when the element is in an iframe. In some cases this isn't the case and we
// need to translate the point to the page.
func (h *ElementHandle) clickablePoint() (*Position, error) {
	r, err := h.BoundingBox()
	if err != nil {
		return nil, fmt.Errorf("finding clickable point: %w", err)
	}
	return &Position{X: r.X + r.Width/2, Y: r.Y + r.Height/2}, nil
}

View on GitHub (pinned to 93accf6570)

Solutions

  1. Retry the waiting/action once after re-acquiring the locator — detached-node evaluations are transient.
  2. Upgrade to the latest k6 release to rule out an injected-script/binary mismatch.
  3. Avoid initiating actions exactly when a navigation is expected (wait for URL/content stability first).
  4. If reproducible on a static page, file a k6 issue with the script and browser version.

Example fix

// before
await page.locator('#status').click();  // actionability wait hits unexpected JS result
// -> checking state "visible" of element "<nil>"

// after
const loc = page.locator('#status');
await loc.waitFor({ state: 'visible' });
try { await loc.click(); }
catch (e) {
  if (!String(e).includes('checking state')) throw e;
  await page.locator('#status').click(); // re-acquire and retry
}
Defensive patterns

Strategy: retry

Validate before calling

// make the wait state explicit and re-acquire the locator, keeping handles short-lived
await page.locator(sel).waitFor({ state: 'visible', timeout: 10_000 });
await page.locator(sel).click();

Try / catch

try {
  await page.locator(sel).click();
} catch (e) {
  if (/checking state .* of element/.test(String(e))) {
    // injected-script contract broke (frame/node churn) — re-acquire and retry once
    await page.locator(sel).waitFor({ state: 'visible' });
    await page.locator(sel).click();
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Waiting for an element state (visible/hidden/enabled/disabled/stable — used by waitFor and actionability checks before click/fill) when the frame navigates or the node is detached mid-evaluation so the injected script returns undefined instead of a bool; or a k6 version whose injected script bundle is out of sync.

Common situations: Auto-waiting on elements in SPAs that re-render aggressively; navigations racing actionability checks; upgrading k6 with a stale cached browser profile; extremely rare on healthy pages.

Related errors


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