{"record":{"id":"d97ac8cb75d9b933","repo":"grafana/k6","slug":"connecting-to-browser-devtools-url-w","errorCode":null,"errorMessage":"connecting to browser DevTools URL: %w","messagePattern":"connecting to browser DevTools URL: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/common/browser.go","lineNumber":160,"sourceCode":"func (b *Browser) connect() error {\n\tb.logger.Debugf(\"Browser:connect\", \"wsURL:%q\", b.browserProc.WsURL())\n\n\t// connectionOnAttachedToTarget hooks into the connection to listen\n\t// for target attachment events. this way, browser can manage the\n\t// decision of target attachments. so that we can stop connection\n\t// from doing unnecessary work.\n\t//\n\t// We need the connection to shutdown when browser.Close is called.\n\t// This is why we're using the internal context.\n\tvar err error\n\tb.conn, err = NewConnection(\n\t\tb.browserCtx,\n\t\tb.browserProc.WsURL(),\n\t\tb.logger,\n\t\tb.connectionOnAttachedToTarget,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"connecting to browser DevTools URL: %w\", err)\n\t}\n\n\t// We don't need to lock this because `connect()` is called only in NewBrowser\n\tb.defaultContext, err = NewBrowserContext(b.vuCtx, b, \"\", DefaultBrowserContextOptions(), b.logger)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"browser connect: %w\", err)\n\t}\n\tb.runOnClose = append(b.runOnClose, b.defaultContext.cleanup)\n\n\treturn b.initEvents()\n}\n\nfunc (b *Browser) disposeContext(id cdp.BrowserContextID) error {\n\tb.logger.Debugf(\"Browser:disposeContext\", \"bctxid:%v\", id)\n\n\taction := target.DisposeBrowserContext(id)\n\tif err := action.Do(cdp.WithExecutor(b.vuCtx, b.conn)); err != nil {\n\t\treturn fmt.Errorf(\"disposing browser context ID %s: %w\", id, err)","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/browser/common/browser.go#L142-L178","documentation":"Returned by Browser.connect() (called from common.NewBrowser during both local launch and remote connect) when NewConnection fails to establish the CDP WebSocket connection to the browser process's DevTools URL. This sits below the 'connecting to browser'/'launching browser' wrappers: the process object exists, but the actual protocol channel to it could not be opened.","triggerScenarios":"Chrome process started but crashed before its DevTools socket accepted the connection; the wsURL reported by the process is stale or wrong (remote browser restarted and the old endpoint is served no longer); network/proxy blocks the second WebSocket; TLS mismatch on wss endpoints; handshake exceeds the browser timeout.","commonSituations":"Chrome dying silently during startup (OOM, sandbox denial) after the process handle exists; remote browser pools recycling instances between wsURL discovery and connect; corporate proxies terminating or buffering WebSockets; Docker port-mapping changes invalidating a previously returned wsURL.","solutions":["Check whether the browser process is actually alive at connect time (docker logs / process list); a dead process is the most common cause","Increase K6_BROWSER_TIMEOUT to cover slow handshakes","For remote setups, re-discover the wsURL (GET /json/version) immediately before connecting instead of caching it","Ensure proxies/LBs between k6 and the browser support long-lived WebSocket connections","Enable K6_BROWSER_DEBUG=true to log the exact wsURL and dial error"],"exampleFix":"# before\nK6_BROWSER_TIMEOUT=10s k6 run test.js\n\n# after\nK6_BROWSER_DEBUG=true K6_BROWSER_TIMEOUT=60s k6 run test.js","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const browser = chromium.launch(); // or connectOverCDP(wsUrl)\n} catch (e) {\n  const m = String(e.message);\n  if (m.includes('connecting to browser DevTools URL')) {\n    // process existed but the CDP WS dial failed: check process liveness / wsURL freshness\n    console.error('CDP dial failed; verify the browser process is alive and the wsURL is current:', m);\n  }\n  throw e;\n}","preventionTips":["Fetch /json/version right before connecting instead of caching wsURLs","Keep remote browser instances pinned (no recycling) for the test duration","Raise K6_BROWSER_TIMEOUT and enable K6_BROWSER_DEBUG to capture the dial error"],"tags":["websocket","cdp","network","connect","launch"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}