grafana/k6 · warning

browser.on promise rejected: %w

Error message

browser.on promise rejected: %w

What it means

browser.on('disconnected') blocks until the browser loses its CDP connection; this error (browser.go:780) is returned when the VU context — the k6 iteration — is canceled first. ContextErr unwraps to context.Canceled or context.DeadlineExceeded, meaning the iteration or scenario ended (duration elapsed, timeout) while the promise was still pending, so the wait is aborted and the promise rejects.

Source

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

	if err != nil {
		return nil, spanRecordError(span, err)
	}

	return page, nil
}

// On returns a Promise that is resolved when the browser process is disconnected.
// The only accepted event value is "disconnected".
func (b *Browser) On(event string) (bool, error) {
	if event != EventBrowserDisconnected {
		return false, fmt.Errorf("unknown browser event: %q, must be %q", event, EventBrowserDisconnected)
	}

	select {
	case <-b.browserProc.lostConnection:
		return true, nil
	case <-b.vuCtx.Done():
		return false, fmt.Errorf("browser.on promise rejected: %w", ContextErr(b.vuCtx))
	}
}

// UserAgent returns the controlled browser's user agent string.
func (b *Browser) UserAgent() string {
	return b.version.userAgent
}

// Version returns the controlled browser's version.
func (b *Browser) Version() string {
	product := b.version.product
	_, after, ok := strings.Cut(product, "/")
	if !ok {
		return product
	}
	return after
}

View on GitHub (pinned to 93accf6570)

Solutions

  1. Call browser.close() explicitly instead of passively waiting for disconnection
  2. Wrap the await in Promise.race with your own timeout so the iteration ending never races the event
  3. Do long waits in teardown(), where the browser lifecycle is still managed by k6, rather than at the tail of defaultFn

Example fix

// before
await browser.on('disconnected') // may reject: iteration ends first

// after
await browser.close() // deterministic disconnect
// or, if you must wait:
await Promise.race([browser.on('disconnected'), sleep(5000)])
Defensive patterns

Strategy: try-catch

Validate before calling

// prefer deterministic disconnect:
// await browser.close() instead of awaiting 'disconnected'

Try / catch

try {
  await browser.on('disconnected')
} catch (e) {
  if (/browser.on promise rejected/.test(String(e))) {
    // iteration ended before disconnect — acceptable at test end
  } else throw e
}

Prevention

When it happens

Trigger: Awaiting browser.on('disconnected') without ever calling browser.close(); scenario duration or gracePeriod expiring while chromium is still connected; the browser being managed/closed outside this VU so disconnection never arrives within the iteration.

Common situations: Waiting for the browser to disconnect at the end of a test instead of initiating the disconnect; long-lived browser outliving the iteration; strict scenario timeouts in CI.

Related errors


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