grafana/k6 · error

frame has been detached

Error message

frame has been detached

What it means

In getFrameElement (page.go:665-672) k6 issues CDP DOM.getFrameOwner to find which DOM node owns the frame. The call failed and the CDP error string contained 'frame with the given id was not found' — the browser had already removed this frame from its frame tree while k6 was asking for its owner, so k6 maps it to 'frame has been detached'.

Solutions

  1. Re-check page.frames() and retry frameElement() on a frame that is still listed
  2. Wait for the iframe to be stably present (waitForSelector on the iframe element) before querying frameElement
  3. Query iframe content via frame.waitForSelector instead of resolving the frame element
  4. Guard the call with try-catch and treat this message as 'frame gone, re-acquire'
Defensive patterns

Strategy: try-catch

Validate before calling

// Confirm the frame still exists in the tree before asking for its element
const stillThere = page.frames().some(fr => fr.id === frame.id);
if (!stillThere) throw new Error('frame removed; re-acquire');
await frame.frameElement();

Try / catch

try {
  await frame.frameElement();
} catch (e) {
  if (/frame has been detached/.test(e.message)) {
    return null; // frame removed mid-call — treat as absent
  }
  throw e;
}

Prevention

When it happens

Trigger: frameElement() racing frame removal: the iframe is deleted by page script, the page navigates away, or the frame tree is rebuilt, all between k6's tree walk and the CDP round-trip.

Common situations: Dynamic iframes (ads, embeds, editors) destroyed on a timer; scripts removing iframes right after content loads; assertions on iframes that the WuT intentionally tears down.

Related errors


AI-assisted analysis of grafana/k6@3fcf5388d7 (2026-08-18). Data as JSON: /api/errors/677421acb35b42b5. Report an issue: GitHub.

Appendix: source

Thrown at internal/js/modules/k6/browser/common/page.go:671

	parent := f.parentFrame
	if parent == nil {
		return nil, errors.New("frame has been detached 1")
	}

	rootFrame := f
	for ; rootFrame.parentFrame != nil; rootFrame = rootFrame.parentFrame {
	}

	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;

View on GitHub (pinned to 3fcf5388d7)