grafana/k6 · error · ErrElementNotAttachedToDOM

element is not attached to the DOM

Error message

element is not attached to the DOM

What it means

Sentinel error ErrElementNotAttachedToDOM from the k6 browser (chromium) module. It means the ElementHandle's backing DOM node no longer exists in the document: the node was removed by script, replaced by a re-render, or its frame navigated away. Operations like boundingBox(), click(), or element-state waits return it instead of acting on a dead reference, and the mapping layer (element_handle_mapping.go:22, locator_mapping.go:42) treats it as a retryable action failure.

Source

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

	"slices"
	"strconv"
	"strings"
	"time"

	"github.com/chromedp/cdproto/cdp"
	"github.com/chromedp/cdproto/dom"
	"go.opentelemetry.io/otel/attribute"

	"go.k6.io/k6/v2/internal/js/modules/k6/browser/common/js"
	"go.k6.io/k6/v2/internal/js/modules/k6/browser/k6ext"
)

// Common error types for element visibility.
var (
	// ErrElementNotVisible is returned when an element is not visible for an operation.
	ErrElementNotVisible = errors.New("element is not visible")
	// ErrElementNotAttachedToDOM is returned when an element is not attached to the DOM.
	ErrElementNotAttachedToDOM = errors.New("element is not attached to the DOM")
)

const (
	resultDone       = "done"
	resultNeedsInput = "needsinput"
)

type (
	elementHandleActionFunc        func(context.Context, *ElementHandle) (any, error)
	elementHandlePointerActionFunc func(context.Context, *ElementHandle, *Position) (any, error)
	retryablePointerActionFunc     func(context.Context, *ScrollIntoViewOptions) (any, error)

	// evalFunc is a common interface for both evalWithScript and eval.
	// It helps abstracting these methods to aid with testing.
	evalFunc func(ctx context.Context, opts evalOptions, js string, args ...any) (any, error)
)

// ElementHandle represents a HTML element JS object inside an execution context.

View on GitHub (pinned to 93accf6570)

Solutions

  1. Re-query the element (page.$ or page.waitForSelector) immediately before the action instead of reusing an old handle
  2. Prefer the Locator API (page.locator(...).click()), which re-resolves the element on each retry
  3. Call page.waitForLoadState('load') or waitForNavigation first so the DOM is stable before querying
  4. Wrap the action in try-catch and on this error retry the whole query+action sequence once

Example fix

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

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

Strategy: retry

Validate before calling

// Check the node is still connected before acting on a stored handle
const attached = await handle.evaluate(el => el.isConnected);
if (!attached) {
  handle = await page.waitForSelector(selector, { state: 'visible' });
}
await handle.click();

Try / catch

try {
  await handle.click();
} catch (e) {
  if (String(e.message).includes('not attached')) {
    handle = await page.waitForSelector(selector); // re-query
    await handle.click(); // one retry with the fresh handle
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling ElementHandle or Locator actions (click, fill, check, waitForElementState, boundingBox) on a handle whose node was already removed; CDP returns 'error:notconnected' or an empty box model (element_handle.go:63-73, 1885, 1892); the element is replaced by an SPA re-render between the query and the action; the frame holding the element navigates and destroys the execution context.

Common situations: React/Vue apps replacing keyed nodes on re-render; handles captured early and used after the page changed; interacting while navigation is still settling; iframes removed mid-action.

Related errors


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