grafana/k6 · error
connecting to Chromium over CDP: %w
Error message
connecting to Chromium over CDP: %w
What it means
browser.chromium.connectOverCDP(wsEndpoint) attaches k6 to a user-managed Chromium over the DevTools Protocol: it validates the ws:// URL up front, initializes browser options from K6_BROWSER_* env vars, then dials the endpoint; any dial/handshake failure is wrapped as 'connecting to Chromium over CDP' (with a UserFriendlyError that surfaces timeout context). It exists so a browser scenario is not required - you bring your own Chromium and pass its WebSocket debugger URL.
Source
Thrown at internal/js/modules/k6/browser/browser/chromium_mapping.go:28
)
// mapChromium maps the Chromium browser type API to the JS module.
func mapChromium(vu moduleVU, bt *chromium.BrowserType) mapping {
return mapping{
"connectOverCDP": func(wsEndpoint string) *sobek.Promise {
return promise(vu, func() (any, error) {
iter := vu.State().Iteration
// Clone the BrowserType for this call so concurrent
// connectOverCDP calls in the same iteration (e.g., via
// Promise.all) don't race on its mutable state.
connBT := bt.Clone()
// Link the connection to the iteration trace and connect.
tracedCtx := vu.startConnectTrace(vu.Context(), iter)
b, err := connBT.ConnectOverCDP(tracedCtx, wsEndpoint)
if err != nil {
return nil, fmt.Errorf("connecting to Chromium over CDP: %w", err)
}
// Register for guaranteed cleanup at IterEnd / Exit.
vu.trackUserManagedBrowser(iter, b)
return mapBrowser(vu, func() (*common.Browser, error) {
return b, nil
}), nil
})
},
}
}
View on GitHub (pinned to 93accf6570)
Solutions
- Start Chromium with remote debugging: chromium --headless --remote-debugging-port=9222 --no-sandbox (as needed)
- Fetch the browser-level WebSocket URL from http://127.0.0.1:9222/json/version and pass its webSocketDebuggerUrl (ws://...) to connectOverCDP
- Verify reachability first: curl http://HOST:9222/json/version from the same host/network k6 runs on
- If the handshake times out, raise K6_BROWSER_TIMEOUT and confirm the browser process stays alive for the whole iteration
Example fix
// before: http endpoint / guessed ws URL
import browser from 'k6/browser';
export default async () => {
const b = browser.chromium.connectOverCDP('http://127.0.0.1:9222'); // wrong scheme
};
// after: resolve the real browser ws endpoint, then connect
import http from 'k6/http';
import browser from 'k6/browser';
const ver = http.get('http://127.0.0.1:9222/json/version').json();
export default async () => {
const b = browser.chromium.connectOverCDP(ver.webSocketDebuggerUrl);
const page = await b.newPage();
await page.goto('https://k6.io');
}; Defensive patterns
Strategy: validation
Validate before calling
// verify the debugger is reachable and resolve the browser ws URL before connecting
import http from 'k6/http';
const res = http.get('http://127.0.0.1:9222/json/version');
if (res.status !== 200) throw new Error(`CDP endpoint unreachable (HTTP ${res.status})`);
const ws = res.json().webSocketDebuggerUrl;
if (typeof ws !== 'string' || !ws.startsWith('ws://')) throw new Error('no valid webSocketDebuggerUrl');
// pass `ws` to browser.chromium.connectOverCDP inside the default function Type guard
function isWsEndpoint(v) {
return typeof v === 'string' && /^wss?:\/\/[^\s/]+(\/\S*)?$/.test(v);
} Try / catch
try {
const b = browser.chromium.connectOverCDP(ws);
} catch (e) {
const msg = String(e);
if (msg.includes('WebSocket endpoint')) throw new Error('fix the ws:// URL: ' + msg);
if (msg.includes('timed out') || msg.includes('timeout')) throw new Error('browser not reachable - check port and K6_BROWSER_TIMEOUT');
if (msg.includes('connecting to Chromium over CDP')) throw new Error('handshake failed - is Chromium running with --remote-debugging-port?');
throw e;
} Prevention
- Always derive wsEndpoint from /json/version's webSocketDebuggerUrl instead of hand-typing it
- Start Chromium with --remote-debugging-port and keep the process alive for the whole test
- From the k6 host, curl the /json/version endpoint first - if curl cannot reach it, k6 cannot either
- Use ws:// (or wss://) - an http:// URL is not a WebSocket endpoint
When it happens
Trigger: wsEndpoint empty, not a ws:// URL, or malformed (caught by validateWSEndpoint); passing an http:// endpoint where a ws:// webSocketDebuggerUrl is required; Chromium not started with --remote-debugging-port; wrong host/port; debugger reachable but the browser exits or the WebSocket handshake times out (K6_BROWSER_TIMEOUT).
Common situations: Connecting k6 to containerized or remote Chromium without publishing the debug port; grabbing the URL from the wrong /json endpoint entry (page target instead of browser target); Chrome security restrictions on remote debugging with non-127.0.0.1 addresses; firewalled CI runners.
Related errors
- sending a message to browser: %w
- WebSocket endpoint cannot be empty
- lost connection while closing the browser (websocket url: %s
- connection closed with websocket code: %d
- closing communication with browser: %w
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/e3f552e7fc0b9c89.
Report an issue: GitHub.