{"record":{"id":"677421acb35b42b5","repo":"grafana/k6","slug":"frame-has-been-detached","errorCode":null,"errorMessage":"frame has been detached","messagePattern":"frame has been detached","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/common/page.go","lineNumber":671,"sourceCode":"\tparent := f.parentFrame\n\tif parent == nil {\n\t\treturn nil, errors.New(\"frame has been detached 1\")\n\t}\n\n\trootFrame := f\n\tfor ; rootFrame.parentFrame != nil; rootFrame = rootFrame.parentFrame {\n\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;","sourceCodeStart":653,"sourceCodeEnd":689,"githubUrl":"https://github.com/grafana/k6/blob/3fcf5388d78cb382f0c0d42ec556a06bfd1b8dee/internal/js/modules/k6/browser/common/page.go#L653-L689","documentation":"In getFrameElement (page.go:665-672) k6 issues CDP DOM.getFrameOwner to find which DOM node owns the frame. The call failed and the CDP error string contained 'frame with the given id was not found' — the browser had already removed this frame from its frame tree while k6 was asking for its owner, so k6 maps it to 'frame has been detached'.","triggerScenarios":"frameElement() racing frame removal: the iframe is deleted by page script, the page navigates away, or the frame tree is rebuilt, all between k6's tree walk and the CDP round-trip.","commonSituations":"Dynamic iframes (ads, embeds, editors) destroyed on a timer; scripts removing iframes right after content loads; assertions on iframes that the WuT intentionally tears down.","solutions":["Re-check page.frames() and retry frameElement() on a frame that is still listed","Wait for the iframe to be stably present (waitForSelector on the iframe element) before querying frameElement","Query iframe content via frame.waitForSelector instead of resolving the frame element","Guard the call with try-catch and treat this message as 'frame gone, re-acquire'"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Confirm the frame still exists in the tree before asking for its element\nconst stillThere = page.frames().some(fr => fr.id === frame.id);\nif (!stillThere) throw new Error('frame removed; re-acquire');\nawait frame.frameElement();","typeGuard":null,"tryCatchPattern":"try {\n  await frame.frameElement();\n} catch (e) {\n  if (/frame has been detached/.test(e.message)) {\n    return null; // frame removed mid-call — treat as absent\n  }\n  throw e;\n}","preventionTips":["Wait for the iframe element to be attached and stable before frameElement()","Prefer querying frame content (waitForSelector) over resolving the frame element","Do not poll frame properties in a loop while the page unloads"],"tags":["browser","iframe","frame","detached","cdp"],"backgroundTag":"frame-detached","analyzedSha":"3fcf5388d78cb382f0c0d42ec556a06bfd1b8dee","analyzedAt":"2026-08-18T03:05:52.393Z","contentChangedAt":"2026-08-18T03:05:52.393Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}