grafana/k6 · error

disposing browser context: %w

Error message

disposing browser context: %w

What it means

BrowserContext.close() calls Target.disposeBrowserContext over CDP via browser.disposeContext (browser_context.go:213); a transport or protocol failure is wrapped as 'disposing browser context: %w'. Typical underlying causes: the context was already disposed (double close), the browser is closing, or the connection is lost.

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 93accf6570)

Solutions

  1. Close each context exactly once; null the reference after close()
  2. Ensure context.close() calls all happen before browser.close() in teardown
  3. Read the wrapped error to distinguish double-dispose from dead connection
  4. Use an idempotent close helper that treats 'already disposed' as success

Example fix

// before
await ctx.close()
await ctx.close() // second call may throw: disposing browser context

// after
let open = true
async function closeCtx() { if (open) { await ctx.close(); open = false } }
await closeCtx()
await closeCtx() // no-op
Defensive patterns

Strategy: try-catch

Validate before calling

let ctxOpen = true
async function closeContext() {
  if (!ctxOpen) return
  await ctx.close()
  ctxOpen = false
}

Try / catch

try {
  await ctx.close()
} catch (e) {
  if (/disposing browser context/.test(String(e)) && /invalid browserContextId|already|closed/i.test(String(e))) {
    ctxOpen = false // treat double-dispose as success
  } else throw e
}

Prevention

When it happens

Trigger: Calling close() twice on the same context; closing a context after browser.close(); the connection dropping mid-close; two VUs closing the same shared context concurrently.

Common situations: Double close from both a helper and teardown; stale context references kept in module-level globals; chromium crashing before teardown runs.

Related errors


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