grafana/k6 · error

removing child frames recursively: %w

Error message

removing child frames recursively: %w

What it means

Thrown in FrameManager.frameNavigated while pruning the navigated frame's child frames before installing the new document: one of the recursive removeFramesRecursively calls failed and the error is re-wrapped with this prefix. The root cause is almost always the same as error 837 — Frame.detach() failing (usually disposing the document handle or stopping frame sessions) while the frame tree is being rebuilt during navigation.

Source

Thrown at internal/js/modules/k6/browser/common/frame_manager.go:269

		// isn't a bug in chrome, and seems to be intended behavior. Instead
		// of worrying about the nil frame and causing the test to fail when
		// the frame is nil, we can instead return early. The frame will
		// be initialized when getFrameTree CDP request is made, which will
		// call onFrameAttached and onFrameNavigated.

		return nil
	}

	m.logger.Debugf("FrameManager:frameNavigated:removeFrames",
		"fmid:%d fid:%v pfid:%v docid:%s fname:%s furl:%s initial:%t",
		m.ID(), frameID, parentFrameID, documentID, name, url, initial)

	if frame != nil {
		m.framesMu.Unlock()
		for _, child := range frame.ChildFrames() {
			if err := m.removeFramesRecursively(child); err != nil {
				m.framesMu.Lock()
				return fmt.Errorf("removing child frames recursively: %w", err)
			}
		}
		m.framesMu.Lock()
	}

	var mainFrame *Frame
	if isMainFrame && frame == nil {
		m.logger.Debugf("FrameManager:frameNavigated:MainFrame:initialMainFrameNavigation",
			"fmid:%d fid:%v pfid:%v docid:%s fname:%s furl:%s initial:%t",
			m.ID(), frameID, parentFrameID, documentID, name, url, initial)

		// Initial main frame navigation.
		frame = NewFrame(m.ctx, m, nil, frameID, m.logger)
		mainFrame = frame
	} else if isMainFrame && frame.ID() != string(frameID) {
		m.logger.Debugf("FrameManager:frameNavigated:MainFrame:delete",
			"fmid:%d fid:%v pfid:%v docid:%s fname:%s furl:%s initial:%t oldfid:%v",
			m.ID(), frameID, parentFrameID, documentID, name, url, initial, frame.ID())

View on GitHub (pinned to 93accf6570)

Solutions

  1. Upgrade k6 — the frame manager/negotiation code saw substantial race fixes
  2. Sequence operations: fully await one goto before triggering the next navigation or close
  3. If intermittent on an ad-heavy page, consider blocking ad/frame hosts to reduce tree churn

Example fix

// before
page.goto('https://example.com'); // not awaited
page.goto('https://example.com/other'); // racing navigations

// after
await page.goto('https://example.com');
await page.goto('https://example.com/other');
Defensive patterns

Strategy: retry

Type guard

function isFrameCleanupError(e) {
  return e instanceof Error && /removing (child )?frames? recursively/.test(e.message);
}

Try / catch

try {
  await page.goto(url);
} catch (e) {
  if (isFrameCleanupError(e)) { return await page.goto(url); } // one retry; race is transient
  throw e;
}

Prevention

When it happens

Trigger: A page with iframes navigating while a child frame's CDP session/context is concurrently being torn down; navigation racing page.close(); browser-initiated frame detach (ad iframe removed) colliding with the k6-side cleanup.

Common situations: Ad-heavy or iframe-heavy sites under load; scripts that navigate and close pages in quick succession; older k6 browser versions with frame-tree races (this area was rewritten in later k6 releases).

Related errors


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