grafana/k6 · error

disposing browser context

Error message

disposing browser context: %w

What it means

Close() passed the guard for the default context and issued Target.disposeBrowserContext via browser.disposeContext(b.id), but the CDP call failed. The wrapped error usually says the browser context ID was not found (already disposed) or reports a lost connection. The context object on the Go side may be left in a half-closed state.

Solutions

  1. Close each context exactly once — track it with a flag or rely on structure instead of repeated finally blocks
  2. Order teardown: close pages, then contexts, then the browser, and don't close contexts after browser.close()
  3. Inspect the wrapped error: 'not found' means it was already gone (safe to ignore), connection errors mean the browser died
  4. Wrap teardown closes in try/catch and log rather than fail the test

Example fix

// before
ctx.close();
// ... later, cleanup path runs again
ctx.close(); // throws 'disposing browser context'

// after
let closed = false;
function cleanup() {
  if (!closed) {
    closed = true;
    ctx.close();
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// make close idempotent in your page/context wrapper
function once(fn) {
  let done = false;
  return () => {
    if (!done) { done = true; fn(); }
  };
}
const closeCtx = once(() => ctx.close());

Try / catch

try {
  ctx.close();
} catch (e) {
  const msg = String(e.message);
  if (msg.includes('disposing browser context') && /not found|no target/i.test(msg)) {
    // already disposed elsewhere: safe to ignore
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Closing a context twice (double-close); closing a context the browser already disposed of during shutdown; closing while the CDP WebSocket to Chromium is disconnected; pages in the context being torn down concurrently.

Common situations: User teardown code and k6's own cleanup both closing contexts; close() in a finally block running after the browser process died; races between parallel VU iterations closing a shared context.

Related errors


AI-assisted analysis of grafana/k6@01ffac6f24 (2026-08-18). Data as JSON: /api/errors/5db87485e3a49b76. Report an issue: GitHub.

Appendix: source

Thrown at internal/js/modules/k6/browser/common/browser_context.go:213

	b.logger.Debugf("BrowserContext:ClearPermissions", "bctxid:%v", b.id)

	action := cdpbrowser.ResetPermissions().WithBrowserContextID(b.id)
	if err := action.Do(cdp.WithExecutor(b.ctx, b.browser.conn)); err != nil {
		return fmt.Errorf("clearing permissions: %w", err)
	}

	return nil
}

// Close shuts down the browser context.
func (b *BrowserContext) Close() error {
	b.logger.Debugf("BrowserContext:Close", "bctxid:%v", b.id)

	if b.id == "" {
		return fmt.Errorf("default browser context can't be closed")
	}
	if err := b.browser.disposeContext(b.id); err != nil {
		return fmt.Errorf("disposing browser context: %w", err)
	}
	return nil
}

// GrantPermissions enables the specified permissions, all others will be disabled.
func (b *BrowserContext) GrantPermissions(permissions []string, opts GrantPermissionsOptions) error {
	b.logger.Debugf("BrowserContext:GrantPermissions", "bctxid:%v", b.id)

	permsToProtocol := map[string]cdpbrowser.PermissionType{
		"geolocation":          cdpbrowser.PermissionTypeGeolocation,
		"midi":                 cdpbrowser.PermissionTypeMidi,
		"midi-sysex":           cdpbrowser.PermissionTypeMidiSysex,
		"notifications":        cdpbrowser.PermissionTypeNotifications,
		"camera":               cdpbrowser.PermissionTypeVideoCapture,
		"microphone":           cdpbrowser.PermissionTypeAudioCapture,
		"background-sync":      cdpbrowser.PermissionTypeBackgroundSync,
		"ambient-light-sensor": cdpbrowser.PermissionTypeSensors,
		"accelerometer":        cdpbrowser.PermissionTypeSensors,

View on GitHub (pinned to 01ffac6f24)