microsoft/playwright · error · Error

Frame has been detached.

Error message

Frame has been detached.

What it means

getFrameElement() throws when frame.parentFrame() is null — the frame is the main frame or has no living parent. Without a parent document there is no owner <iframe> element to resolve, so the operation is undefined. Mirrors the wvPage variant but on the standard WKPage backend.

Source

Thrown at packages/playwright-core/src/server/webkit/wkPage.ts:1018

    const result = await this._session.sendMayFail('DOM.resolveNode', {
      objectId: handle._objectId,
      executionContextId: (to.delegate as WKExecutionContext)._contextId
    });
    if (!result || result.object.subtype === 'null')
      throw new Error(dom.kUnableToAdoptErrorMessage);
    return createHandle(to, result.object) as dom.ElementHandle<T>;
  }

  async inputActionEpilogue(): Promise<void> {
  }

  async resetForReuse(progress: Progress): Promise<void> {
  }

  async getFrameElement(frame: frames.Frame): Promise<dom.ElementHandle> {
    const parent = frame.parentFrame();
    if (!parent)
      throw new Error('Frame has been detached.');
    const context = await parent.mainContext();
    const result = await this._session.send('DOM.resolveNode', {
      frameId: frame._id,
      executionContextId: (context.delegate as WKExecutionContext)._contextId
    });
    if (!result || result.object.subtype === 'null')
      throw new Error('Frame has been detached.');
    return createHandle(context, result.object) as dom.ElementHandle;
  }

  private _maybeCancelCoopNavigationRequest(provisionalPage: WKProvisionalPage) {
    const navigationRequest = provisionalPage.coopNavigationRequest();
    for (const [requestId, request] of this._requestIdToRequest) {
      if (request.request === navigationRequest) {
        // Make sure the request completes if the provisional navigation is canceled.
        this._onLoadingFailed(provisionalPage._session, {
          requestId: requestId,
          errorText: 'Provisiolal navigation canceled.',

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Guard with frame.parentFrame() and frame.isDetached() before calling frame.frameElement().
  2. Skip owner-element resolution for the main frame entirely.
  3. Re-acquire frames from page.frames() after navigation.

Example fix

// before
const el = await frame.frameElement();

// after
if (!frame.parentFrame() || frame.isDetached()) return null;
const el = await frame.frameElement();
Defensive patterns

Strategy: validation

Validate before calling

function canGetOwner(frame) { return !!frame.parentFrame() && !frame.isDetached(); }
if (canGetOwner(frame)) await frame.frameElement();

Type guard

function hasLivingParent(frame: any): boolean { return !!frame?.parentFrame() && !frame.isDetached(); }

Try / catch

try { return await frame.frameElement(); }
catch (e) { if (/Frame has been detached/.test(e.message)) return null; throw e; }

Prevention

When it happens

Trigger: Calling frame.frameElement() (or internal getFrameElement) on page.mainFrame() or on a frame whose parent was detached. Any snapshot/locator path that resolves a frame's owner element.

Common situations: Calling frameElement() on the top frame. Holding a stale child Frame after the parent removed it. Race between detach and an owner-element resolution.

Related errors


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