grafana/k6 · error

expected node but got %s

Error message

expected node but got %s

What it means

This error is a translation of a raw Chromium DevTools Protocol error whose message starts with 'error:expectednode:'. CDP returns that error when an internal command (DOM.requestNode, DOM.describeNode, DOM.resolveNode) is given a remote object reference that is not a DOM node. k6 rewrites it to 'expected node but got <type>' where <type> is the actual remote object type Chromium reported (e.g. 'object', 'string', 'function'). It almost always means the ElementHandle being used no longer refers to a live DOM node.

Source

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

	case error:
		if e == nil {
			return errors.New("DOM error is nil")
		}
		err, serr = e, e.Error()
	default:
		return fmt.Errorf("unexpected DOM error type %T", v)
	}
	var uerr *k6ext.UserFriendlyError
	if errors.As(err, &uerr) {
		return err
	}
	if strings.Contains(serr, "timed out") {
		return &k6ext.UserFriendlyError{
			Err: ErrTimedOut,
		}
	}
	if s := "error:expectednode:"; strings.HasPrefix(serr, s) {
		return fmt.Errorf("expected node but got %s", strings.TrimPrefix(serr, s))
	}

	if serr == "error:notconnected" {
		return ErrElementNotAttachedToDOM
	}

	if errors.Is(err, ErrElementNotVisible) {
		return ErrElementNotVisible
	}

	if errors.Is(err, ErrElementNotAttachedToDOM) {
		return ErrElementNotAttachedToDOM
	}

	errs := map[string]string{
		"error:notelement":             "node is not an element",
		"error:nothtmlelement":         "not an HTMLElement",
		"error:notfillableelement":     "element is not an <input>, <textarea> or [contenteditable] element",

View on GitHub (pinned to 93accf6570)

Solutions

  1. Re-acquire the element immediately before acting on it (fresh page.$ / waitForSelector) instead of reusing a cached handle
  2. Wrap the action in a bounded retry that re-queries the selector when this message appears
  3. Prefer selector-based auto-waiting APIs (page.click(selector)) over manual handle plumbing
  4. If it appears intermittently under load, give Chrome more CPU/memory or lower the VU count per browser

Example fix

// before
const btn = await page.$('#submit');
await page.goto('https://example.com/next');
await btn.click(); // stale handle

// after
await page.goto('https://example.com/next');
const btn = await page.$('#submit');
await btn.click();
Defensive patterns

Strategy: retry

Validate before calling

// verify the handle still points at a live, visible node before acting
if (!el) throw new Error('element handle is null');
if (!(await el.isVisible())) throw new Error('element no longer visible');

Type guard

function isElementHandleLike(v) {
  return !!v && typeof v.click === 'function' && typeof v.boundingBox === 'function' && typeof v.screenshot === 'function';
}

Try / catch

try {
  await el.click();
} catch (e) {
  if (/expected node but got/.test(e.message)) {
    el = await page.waitForSelector(sel, { state: 'visible' }); // re-acquire, retry once
    await el.click();
  } else throw e;
}

Prevention

When it happens

Trigger: Calling ElementHandle methods that internally resolve or adopt the handle as a node (boundingBox, contentFrame, the cross-context adoption in waitForSelector, clicks on stale handles) after the page navigated or the node was replaced; using a plain JSHandle (a non-element object) where an ElementHandle is required; Chrome disposing remote objects under heavy load.

Common situations: Reusing an ElementHandle obtained before a page.goto or an SPA route change; React/Vue re-rendering a node between query and action; scripts running many VUs against a memory-starved Chrome where remote objects get garbage collected; confusing page.evaluateHandle results (JS values) with element handles.

Related errors


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