{"record":{"id":"e5c12e84a3b5fb18","repo":"microsoft/playwright","slug":"frame-has-been-detached-e5c12e","errorCode":null,"errorMessage":"Frame has been detached.","messagePattern":"Frame has been detached\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/playwright-core/src/server/chromium/crPage.ts","lineNumber":149,"sourceCode":"    const frameSessions = Array.from(this._sessions.values());\n    await Promise.all(frameSessions.map(frameSession => {\n      if (frameSession._isMainFrame())\n        return cb(frameSession);\n      return cb(frameSession).catch(e => {\n        // Broadcasting a message to the closed iframe should be a noop.\n        if (isSessionClosedError(e))\n          return;\n        throw e;\n      });\n    }));\n  }\n\n  _sessionForFrame(frame: frames.Frame): FrameSession {\n    // Frame id equals target id.\n    while (!this._sessions.has(frame._id)) {\n      const parent = frame.parentFrame();\n      if (!parent)\n        throw new Error(`Frame has been detached.`);\n      frame = parent;\n    }\n    return this._sessions.get(frame._id)!;\n  }\n\n  private _sessionForHandle(handle: dom.ElementHandle): FrameSession {\n    const frame = handle._context.frame;\n    return this._sessionForFrame(frame);\n  }\n\n  willBeginDownload() {\n    this._mainFrameSession._willBeginDownload();\n  }\n\n  didClose() {\n    for (const session of this._sessions.values())\n      session.dispose();\n    this._page._didClose();","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/microsoft/playwright/blob/c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6/packages/playwright-core/src/server/chromium/crPage.ts#L131-L167","documentation":"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.","triggerScenarios":"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().","commonSituations":"SPAs that remove iframes during state changes; tests that cache childFrames() then act on them after a route change; popups closed mid-action.","solutions":["Re-acquire the frame from page.frame() / page.frames() right before each action.","Use frameLocator() with waitFor so a fresh frame is resolved each time.","Guard with frame.isDetached() (or check parentFrame()) before acting."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Re-resolve the frame immediately before use; bail if detached.\nconst frame = page.frame({ name: 'widget' });\nif (!frame || frame.isDetached()) throw new Error('widget frame is gone');\nawait frame.evaluate(() => document.title);","typeGuard":"function isLiveFrame(f: Frame | null | undefined): f is Frame {\n  return !!f && !f.isDetached() && !!f.parentFrame();\n}","tryCatchPattern":null,"preventionTips":["Never cache Frame references across await boundaries that may navigate.","Use page.frameLocator() with built-in retry instead of imperative frame lookups.","Check frame.isDetached() before each action."],"tags":["frame","lifecycle","chromium"],"backgroundTag":null,"analyzedSha":"c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6","analyzedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}