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

  1. Retry the complete sequence: re-discover the frame, then call frameElement()
  2. Avoid racing page navigation or close with frame-element queries
  3. Add waitForSelector on the iframe to confirm stability before querying its frame element
  4. 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

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


AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15). Data as JSON: /api/errors/c4c039caf5a2025c. Report an issue: GitHub.