grafana/k6 · error
frame has been detached 2
Error message
frame has been detached 2
What it means
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.
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
Defensive patterns
Strategy: retry
Validate before calling
await page.waitForLoadState('load'); // avoid the detach window
const el = await frame.frameElement(); Try / catch
try {
await frame.frameElement();
} catch (e) {
if (/detached/.test(e.message)) {
const fresh = page.frames().find(fr => fr.id === frame.id);
return fresh ? fresh.frameElement() : null;
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Fast SPA transitions; CI environments where timing jitter widens the race; loops polling frame properties while the page unloads.
Related errors
- frame has been detached
- frame has been detached 1
- Error while parsing selector
- frame not found for id
- parent frame has been detached
AI-assisted analysis of grafana/k6@3fcf5388d7 (2026-08-18).
Data as JSON: /api/errors/c4c039caf5a2025c.
Report an issue: GitHub.
Appendix: source
Thrown at internal/js/modules/k6/browser/common/page.go:678
}
parentSession, ok := p.getFrameSession(cdp.FrameID(rootFrame.ID()))
if !ok {
return nil, errors.New("parent frame has been detached")
}
action := dom.GetFrameOwner(cdp.FrameID(f.ID()))
backendNodeID, _, err := action.Do(cdp.WithExecutor(p.ctx, parentSession.session))
if err != nil {
if strings.Contains(err.Error(), "frame with the given id was not found") {
return nil, errors.New("frame has been detached")
}
return nil, fmt.Errorf("getting frame owner: %w", err)
}
parent = f.parentFrame
if parent == nil {
return nil, errors.New("frame has been detached 2")
}
return parent.adoptBackendNodeID(mainWorld, backendNodeID)
}
func (p *Page) getOwnerFrame(apiCtx context.Context, h *ElementHandle) (cdp.FrameID, error) {
p.logger.Debugf("Page:getOwnerFrame", "sid:%v", p.sessionID())
// document.documentElement has frameId of the owner frame
pageFn := `
node => {
const doc = node;
if (doc.documentElement && doc.documentElement.ownerDocument === doc)
return doc.documentElement;
return node.ownerDocument ? node.ownerDocument.documentElement : null;
}
`
opts := evalOptions{View on GitHub (pinned to 3fcf5388d7)