{"record":{"id":"91fc5b037c65de3d","repo":"grafana/k6","slug":"removing-frames-recursively-detaching-frame-w","errorCode":null,"errorMessage":"removing frames recursively: detaching frame: %w","messagePattern":"removing frames recursively: detaching frame: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/common/frame_manager.go","lineNumber":457,"sourceCode":"\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (m *FrameManager) removeFramesRecursively(frame *Frame) error {\n\tfor _, child := range frame.ChildFrames() {\n\t\tm.logger.Debugf(\"FrameManager:removeFramesRecursively\",\n\t\t\t\"fmid:%d cfid:%v pfid:%v cfname:%s cfurl:%s\",\n\t\t\tm.ID(), child.ID(), frame.ID(), child.Name(), child.URL())\n\n\t\tif err := m.removeFramesRecursively(child); err != nil {\n\t\t\treturn fmt.Errorf(\"removing frames recursively: %w\", err)\n\t\t}\n\t}\n\n\tif err := frame.detach(); err != nil {\n\t\treturn fmt.Errorf(\"removing frames recursively: detaching frame: %w\", err)\n\t}\n\n\tm.framesMu.Lock()\n\tm.logger.Debugf(\"FrameManager:removeFramesRecursively:delParentFrame\",\n\t\t\"fmid:%d fid:%v fname:%s furl:%s\",\n\t\tm.ID(), frame.ID(), frame.Name(), frame.URL())\n\n\tdelete(m.frames, cdp.FrameID(frame.ID()))\n\tm.framesMu.Unlock()\n\n\treturn nil\n}\n\nfunc (m *FrameManager) requestFailed(req *Request, canceled bool) {\n\tm.logger.Debugf(\"FrameManager:requestFailed\", \"fmid:%d rurl:%s\", m.ID(), req.URL())\n\n\tframe := req.getFrame()\n\tif frame == nil {","sourceCodeStart":439,"sourceCodeEnd":475,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/browser/common/frame_manager.go#L439-L475","documentation":"The leaf error of the frame-removal chain: FrameManager.removeFramesRecursively reports that Frame.detach() itself failed. detach() marks the frame detached, unlinks it from the parent, and disposes the document handle over CDP; if that disposal (or the session teardown it triggers) errors — often because the browser already destroyed the context — the error surfaces here and is wrapped by the recursive callers.","triggerScenarios":"Detaching a frame whose CDP runtime context was already destroyed (navigation race); disposing a document handle for an executed-context-destroyed world; page/browser closing while frame cleanup runs.","commonSituations":"Intermittent errors during goto on iframe-heavy pages or during browserContext.close/page.close; older k6 builds; headless-only timing differences in CI.","solutions":["Upgrade k6 — detach/teardown races are among the most-fixed areas in the browser module","Await page and context close at the end of the scenario instead of abandoning them","If reproducible, run with -v environment=DEBUG and file an issue with the frame IDs from the log"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"function isFrameDetachError(e) {\n  return e instanceof Error && /detaching frame/.test(e.message);\n}","tryCatchPattern":"try {\n  await page.close();\n} catch (e) {\n  if (isFrameDetachError(e)) { /* ignore: target already torn down by browser */ }\n  else throw e;\n}","preventionTips":["Always await context/page teardown at scenario end","Treat detach-during-close as benign; treat detach-during-goto as a retry signal"],"tags":["browser","frame-detach","cdp","internal","race"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}