grafana/k6 · warning
closing a page: %w
Error message
closing a page: %w
What it means
Page.Close sends target.CloseTarget to the browser; if that command fails with anything other than context.Canceled, this error is recorded. context.Canceled is explicitly tolerated because closing a target races with session teardown, but other failures (target not found, browser connection dead) surface here.
Source
Thrown at internal/js/modules/k6/browser/common/page.go:1021
add := runtime.RemoveBinding(webVitalBinding)
if err := add.Do(cdp.WithExecutor(teardownTimeoutCtx, p.session)); err != nil {
// continue so that we can shutdown the page even if we fail to remove the binding.
closeErrs = append(closeErrs, fmt.Errorf("internal error while removing binding from page: %w", err))
}
err := target.CloseTarget(p.targetID).Do(cdp.WithExecutor(teardownTimeoutCtx, p.session))
if err != nil && !errors.Is(err, context.Canceled) {
// When a close target command is sent to the browser via CDP,
// the browser will start to cleanup and the first thing it
// will do is return a target.EventDetachedFromTarget, which in
// our implementation will close the session connection (this
// does not close the CDP websocket, just removes the session
// so no other CDP calls can be made with the session ID).
// This can result in the session's context being closed while
// we're waiting for the response to come back from the browser
// for this current command (it's racey).
closeErrs = append(closeErrs, fmt.Errorf("closing a page: %w", err))
}
// Start the teardown of the page's resources (FrameSessions, NetworkManagers, etc)
// and wait for them to finish their teardown. This allows for a graceful cleanup
// of resources and ensures that all events are processed before the page is closed.
p.cancelCtx()
p.waitForFrameSessions()
if len(closeErrs) > 0 {
p.closeErr = spanRecordError(span, errors.Join(closeErrs...))
}
})
return p.closeErr
}
// Content returns the HTML content of the page.
func (p *Page) Content() (string, error) {View on GitHub (pinned to 93accf6570)
Solutions
- Wrap page.close() in a catch when the tab's lifetime is not fully under your control
- Check page.isClosed() before explicit close
- Verify the browser is still running before teardown if crashes are suspected
- Only escalate the error if subsequent operations on that browser still fail
Example fix
// before
await page.close(); // throws if tab already gone
// after
try {
await page.close();
} catch (e) {
if (!/closing a page/.test(e.message)) throw e;
console.warn('page already closed elsewhere');
} Defensive patterns
Strategy: validation
Validate before calling
if (!page.isClosed()) { await page.close(); } Try / catch
try {
await page.close();
} catch (e) {
if (!/closing a page/.test(e.message)) throw e; // tab was likely already gone
} Prevention
- Tolerate close failures when the tab's lifetime is not fully script-controlled
- Close pages before browser.close() in a strict order
When it happens
Trigger: page.close() when the tab was already closed (manually or by the site), the browser process crashed, or the DevTools websocket dropped. The close proceeds with local teardown regardless, and the error is joined into page.close()'s return.
Common situations: Site JavaScript (window.close, target=_blank flows) closing the tab first; browser crash earlier in the run; closing pages in the wrong order relative to browser.close(); reusing a page handle after its tab died.
Related errors
- internal error while removing binding from page: %w
- internal error while auto attaching to browser pages: %w
- WebSocket endpoint cannot be empty
- can't fetch the page for unknown reason
- cannot close context as none is active in browser
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/185a1a5fc9941dc1.
Report an issue: GitHub.