grafana/k6 · warning

closing a page: %w

Error message

closing a page: %w

What it means

Page.Close sends target.CloseTarget to the browser; if that command fails with anything other than context.Canceled, this error is recorded. context.Canceled is explicitly tolerated because closing a target races with session teardown, but other failures (target not found, browser connection dead) surface here.

Source

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

		add := runtime.RemoveBinding(webVitalBinding)
		if err := add.Do(cdp.WithExecutor(teardownTimeoutCtx, p.session)); err != nil {
			// continue so that we can shutdown the page even if we fail to remove the binding.
			closeErrs = append(closeErrs, fmt.Errorf("internal error while removing binding from page: %w", err))
		}

		err := target.CloseTarget(p.targetID).Do(cdp.WithExecutor(teardownTimeoutCtx, p.session))
		if err != nil && !errors.Is(err, context.Canceled) {
			// When a close target command is sent to the browser via CDP,
			// the browser will start to cleanup and the first thing it
			// will do is return a target.EventDetachedFromTarget, which in
			// our implementation will close the session connection (this
			// does not close the CDP websocket, just removes the session
			// so no other CDP calls can be made with the session ID).
			// This can result in the session's context being closed while
			// we're waiting for the response to come back from the browser
			// for this current command (it's racey).
			closeErrs = append(closeErrs, fmt.Errorf("closing a page: %w", err))
		}

		// Start the teardown of the page's resources (FrameSessions, NetworkManagers, etc)
		// and wait for them to finish their teardown. This allows for a graceful cleanup
		// of resources and ensures that all events are processed before the page is closed.
		p.cancelCtx()
		p.waitForFrameSessions()

		if len(closeErrs) > 0 {
			p.closeErr = spanRecordError(span, errors.Join(closeErrs...))
		}
	})

	return p.closeErr
}

// Content returns the HTML content of the page.
func (p *Page) Content() (string, error) {

View on GitHub (pinned to 93accf6570)

Solutions

  1. Wrap page.close() in a catch when the tab's lifetime is not fully under your control
  2. Check page.isClosed() before explicit close
  3. Verify the browser is still running before teardown if crashes are suspected
  4. Only escalate the error if subsequent operations on that browser still fail

Example fix

// before
await page.close(); // throws if tab already gone

// after
try {
  await page.close();
} catch (e) {
  if (!/closing a page/.test(e.message)) throw e;
  console.warn('page already closed elsewhere');
}
Defensive patterns

Strategy: validation

Validate before calling

if (!page.isClosed()) { await page.close(); }

Try / catch

try {
  await page.close();
} catch (e) {
  if (!/closing a page/.test(e.message)) throw e; // tab was likely already gone
}

Prevention

When it happens

Trigger: page.close() when the tab was already closed (manually or by the site), the browser process crashed, or the DevTools websocket dropped. The close proceeds with local teardown regardless, and the error is joined into page.close()'s return.

Common situations: Site JavaScript (window.close, target=_blank flows) closing the tab first; browser crash earlier in the run; closing pages in the wrong order relative to browser.close(); reusing a page handle after its tab died.

Related errors


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