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
- Call browser.close() explicitly instead of passively waiting for disconnection
- Wrap the await in Promise.race with your own timeout so the iteration ending never races the event
- 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
- Call browser.close() rather than waiting for disconnection
- Bound any wait with Promise.race and your own timeout
- Move long lifecycle waits to teardown()
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
- iteration ended before page.on handler completed executing
- waiting for load state %q: %w
- waiting for page %s event: %w
- cannot close context as none is active in browser
- ErrFatal
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/91615bebb56e5bc0.
Report an issue: GitHub.