grafana/k6 · error · websocket.CloseError
closing communication with browser: %w
Error message
closing communication with browser: %w
What it means
In Connection.send, the closeCh branch fires when a websocket close frame arrives from the browser while k6 is sending a message. k6 then runs c.close(code) and returns a *websocket.CloseError wrapped with this text, so callers see both the wrapper and the machine-readable close code.
Source
Thrown at internal/js/modules/k6/browser/common/connection.go:484
}
err := c.send(c.ctx, msg, nil, nil)
if err != nil {
c.logger.Errorf("Connection:stopWaitingForDebugger", "sid:%v wsURL:%q, err:%v", sid, c.wsURL, err)
}
}
func (c *Connection) send(
ctx context.Context, msg *cdproto.Message, recvCh chan *cdproto.Message, res any,
) error {
select {
case c.sendCh <- msg:
case err := <-c.errorCh:
c.logger.Debugf("Connection:send:<-c.errorCh", "wsURL:%q sid:%v, err:%v", c.wsURL, msg.SessionID, err)
return fmt.Errorf("sending a message to browser: %w", err)
case code := <-c.closeCh:
c.logger.Debugf("Connection:send:<-c.closeCh", "wsURL:%q sid:%v, websocket code:%v", c.wsURL, msg.SessionID, code)
_ = c.close(code)
return fmt.Errorf("closing communication with browser: %w", &websocket.CloseError{Code: code})
case <-ctx.Done():
c.logger.Debugf("Connection:send:<-ctx.Done", "wsURL:%q sid:%v err:%v", c.wsURL, msg.SessionID, ContextErr(ctx))
return nil
case <-c.done:
c.logger.Debugf("Connection:send:<-c.done", "wsURL:%q sid:%v", c.wsURL, msg.SessionID)
return nil
}
// Block waiting for response.
if recvCh == nil {
return nil
}
tid := c.findTargetIDForLog(msg.SessionID)
select {
case msg := <-recvCh:
var sid target.SessionID
tid = ""
if msg != nil {View on GitHub (pinned to 93accf6570)
Solutions
- Detect the shape: error messages containing 'closing communication with browser' plus a websocket.CloseError code — treat as 'target gone', not as a transient fault.
- Re-locate pages/targets after any action that can navigate away or close the page (query browser.contexts().pages() again).
- Await navigation/close-triggering actions so subsequent calls are not racing them.
- In scripts, treat this specific error as non-retryable for the current page: abandon the page and continue the scenario.
Example fix
// before
await page.click('#logout'); // closes the page
await page.screenshot(); // races target destruction
// after
await page.click('#logout');
try {
await page.screenshot();
} catch (e) {
if (!String(e).includes('closing communication with browser')) throw e;
// target legitimately closed; skip screenshot
} Defensive patterns
Strategy: try-catch
Try / catch
try {
await page.click('#self-destruct');
} catch (e) {
if (/closing communication with browser/.test(String(e))) {
// target/page was closed by the app — expected; verify via browser.contexts().pages()
const gone = (await browser.contexts()[0].pages()).length === 0;
if (!gone) throw e;
} else {
throw e;
}
} Prevention
- Classify this error as 'target gone' and never blind-retry the same page object.
- Re-enumerate pages after actions that can close or navigate targets.
- Await close-triggering actions before issuing follow-up commands on the same target.
When it happens
Trigger: A CDP send racing a browser-initiated close: the page or target being destroyed (e.g. window.close, navigation replacing the target), browser shutting down, or remote browser restarting, all while a command for that session is in flight.
Common situations: Clicking a link/button whose handler closes the page, then issuing another action on the stale page object; auto-closing popups racing assertions; teardown concurrent with slow operations.
Related errors
- connecting to Chromium over CDP: %w
- connection closed with websocket code: %d
- sending a message to browser: %w
- WebSocket endpoint cannot be empty
- invalid WebSocket endpoint %q: scheme must be ws or wss, got
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/729009407c95b77d.
Report an issue: GitHub.