microsoft/playwright · error · Error
Element is not attached to the DOM
Error message
Element is not attached to the DOM
What it means
An element handle references a DOM node that has since been removed from the document (the injected utility returned 'error:notconnected'). throwElementIsNotAttached converts that into Error('Element is not attached to the DOM'). ElementHandle is a snapshot pointer; once the node is removed/re-rendered the handle is stale and any action on it fails.
Solutions
- Use Locators instead of ElementHandle — Locators re-resolve the element on every action and stay valid across re-renders.
- Re-query the element handle immediately before the action if you must use handles.
- Await stability (waitFor selector / waitForLoadState) so re-renders settle before acting.
- Avoid caching handles from beforeEach across the test body.
Example fix
// before — stale handle
const handle = await page.$('#item');
await page.reload();
await handle.click(); // node gone → throws
// after — locator re-resolves
const item = page.locator('#item');
await page.reload();
await item.click(); Defensive patterns
Strategy: retry
Validate before calling
// Use a Locator so the element is re-resolved on each action.
const item = page.locator('#item');
await page.reload();
await item.click(); // re-queries after reload Try / catch
// If you must use a handle, re-query on staleness.
async function clickFresh(page: Page, selector: string) {
try {
await (await page.$(selector))?.click();
} catch (e) {
if (e.message.includes('not attached to the DOM')) {
await (await page.$(selector))?.click(); // re-query and retry once
} else throw e;
}
} Prevention
- Prefer Locators over ElementHandle — they re-resolve automatically.
- Do not cache element handles across navigations or re-renders.
- Await load-state/stability before acting to let re-renders settle.
When it happens
Trigger: Querying an ElementHandle, then after a re-render or navigation the node is replaced, then calling an action on the old handle; React/Vue re-rendering a list item so the old node is discarded; SPA route change that replaces the container.
Common situations: Storing element handles across awaits in React/Vue apps; acting on a handle obtained before an animation that swaps nodes; navigating away and back so the original nodes are new instances.
Related errors
- Unable to adopt element handle from a different document
- Cannot set input files to detached element
- Cannot set input files to detached input element
- Did not find some options
- Drop target did not accept the drop — its dragover handler…
AI-assisted analysis of microsoft/playwright@f1d33b5029 (2026-08-12).
Data as JSON: /api/errors/141dc72996d8401f.
Report an issue: GitHub.
Appendix: source
Thrown at packages/playwright-core/src/server/dom.ts:987
if (hitTargetResult !== 'done')
return hitTargetResult;
}
return { framePoint: data[0].pointInFrame };
} finally {
for (const frameElement of temporaryFrameElements)
frameElement.dispose();
}
}
}
export function throwRetargetableDOMError<T>(result: T | 'error:notconnected'): T {
if (result === 'error:notconnected')
throwElementIsNotAttached();
return result;
}
export function throwElementIsNotAttached(): never {
throw new Error('Element is not attached to the DOM');
}
export function assertDone(result: 'done'): void {
// This function converts 'done' to void and ensures typescript catches unhandled errors.
}
function roundPoint(point: types.Point): types.Point {
return {
x: (point.x * 100 | 0) / 100,
y: (point.y * 100 | 0) / 100,
};
}
function quadToRect(quad: types.Quad): types.Rect {
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
let maxY = -Infinity;View on GitHub (pinned to f1d33b5029)