{"record":{"id":"9d21f219394f0bff","repo":"grafana/k6","slug":"invalid-websocket-endpoint-q-scheme-must-be-ws-o","errorCode":null,"errorMessage":"invalid WebSocket endpoint %q: scheme must be ws or wss, got %q","messagePattern":"invalid WebSocket endpoint %q: scheme must be ws or wss, got %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/chromium/browser_type.go","lineNumber":164,"sourceCode":"\n\treturn bp, nil\n}\n\n// validateWSEndpoint returns an error if wsEndpoint is not a usable CDP\n// WebSocket URL, catching common mistakes (an empty value, a non-ws scheme,\n// a missing host, etc.) before we attempt to connect.\nfunc validateWSEndpoint(wsEndpoint string) error {\n\tif strings.TrimSpace(wsEndpoint) == \"\" {\n\t\treturn errors.New(\"WebSocket endpoint cannot be empty\")\n\t}\n\n\tu, err := url.Parse(wsEndpoint)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"invalid WebSocket endpoint %q: %w\", wsEndpoint, err)\n\t}\n\n\tif u.Scheme != \"ws\" && u.Scheme != \"wss\" {\n\t\treturn fmt.Errorf(\n\t\t\t\"invalid WebSocket endpoint %q: scheme must be ws or wss, got %q\", wsEndpoint, u.Scheme,\n\t\t)\n\t}\n\n\tif u.Hostname() == \"\" {\n\t\treturn fmt.Errorf(\"invalid WebSocket endpoint %q: host is missing\", wsEndpoint)\n\t}\n\n\treturn nil\n}\n\n// Connect attaches k6 browser to an existing browser instance.\n//\n// vuCtx is the context coming from the VU itself. The k6 vu/iteration controls\n// its lifecycle.\n//\n// context.background() is used when connecting to an instance of chromium. The\n// connection lifecycle should be handled by the k6 event system.","sourceCodeStart":146,"sourceCodeEnd":182,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/browser/chromium/browser_type.go#L146-L182","documentation":"Thrown by validateWSEndpoint (called from BrowserType.ConnectOverCDP) when the WebSocket endpoint string parses as a URL but its scheme is neither 'ws' nor 'wss'. k6 requires a raw CDP WebSocket URL (the webSocketDebuggerUrl returned by Chrome's /json/version endpoint), so an http:// DevTools HTTP URL or a bare host:port is rejected up front with a clear message instead of a confusing lower-level handshake failure.","triggerScenarios":"Calling chromium.connectOverCDP('http://localhost:9222') (the HTTP DevTools URL instead of the WS one); passing 'localhost:9222' or '127.0.0.1:9222' (url.Parse yields an empty scheme); passing an https:// endpoint; passing a URL with a typo like 'wss://' misspelled.","commonSituations":"Developer copies the http://localhost:9222 URL from Chrome's console output or from curl http://localhost:9222/json/version (they should copy the webSocketDebuggerUrl field instead); a containerized setup where only the HTTP port was exposed; scripts migrated from Puppeteer/Playwright examples that use http endpoints with browserURL.","solutions":["Use the WebSocket URL from Chrome: run curl http://<host>:9222/json/version and pass the value of its webSocketDebuggerUrl field (e.g. ws://127.0.0.1:9222/devtools/browser/<uuid>) to connectOverCDP","Start Chrome with --remote-debugging-port=9222 (or --remote-debugging-pipe is not supported here) so it exposes a ws:// DevTools endpoint","If your endpoint really is ws but on another scheme alias, rewrite it explicitly as ws://host:port/path before calling connectOverCDP","Add a preflight check in the script that parses the endpoint and asserts the scheme starts with 'ws'"],"exampleFix":"// before\nconst browser = chromium.connectOverCDP('http://localhost:9222');\n\n// after\nconst browser = chromium.connectOverCDP('ws://127.0.0.1:9222/devtools/browser/5f5a1e30-8b4f-4c2a-9d3e-1a2b3c4d5e6f');","handlingStrategy":"validation","validationCode":"function assertWSEndpoint(urlStr) {\n  try {\n    const u = new URL(urlStr);\n    if (u.protocol !== 'ws:' && u.protocol !== 'wss:') {\n      throw new Error(`scheme must be ws or wss, got ${u.protocol}`);\n    }\n    if (!u.hostname) throw new Error('host is missing');\n    return true;\n  } catch (e) {\n    throw new Error(`invalid WebSocket endpoint ${urlStr}: ${e.message}`);\n  }\n}\nassertWSEndpoint(CDP_ENDPOINT);","typeGuard":null,"tryCatchPattern":"try {\n  const browser = chromium.connectOverCDP(CDP_ENDPOINT);\n} catch (e) {\n  if (String(e.message).includes('scheme must be ws or wss')) {\n    console.error('Pass the webSocketDebuggerUrl from http://host:9222/json/version, not the http URL');\n  }\n  throw e;\n}","preventionTips":["Always fetch the endpoint from /json/version's webSocketDebuggerUrl instead of hand-writing it","Keep the endpoint in a single environment variable so it is validated in one place","Prefer URL construction over string concatenation so the host cannot silently vanish"],"tags":["websocket","cdp","connectovercdp","config","endpoint-validation"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}