microsoft/playwright · error · Error
Unable to adopt element handle from a different document
Error message
Unable to adopt element handle from a different document
What it means
Thrown by CRFrameSession._adoptBackendNodeId (crPage.ts:1170) when DOM.resolveNode returns nothing or a null subtype for the given backendNodeId + executionContextId. The node cannot be resolved into the destination context — typically because it lives in a different document that the target context cannot reach.
Source
Thrown at packages/playwright-core/src/server/chromium/crPage.ts:1170
{ x: quad[4] + position.x, y: quad[5] + position.y },
{ x: quad[6] + position.x, y: quad[7] + position.y }
]);
}
async _adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.FrameExecutionContext): Promise<dom.ElementHandle<T>> {
const nodeInfo = await this._client.send('DOM.describeNode', {
objectId: handle._objectId,
});
return this._adoptBackendNodeId(nodeInfo.node.backendNodeId, to) as Promise<dom.ElementHandle<T>>;
}
async _adoptBackendNodeId(backendNodeId: Protocol.DOM.BackendNodeId, to: dom.FrameExecutionContext): Promise<dom.ElementHandle> {
const result = await this._client._sendMayFail('DOM.resolveNode', {
backendNodeId,
executionContextId: (to.delegate as CRExecutionContext)._contextId,
});
if (!result || result.object.subtype === 'null')
throw new Error(dom.kUnableToAdoptErrorMessage);
return createHandle(to, result.object).asElement()!;
}
}
async function emulateLocale(session: CRSession, locale: string) {
try {
await session.send('Emulation.setLocaleOverride', { locale });
} catch (exception) {
// All pages in the same renderer share locale. All such pages belong to the same
// context and if locale is overridden for one of them its value is the same as
// we are trying to set so it's not a problem.
if (exception.message.includes('Another locale override is already in effect'))
return;
throw exception;
}
}
async function emulateTimezone(session: CRSession, timezoneId: string) {View on GitHub (pinned to c8fc3bf8d3)
Solutions
- Re-resolve the element inside the destination page via a selector rather than passing a handle across documents.
- Adopt within the same frame tree only; for popups, query afresh in the popup.
- Discard handles after navigations and re-query them.
Example fix
// before
const handle = await pageA.locator('h1').elementHandle();
const text = await pageB.evaluate(h => h.textContent, handle);
// after
const text = await pageB.locator('h1').textContent(); Defensive patterns
Strategy: validation
Validate before calling
// Do not pass ElementHandles across documents; re-query in the destination.
// Instead of carrying a handle from pageA into pageB.evaluate(...):
const text = await pageB.locator('h1').textContent(); Prevention
- Never pass an ElementHandle from one page into another page's evaluate.
- Re-query elements in the destination page via selectors/locators.
- Discard handles after any navigation and re-resolve them.
When it happens
Trigger: Adopting an element handle across documents that do not share a frame tree (different tabs without a parent link, detached documents, OOIF cross-origin boundaries); the backend node was collected; shadow-DOM hosts that cannot be resolved into the isolated world.
Common situations: Passing an ElementHandle from one page into an evaluate on another; moving handles across popup windows; handles captured before a navigation that invalidated them.
Related errors
- Cannot set input files to detached input element
- Element is not attached to the DOM
- PDF creation is only working with Chromium
- The object has been collected to prevent unbounded heap grow
- Cannot set input files to detached element
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/4f7a180e903abc08.
Report an issue: GitHub.