microsoft/playwright · error · Error

Frame has been detached.

Error message

Frame has been detached.

What it means

Thrown by CRPage._sessionForFrame (crPage.ts:149). It walks up the frame tree looking for a frame that owns a CDP frame session; if it reaches a frame with no parentFrame() before finding one, the frame has been detached from the document and no longer has a live target.

Source

Thrown at packages/playwright-core/src/server/chromium/crPage.ts:149

    const frameSessions = Array.from(this._sessions.values());
    await Promise.all(frameSessions.map(frameSession => {
      if (frameSession._isMainFrame())
        return cb(frameSession);
      return cb(frameSession).catch(e => {
        // Broadcasting a message to the closed iframe should be a noop.
        if (isSessionClosedError(e))
          return;
        throw e;
      });
    }));
  }

  _sessionForFrame(frame: frames.Frame): FrameSession {
    // Frame id equals target id.
    while (!this._sessions.has(frame._id)) {
      const parent = frame.parentFrame();
      if (!parent)
        throw new Error(`Frame has been detached.`);
      frame = parent;
    }
    return this._sessions.get(frame._id)!;
  }

  private _sessionForHandle(handle: dom.ElementHandle): FrameSession {
    const frame = handle._context.frame;
    return this._sessionForFrame(frame);
  }

  willBeginDownload() {
    this._mainFrameSession._willBeginDownload();
  }

  didClose() {
    for (const session of this._sessions.values())
      session.dispose();
    this._page._didClose();

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Re-acquire the frame from page.frame() / page.frames() right before each action.
  2. Use frameLocator() with waitFor so a fresh frame is resolved each time.
  3. Guard with frame.isDetached() (or check parentFrame()) before acting.
Defensive patterns

Strategy: validation

Validate before calling

// Re-resolve the frame immediately before use; bail if detached.
const frame = page.frame({ name: 'widget' });
if (!frame || frame.isDetached()) throw new Error('widget frame is gone');
await frame.evaluate(() => document.title);

Type guard

function isLiveFrame(f: Frame | null | undefined): f is Frame {
  return !!f && !f.isDetached() && !!f.parentFrame();
}

Prevention

When it happens

Trigger: Operating on a Frame object whose <iframe> was removed from the DOM (OOIF detached); calling frame.evaluate / frame.click after the iframe was torn down; holding a stale Frame reference from page.frames().

Common situations: SPAs that remove iframes during state changes; tests that cache childFrames() then act on them after a route change; popups closed mid-action.

Related errors


AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12). Data as JSON: /api/errors/e5c12e84a3b5fb18. Report an issue: GitHub.