{"record":{"id":"c4c039caf5a2025c","repo":"grafana/k6","slug":"frame-has-been-detached-2","errorCode":null,"errorMessage":"frame has been detached 2","messagePattern":"frame has been detached 2","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/common/page.go","lineNumber":676,"sourceCode":"\t}\n\n\tparentSession, ok := p.getFrameSession(cdp.FrameID(rootFrame.ID()))\n\tif !ok {\n\t\treturn nil, errors.New(\"parent frame has been detached\")\n\t}\n\n\taction := dom.GetFrameOwner(cdp.FrameID(f.ID()))\n\tbackendNodeID, _, err := action.Do(cdp.WithExecutor(p.ctx, parentSession.session))\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"frame with the given id was not found\") {\n\t\t\treturn nil, errors.New(\"frame has been detached\")\n\t\t}\n\t\treturn nil, fmt.Errorf(\"getting frame owner: %w\", err)\n\t}\n\n\tparent = f.parentFrame\n\tif parent == nil {\n\t\treturn nil, errors.New(\"frame has been detached 2\")\n\t}\n\treturn parent.adoptBackendNodeID(mainWorld, backendNodeID)\n}\n\nfunc (p *Page) getOwnerFrame(apiCtx context.Context, h *ElementHandle) (cdp.FrameID, error) {\n\tp.logger.Debugf(\"Page:getOwnerFrame\", \"sid:%v\", p.sessionID())\n\n\t// document.documentElement has frameId of the owner frame\n\tpageFn := `\n\t\tnode => {\n\t\t\tconst doc = node;\n      \t\tif (doc.documentElement && doc.documentElement.ownerDocument === doc)\n        \t\treturn doc.documentElement;\n      \t\treturn node.ownerDocument ? node.ownerDocument.documentElement : null;\n\t\t}\n\t`\n\n\topts := evalOptions{","sourceCodeStart":658,"sourceCodeEnd":694,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/browser/common/page.go#L658-L694","documentation":"Even after dom.GetFrameOwner succeeds, Page.getFrameElement re-reads f.parentFrame (page.go:673-677). If it became nil, the frame was detached from the frame tree during the CDP round-trip — 'frame has been detached 2' marks that mid-call race between the owner lookup and the adoption step.","triggerScenarios":"The frame is detached concurrently with the frameElement() call: the <iframe> is removed or the page navigates while GetFrameOwner is in flight, so the parent pointer is cleared by the time it is used.","commonSituations":"Fast SPAs unmounting iframes mid-operation; parallel browser actions from multiple VUs against the same page; timing-sensitive tests that pass locally but fail in CI.","solutions":["Retry the complete sequence: re-discover the frame, then call frameElement()","Avoid racing page navigation or close with frame-element queries","Add waitForSelector on the iframe to confirm stability before querying its frame element","Reduce concurrent operations on the same page during iframe churn"],"exampleFix":"// before\nlet el;\ntry { el = await frame.frameElement(); } catch (e) { /* lost */ }\n\n// after\nlet el;\nfor (let i = 0; i < 3; i++) {\n  const f = page.frames().find(f => f.name() === 'app');\n  if (!f) break;\n  try { el = await f.frameElement(); break; }\n  catch (e) { await page.waitForTimeout(200); }\n}","handlingStrategy":"try-catch","validationCode":"// Minimize the race window: fresh frame + immediate call\nconst frame = page.frames().find(f => f.name() === 'app');\nif (frame && frame.parentFrame()) { const el = await frame.frameElement(); }","typeGuard":null,"tryCatchPattern":"for (let i = 0; i < 3; i++) {\n  const f = page.frames().find(f => f.name() === 'app');\n  if (!f) break;\n  try { el = await f.frameElement(); break; }\n  catch (e) { if (!String(e.message).includes('detached')) throw e; await page.waitForTimeout(200); }\n}","preventionTips":["Retry discover+frameElement as one unit when iframes unmount frequently","Don't run navigation/close concurrently with frame-element queries on the same page","Stabilize dynamic pages with waits on the iframe selector first"],"tags":["browser","frame","race-condition"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}