{"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":678,"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":660,"sourceCodeEnd":696,"githubUrl":"https://github.com/grafana/k6/blob/3fcf5388d78cb382f0c0d42ec556a06bfd1b8dee/internal/js/modules/k6/browser/common/page.go#L660-L696","documentation":"Final stage of getFrameElement (page.go:674-679): DOM.getFrameOwner succeeded and returned a backend node id, but by the time k4 adopts that node, f.parentFrame has become nil — the frame detached in the window between the CDP call and the adoption step. The numbered suffix distinguishes it from the two earlier detach checks in the same function.","triggerScenarios":"frameElement() where the frame is detached between the successful GetFrameOwner round-trip and the parent.adoptBackendNodeID call — a pure race, favored by fast navigation or iframe teardown.","commonSituations":"Fast SPA transitions; CI environments where timing jitter widens the race; loops polling frame properties while the page unloads.","solutions":["Retry the whole frameElement() on a freshly fetched frame from page.frames()","Avoid calling frame APIs during known navigation windows — wait for load state first","Reduce concurrent frame operations against the same page during transitions","Upgrade k6 for frame lifecycle race fixes"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"await page.waitForLoadState('load'); // avoid the detach window\nconst el = await frame.frameElement();","typeGuard":null,"tryCatchPattern":"try {\n  await frame.frameElement();\n} catch (e) {\n  if (/detached/.test(e.message)) {\n    const fresh = page.frames().find(fr => fr.id === frame.id);\n    return fresh ? fresh.frameElement() : null;\n  }\n  throw e;\n}","preventionTips":["Batch frame queries away from navigation transitions","Retry with a freshly fetched frame object, never the stale reference","Keep k6 current — these races shrink with each release"],"tags":["browser","iframe","frame","race","detached"],"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"}