ScreenshotNeo

BlogEngineering

Puppeteer CDP WebSocket Endpoint Regex: What It Matches

See exactly what Puppeteer’s CDP WebSocket endpoint regex captures, how to extract the URL, and what it does not validate.

By the ScreenshotNeo team4 October 20265 min read

Puppeteer’s CDP_WEBSOCKET_ENDPOINT_REGEX matches a launch-output line in this exact form: DevTools listening on ws://…. Its source expression is /^DevTools listening on (ws:\/\/.*)$/, and capture group 1 contains everything after the literal prefix. It extracts a value from a specific log line; it is not a general CDP URL validator. Puppeteer source.

1. The exact regex, explained

/^DevTools listening on (ws:\/\/.*)$/
Part Meaning
^ Requires the match to start at the beginning of the input line.
DevTools listening on Matches this literal, case-sensitive text, including spaces.
(ws:\/\/.*) Capture group 1 requires ws:// and captures the remaining characters on the line.
$ Requires the match to end at the end of the input line.

The slashes are escaped because this is JavaScript regex literal syntax. The regex has no flags. Since . does not match a line terminator, Puppeteer’s line-by-line process-output handling is a natural fit.

Puppeteer waits for a line matching the expression and resolves with capture group 1. That is why the parentheses matter: the caller needs the endpoint string, not only a yes/no match.

2. What the captured endpoint looks like

Puppeteer documents a browser WebSocket endpoint in the form ws://HOST:PORT/devtools/browser/<id>. For example, a matching launch message could be:

DevTools listening on ws://127.0.0.1:9222/devtools/browser/abc123

The captured group is:

ws://127.0.0.1:9222/devtools/browser/abc123

The browser endpoint is distinct from a page target endpoint, whose path can look like /devtools/page/<target-id>. The regex does not check for either path shape; it accepts any remainder beginning with ws:// after the required prefix. Puppeteer Browser.wsEndpoint().

3. Runnable JavaScript: extract and connect

This small example extracts the endpoint from a line and connects to the browser. Install Puppeteer with npm install puppeteer. The example assumes the browser is already running and you have its launch line.

import puppeteer from 'puppeteer';

const line = 'DevTools listening on ws://127.0.0.1:9222/devtools/browser/abc123';
const match = line.match(/^DevTools listening on (ws:\/\/.*)$/);

if (!match) {
  throw new Error('No Puppeteer CDP endpoint found in the line');
}

const browserWSEndpoint = match[1];
const browser = await puppeteer.connect({ browserWSEndpoint });

console.log(await browser.version());
await browser.disconnect(); // Detach; the remote browser keeps running.

Use browser.disconnect() when you only want to detach Puppeteer. Use browser.close() when you intend to close the browser. Puppeteer browser management.

4. Where to discover the endpoint

The regex is specifically for the DevTools listening on … launch-output message. When Chrome is already running, another discovery route is /json/version, whose response includes webSocketDebuggerUrl. For example:

curl http://127.0.0.1:9222/json/version

When Chrome starts with --remote-debugging-port=0, it chooses an open port and writes the browser endpoint to stderr and to the DevToolsActivePort file in the browser profile directory. These are alternative ways to obtain the endpoint; they are not strings this named regex is designed to match. Chrome DevTools Protocol documentation.

5. What this regex does not validate

  • Host: no hostname or IP address checks.
  • Port: no requirement that a numeric port is present or in range.
  • Path: it does not require /devtools/browser/; a page target path or arbitrary suffix can also be captured.
  • Identifier: no validation of the browser ID shape.
  • Reachability: a string can match even if the endpoint is offline or inaccessible.
  • Secure WebSocket: it expects ws://; a line containing wss:// does not satisfy the expression.

If your application requires a browser endpoint specifically, validate the parsed URL and expected path separately, then attempt the connection and handle connection errors.

function parseBrowserEndpoint(value) {
  const url = new URL(value);
  if (url.protocol !== 'ws:' || !url.port || !url.pathname.startsWith('/devtools/browser/')) {
    throw new Error('Expected a ws:// browser endpoint');
  }
  return url;
}

This is an example of additional application-level checking, not behavior built into Puppeteer’s regex.

6. CDP versus WebDriver BiDi output

Puppeteer defines a separate regex for WebDriver BiDi launch output. Its literal prefix is WebDriver BiDi listening on , rather than DevTools listening on . The CDP expression therefore should not be described as matching every browser WebSocket endpoint. Use the expression corresponding to the protocol’s launch message. Puppeteer source.

7. Common errors and fixes

Symptom Likely cause Fix
No regex match The prefix differs in capitalization, spacing, or wording, or the line contains wss://. Inspect the raw line and confirm it begins exactly with DevTools listening on ws://.
Match succeeds but connection fails The regex only extracts text; the browser may be stopped, the port may be inaccessible, or the endpoint may be stale. Check that Chrome is running and reachable, rediscover the current endpoint, and handle connection rejection.
Captured URL is not a browser endpoint The expression does not validate the path; a page-target path or malformed suffix can still match. Parse the URL and validate the expected /devtools/browser/ path before connecting.
Multiline string does not match as expected The anchors apply to the entire input and dot does not span line terminators. Pass one output line at a time, as Puppeteer does, or deliberately extract the relevant line first.
Wrong protocol message The output is a WebDriver BiDi message rather than the CDP DevTools message. Use Puppeteer’s separate BiDi endpoint handling for the BiDi prefix.

8. Performance, reliability, and cost

The expression is a simple anchored match over one line, so its work is proportional to the line length. In practice, endpoint discovery and browser startup or network reachability are the consequential reliability concerns. Treat captured text as a candidate endpoint: validate it if your program has stricter requirements, and still handle failures from puppeteer.connect().

This regex itself has no service cost. Any cost of running Chrome depends on where and how you run the browser; no vendor or price is implied by the expression. If your task is to capture website screenshots rather than control a browser over CDP, a screenshot API can avoid managing browser startup and endpoint discovery.

9. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. A single GET request returns an image or PDF, without requiring you to launch Chrome and extract its CDP endpoint. Its cookie and consent handling accepts banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot; those steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status. Its MCP server exposes screenshot, page-info, and PDF capture tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Create a free ScreenshotNeo account for 1,000 screenshots a month, with no card required.

10. FAQ

Does this regex return the full match or just the URL?

The regex match contains the full log line; capture group 1 contains the URL portion beginning with ws://.

Does it accept an endpoint found in /json/version?

The value from webSocketDebuggerUrl is an endpoint, but the regex only matches it if it appears after the expected launch-message prefix in a line.

Has this expression been unchanged across all Puppeteer versions?

The cited source establishes the expression in the inspected source, but does not establish its history across releases.