{"record":{"id":"7b40cfb6742ef9cd","repo":"grafana/k6","slug":"invalid-websocket-endpoint-q-host-is-missing","errorCode":null,"errorMessage":"invalid WebSocket endpoint %q: host is missing","messagePattern":"invalid WebSocket endpoint %q: host is missing","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/chromium/browser_type.go","lineNumber":170,"sourceCode":"// 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.\n//\n// The separation is important to allow for the iteration to end when k6 requires\n// the iteration to end (e.g. during a SIGTERM) and unblocks k6 to then fire off\n// the events which allows the connection to close.\nfunc (b *BrowserType) Connect(ctx, vuCtx context.Context, wsEndpoint string) (*common.Browser, error) {\n\tvuCtx, browserOpts, logger, err := b.init(vuCtx, true, k6ext.GetScenarioOpts(b.vu.Context(), b.vu))","sourceCodeStart":152,"sourceCodeEnd":188,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/browser/chromium/browser_type.go#L152-L188","documentation":"Thrown by validateWSEndpoint (called from BrowserType.ConnectOverCDP) when the endpoint has a valid ws/wss scheme but url.Hostname() is empty, i.e. there is no host between 'ws://' and the path. This catches partially-formed CDP URLs such as 'ws:///devtools/browser/<uuid>' before any network dial is attempted, guaranteeing a precise error rather than a DNS/dial failure.","triggerScenarios":"Calling chromium.connectOverCDP('ws:///devtools/browser/<uuid>') (host omitted, e.g. lost during string templating); building the URL by concatenation where the host variable is empty: `ws://${host}/devtools/...` with host unset; passing 'ws://:9222' (port without host).","commonSituations":"Endpoint assembled from environment variables where the host variable is missing or misspelled; CI pipelines that inject the WS path but not the host; scripts copied between environments where the host placeholder was never substituted.","solutions":["Include an explicit host: `ws://127.0.0.1:9222/devtools/browser/<uuid>`","If the URL is built dynamically, log it before calling connectOverCDP and fix the empty host variable","Run curl http://<host>:<port>/json/version against the browser and copy the full webSocketDebuggerUrl verbatim","Add a script-level assertion that the URL matches /^wss?://[^/]+/"],"exampleFix":"// before\nconst endpoint = `ws://${K6_BROWSER_HOST}/devtools/browser/${uuid}`; // K6_BROWSER_HOST unset -> 'ws:///devtools/...'\nconst browser = chromium.connectOverCDP(endpoint);\n\n// after\nconst host = __ENV.BROWSER_HOST || '127.0.0.1';\nconst endpoint = `ws://${host}:9222/devtools/browser/${uuid}`;\nconst browser = chromium.connectOverCDP(endpoint);","handlingStrategy":"validation","validationCode":"function buildWsEndpoint(host, port, uuid) {\n  if (!host) throw new Error('browser host is required');\n  return `ws://${host}:${port}/devtools/browser/${uuid}`;\n}\nconst endpoint = buildWsEndpoint(__ENV.BROWSER_HOST, __ENV.BROWSER_PORT || '9222', uuid);\nif (!/^wss?:\\/\\/[^\\/]+/.test(endpoint)) throw new Error(`bad endpoint: ${endpoint}`);","typeGuard":null,"tryCatchPattern":"try {\n  const browser = chromium.connectOverCDP(endpoint);\n} catch (e) {\n  if (String(e.message).includes('host is missing')) {\n    throw new Error(`BROWSER_HOST env var is empty; endpoint was: ${endpoint}`);\n  }\n  throw e;\n}","preventionTips":["Fail fast on empty host variables with an explicit error naming the missing env var","Log the final endpoint before connecting when it is assembled dynamically","Unit-test URL builders in CI so empty hosts cannot reach the k6 run"],"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"}