BLOG
Playwright Connect To Remote Chrome Download
Learn how to playwright connect to remote chrome download flows, why local browser installs break CI, and how CDP connects Playwright to hosted Chromium.
# Playwright Connect To Remote Chrome Download
If you searched for playwright connect to remote chrome download, you probably hit one of two walls: either npx playwright install is pulling a large Chromium bundle onto every machine, or you have a Chrome instance running somewhere else and you want Playwright to drive it. This guide answers both. It explains what Playwright actually downloads, why the download step is often the wrong thing to optimize, and how to connect Playwright to a remote Chrome over the Chrome DevTools Protocol (CDP) instead.
The short version: Playwright's chromium download is a local install of a browser binary. A remote Chrome connection is a network session to a browser that already exists. If your goal is to run automation without managing browser binaries, you want the second path — chromium.connectOverCDP() against a hosted endpoint.
What "Playwright Connect To Remote Chrome Download" Actually Means
The phrase mixes two different operations, and separating them saves a lot of debugging time.
Download refers to Playwright's browser installation step. When you run npm i playwright or npm i @playwright/test, you get the library. You do not get a browser. Playwright fetches browser builds separately via npx playwright install, which places Chromium, Firefox, and WebKit binaries in a cache directory (~/.cache/ms-playwright on Linux, ~/Library/Caches/ms-playwright on macOS, %USERPROFILE%\AppData\Local\ms-playwright on Windows).
Connect refers to browserType.connectOverCDP(), which attaches Playwright to an already-running Chromium-based browser over a WebSocket CDP endpoint. No download happens on the client. The browser lives wherever the endpoint points — a container, a VM, or a hosted runtime.
So the real question is: do you want to install a browser, or do you want to talk to one? For CI pipelines, ephemeral containers, and AI agent workloads, connecting is usually the better default.
Why the Local Download Step Becomes a Problem
Playwright's install step is well-engineered. It is also a liability at scale for reasons that have nothing to do with Playwright's quality.
- Image size. A full
playwright installwith dependencies adds roughly 1–2 GB to a container image depending on which browsers you include. Every cold start pays for it. - Version drift. The browser build Playwright expects is pinned to the library version. Mismatched
playwrightand browser versions produce confusing launch failures. - OS dependencies. Chromium needs system libraries (
libnss3,libatk,libgbm, and friends).playwright install-depshandles this, but only if you have root and the right package manager. - Cache invalidation. Layer caching helps until it doesn't. A single version bump re-downloads everything.
- No shared state. Each container gets a fresh browser with no profile, no cookies, no session continuity.
None of these are fatal. They are cumulative friction. If you run one test suite on one machine, install locally and move on. If you run many short-lived sessions across many workers, the download step is overhead you can delete entirely.
How Playwright Connects to a Remote Chrome
Playwright exposes two connection methods on BrowserType:
| Method | Protocol | Browser support | Typical use |
|---|---|---|---|
chromium.connectOverCDP(endpointURL) | CDP over WebSocket | Chromium-based only (Chrome, Edge, Chromium) | Attach to a running browser, hosted sessions, existing profiles |
chromium.connect(wsEndpoint) | Playwright's own protocol | Chromium, Firefox, WebKit | Connect to a Playwright server started with --remote-debugging style flags |
connectOverCDP is the one people mean when they say "connect Playwright to remote Chrome." It speaks raw CDP, so it works against anything exposing a DevTools WebSocket — a local Chrome started with --remote-debugging-port=9222, a Docker container, or a hosted browser runtime.
The trade-off: connectOverCDP only works with Chromium-based browsers. If you need Firefox or WebKit, you need connect() against a Playwright server, which is a different architecture. For most remote-Chrome use cases, CDP is the right call.
Starting Chrome with a CDP Endpoint
Before connecting, something has to expose the endpoint. Locally, that means launching Chrome with remote debugging enabled:
# Local Chrome with CDP on port 9222
google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-profileThen http://localhost:9222/json/version returns a webSocketDebuggerUrl. That URL is what Playwright needs.
In production you rarely do this by hand. You either run Chrome in a container with the debugging port exposed, or you use a hosted runtime that hands you a CDP URL on session creation. The hosted path removes the container, the port mapping, the OS dependencies, and the browser download in one move.
TypeScript Example: Connect Over CDP
Here is a minimal, production-shaped example. It connects to a remote CDP endpoint, reuses the existing browser context when one exists, and cleans up the session.
import { chromium, Browser, BrowserContext, Page } from 'playwright';
const CDP_ENDPOINT = process.env.CDP_ENDPOINT!; // e.g. wss://.../cdp
async function run(): Promise<void> {
let browser: Browser | null = null;
try {
browser = await chromium.connectOverCDP(CDP_ENDPOINT, {
timeout: 30_000,
});
// connectOverCDP attaches to contexts that already exist.
// Reuse the first one instead of creating a new context.
const contexts: BrowserContext[] = browser.contexts();
const context: BrowserContext =
contexts.length > 0 ? contexts[0] : await browser.newContext();
const page: Page = context.pages()[0] ?? (await context.newPage());
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
const title = await page.title();
console.log('Page title:', title);
// Do NOT call browser.close() on a shared remote session
// unless you own its lifecycle. Disconnect instead.
await browser.close();
} catch (err) {
console.error('CDP connection failed:', err);
throw err;
}
}
run();Two details matter here and cause most "not working" reports:
- `connectOverCDP` does not create a fresh context by default. It exposes whatever contexts the remote browser already has. If you call
browser.newContext()blindly, you may end up with an orphaned context and a leaked session. - `browser.close()` on a remote session can terminate the whole browser, not just your connection. If the session is shared or managed by a runtime, check the runtime's disconnect semantics before closing.
For deeper protocol details, the Chrome DevTools Protocol documentation is the authoritative reference, and Playwright's own CDP guidance is worth reading alongside it.
Common Failure Modes
Most connection failures fall into a small set of causes.
- Wrong endpoint format.
connectOverCDPneeds a WebSocket URL (ws://orwss://), not the HTTP/json/versionURL. Fetch the version endpoint, extractwebSocketDebuggerUrl, then connect. - Chrome not started with `--remote-debugging-port`. Without the flag, there is no CDP server to connect to.
- Chrome bound to localhost only. By default, remote debugging binds to
127.0.0.1. From another container or host, you need--remote-debugging-address=0.0.0.0or a tunnel. - Version mismatch. A very new Chrome against an old Playwright (or vice versa) can break CDP method expectations. Pin both, or use a runtime that manages the pairing.
- Session already closed. Hosted sessions have idle timeouts. If your worker waits too long between steps, the endpoint goes away.
- TLS/proxy interception. Corporate proxies that terminate TLS can break
wss://upgrades. Test the endpoint with a raw WebSocket client before blaming Playwright.
If you are debugging a live failure, the Playwright connect to remote Chrome not working walkthrough covers these in more depth.
Local Install vs Remote Connect: A Decision Table
| Criterion | Local playwright install | Remote Chrome via CDP |
|---|---|---|
| Setup cost | 1–2 GB download per environment | One endpoint URL |
| Cold start | Image pull + browser launch | Session creation |
| Version management | You pin and upgrade | Runtime manages |
| OS dependencies | You install them | Handled by host |
| Profile persistence | Manual --user-data-dir | Runtime-managed profiles |
| Horizontal scale | Each worker installs | Each worker connects |
| Debugging | Local DevTools | Live viewer or CDP |
| Best for | Single-machine dev, offline work | CI, agents, multi-tenant automation |
The table is not a verdict. Local install is correct when you need offline determinism or full control over the binary. Remote connect is correct when the browser is infrastructure, not an artifact.
Where a Hosted Runtime Fits
A hosted Chromium runtime gives you a CDP endpoint per session without you running Chrome, managing ports, or shipping browser binaries. Remote Browser provides hosted Chromium sessions with CDP access, Playwright/Puppeteer/Selenium compatibility, persistent profiles, configurable browser settings, session isolation, and a live viewer for debugging.
The practical difference for a Playwright user:
- You delete
npx playwright installfrom your Dockerfile. - You replace
chromium.launch()withchromium.connectOverCDP(endpoint). - You get session isolation per task instead of shared browser state.
- You can watch a session live when something breaks, instead of reading a trace after the fact.
If you are moving from local Chrome to hosted sessions, the remote browser for AI agents guide covers the runtime model, and remote browser online walks through running Chromium without a local install. For usage-based cost questions, see /pricing — pricing changes, so check current details there rather than relying on secondhand numbers.
Playwright, Puppeteer, and the Same CDP Endpoint
One useful property of CDP: the endpoint is not Playwright-specific. The same ws:// URL works with Puppeteer's puppeteer.connect({ browserWSEndpoint }), with raw CDP clients, and with Selenium 4's CDP support. If you have a mixed stack, you do not need a separate browser per tool.
This also means you can migrate incrementally. Keep Puppeteer for one service, move another to Playwright, point both at the same class of endpoint. The connection contract is the protocol, not the library.
Production Checklist
Before you ship a remote-Chrome Playwright setup, verify these:
- Endpoint retrieval is dynamic. Do not hardcode a CDP URL. Fetch it per session from your runtime's API.
- Timeouts are explicit. Set
connectOverCDPtimeout and per-action timeouts. Defaults are generous and hide hangs. - Context reuse is intentional. Decide whether you attach to an existing context or create one, and handle both cases.
- Cleanup is correct. Know whether
browser.close()ends your session or the whole browser. Prefer an explicit disconnect or session-terminate call. - Profiles are scoped. Persistent profiles are powerful and dangerous. Scope them per tenant or per task, not globally.
- Observability exists. You want a live viewer or session recording, not just logs. Debugging a remote browser without visual access is slow.
- Secrets stay server-side. Never pass credentials into page context. Inject them at the network layer or via the runtime.
- Rate and concurrency limits are known. Check your runtime's limits rather than assuming there is no cap on parallel sessions.
When to Keep the Download
Be honest about the cases where local install still wins:
- Offline or air-gapped environments. No endpoint, no connection.
- Exact binary reproducibility. You need the precise Chromium build Playwright pins.
- Single-developer local testing. The download is a one-time cost.
- Non-Chromium browsers. Firefox and WebKit are not reachable via
connectOverCDP.
For everything else — CI matrices, agent fleets, multi-tenant automation, short-lived tasks — the download is a step you can remove. Connecting to a remote Chrome over CDP gives you the same Playwright API with less environment to maintain.
Next Steps
- Confirm your target browser is Chromium-based. If not,
connectOverCDPis off the table. - Get a CDP WebSocket URL from your runtime or a locally launched Chrome.
- Replace
chromium.launch()withchromium.connectOverCDP(endpoint). - Handle context reuse and cleanup explicitly.
- Remove
playwright installfrom your build once the connection path is stable.
The documentation covers session creation, CDP endpoints, and profile handling in detail. If you want to see a live session before committing, the remote control browser overview shows how the viewer and CDP access fit together.