grafana/k6 · error · errBrowserClosing
browser is closing
Error message
browser is closing
What it means
The Browser type keeps an atomic closing flag set by Close() and by context teardown. When a new target (page/popup) tries to attach after closing has started, attachNewPage rejects it with errBrowserClosing (internal/js/modules/k6/browser/common/browser.go:368). The guard prevents mutating a browser that is already shutting down; the error usually surfaces as a race between teardown and a late CDP Target.attachedToTarget event.
Source
Thrown at internal/js/modules/k6/browser/common/browser.go:369
}
detachSession(session)
return nil
}
// Emit the page event only for pages, not for background pages.
// Background pages are created by extensions.
if isPage {
browserCtx.emit(EventBrowserContextPage, p)
}
return nil
}
// errBrowserClosing is returned when a page attachment
// is rejected because the browser has started closing.
var errBrowserClosing = errors.New("browser is closing")
// attachNewPage checks whether the browser is closing and, if not, attaches
// the page. Returns errBrowserClosing if the browser is shutting down.
func (b *Browser) attachNewPage(p *Page, ev *target.EventAttachedToTarget) error {
targetPage := ev.TargetInfo
attachPage := func() error {
b.pagesMu.Lock()
defer b.pagesMu.Unlock()
if b.closing.Load() {
return errBrowserClosing
}
b.logger.Debugf(
"Browser:attachNewPage:addTarget",
"sid:%v tid:%v pageType:%s",
ev.SessionID, targetPage.TargetID, targetPage.Type,View on GitHub (pinned to 94169daab2)
Solutions
- Await every action that can open a page before closing: const popup = await page.waitForEvent('popup'); await popup.close(); then browser.close() last
- If a popup may appear, handle it explicitly (waitForEvent('popup')) instead of letting it race teardown
- During teardown, catch and ignore this specific error when the page is no longer needed
- Extend the scenario/iteration timeout if k6 is canceling mid-navigation
Example fix
// before
await page.click('#opens-popup');
browser.close(); // popup attach still in flight -> "browser is closing"
// after
const popup = await page.waitForEvent('popup');
await popup.close();
browser.close(); Defensive patterns
Strategy: try-catch
Try / catch
// tolerate the teardown race when the extra page is no longer needed
try {
const page = await browser.newPage();
} catch (e) {
if (!String(e?.message || e).includes('browser is closing')) throw e;
// shutting down — skip page setup
} Prevention
- Make browser.close() the last statement and await every navigation/popup before it
- Handle popups explicitly with page.waitForEvent('popup') instead of letting them race teardown
- Never reuse a browser instance after close(); create it per iteration via the module API
- Give scenarios enough duration that page creation is not canceled mid-flight
When it happens
Trigger: A click/navigation opens a popup at the same moment browser.close() runs at the end of the default function; calling browser.newPage()/newContext() after close(); the scenario timeout canceling the context while a target-attached event is in flight; reusing a browser handle across iterations after it was closed.
Common situations: Popup-heavy tests where the last interaction triggers a new tab during teardown; close() in a finally block racing in-flight CDP events; shared browser instances across VUs; tight test durations that cut off page creation.
Related errors
- page.route('%s'): iteration ended before route completed
- default browser context can't be closed
- disposing browser context: %w
- waitForEvent timed out after %v
- getting iframe frame: %w
AI-assisted analysis of grafana/k6@94169daab2 (2026-08-18).
Data as JSON: /api/errors/8539394ca6d0721a.
Report an issue: GitHub.