microsoft/playwright · critical · Error
Frame was detached
Error message
Frame was detached
What it means
frame.context(world) awaits the world's contextPromise. If the promise resolves to a destroyed marker (not an ExecutionContext) - because the frame was detached/destroyed - it throws the stored destroyedReason, conventionally 'Frame was detached'.
Source
Thrown at packages/playwright-core/src/server/frames.ts:795
}
async waitForLoadState(progress: Progress, state: types.LifecycleEvent): Promise<void> {
const waitUntil = verifyLifecycle('state', state);
if (!this._firedLifecycleEvents.has(waitUntil))
await helper.waitForEvent(progress, this, Frame.Events.AddLifecycle, (e: types.LifecycleEvent) => e === waitUntil).promise;
}
async frameElement(progress: Progress): Promise<dom.ElementHandle> {
return await progress.race(this._page.delegate.getFrameElement(this));
}
context(world: types.World): Promise<dom.FrameExecutionContext> {
if (this._page.delegate.noUtilityWorld?.())
world = 'main';
return this._contextData.get(world)!.contextPromise.then(contextOrDestroyedReason => {
if (contextOrDestroyedReason instanceof js.ExecutionContext)
return contextOrDestroyedReason;
throw new Error(contextOrDestroyedReason.destroyedReason);
});
}
mainContext(): Promise<dom.FrameExecutionContext> {
return this.context('main');
}
existingContext(world: types.World): dom.FrameExecutionContext | null {
if (this._page.delegate.noUtilityWorld?.())
world = 'main';
return this._contextData.get(world)?.context || null;
}
utilityContext(): Promise<dom.FrameExecutionContext> {
return this.context('utility');
}
async evaluateExpression(progress: Progress, expression: string, options: { isFunction?: boolean, world?: types.World } = {}, arg?: any): Promise<any> {View on GitHub (pinned to c8fc3bf8d3)
Solutions
- Drop stale Frame references; re-query page.frame(...) or page.mainFrame() as needed.
- Guard operations by checking the frame still exists in page.frames().
- Await the action that removes the iframe only after you are done with the frame.
- Listen for the frameDetached event and cancel pending work.
Example fix
// before
const frame = page.frame({ name: 'widget' });
await removeIframe();
await frame.evaluate(() => 1); // throws 'Frame was detached'
// after
const frame = page.frame({ name: 'widget' });
await frame.evaluate(() => 1);
await removeIframe(); Defensive patterns
Strategy: validation
Validate before calling
// Verify the frame is still attached before use
if (!page.frames().includes(frame)) throw new Error('frame detached');
await frame.evaluate(() => 1); Type guard
function isFrameAlive(page: any, frame: any): boolean {
return page.frames().includes(frame);
} Try / catch
try {
await frame.evaluate(fn);
} catch (e) {
if (e instanceof Error && /Frame was detached|Target frame/i.test(e.message)) { /* re-acquire */ }
else throw e;
} Prevention
- Never retain a Frame reference across DOM operations that remove its iframe.
- Re-query frames by name/url at the point of use.
When it happens
Trigger: Requesting a frame's execution context after the frame has been detached from the DOM (removed iframe, closed popup window, navigation that destroyed the frame).
Common situations: Holding a Frame reference and using it after the iframe was removed from the page; acting on popup windows after they closed; operations scheduled on a frame that detached between scheduling and execution.
Related errors
- Frame has been detached.
- Frame has been detached.
- Frame has been detached.
- "${part.body}" is only allowed as the first selector token,
- Selector cannot start with entering frame, select the iframe
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/a1a9ec85d38a6b6e.
Report an issue: GitHub.