grafana/k6 · error
frame has been detached 2
Error message
frame has been detached 2
What it means
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.
Source
Thrown at internal/js/modules/k6/browser/common/page.go:676
}
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 93accf6570)
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
Example fix
// before
let el;
try { el = await frame.frameElement(); } catch (e) { /* lost */ }
// after
let el;
for (let i = 0; i < 3; i++) {
const f = page.frames().find(f => f.name() === 'app');
if (!f) break;
try { el = await f.frameElement(); break; }
catch (e) { await page.waitForTimeout(200); }
} Defensive patterns
Strategy: try-catch
Validate before calling
// Minimize the race window: fresh frame + immediate call
const frame = page.frames().find(f => f.name() === 'app');
if (frame && frame.parentFrame()) { const el = await frame.frameElement(); } Try / catch
for (let i = 0; i < 3; i++) {
const f = page.frames().find(f => f.name() === 'app');
if (!f) break;
try { el = await f.frameElement(); break; }
catch (e) { if (!String(e.message).includes('detached')) throw e; await page.waitForTimeout(200); }
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- getting document element: nil document
- waitFor retry threshold reached
- parent frame has been detached
- no frame found for id %s
- getting new document handle: %w
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/c4c039caf5a2025c.
Report an issue: GitHub.