← Blog

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.

September 14, 20269 min readRemote Browser

# 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 install with 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 playwright and browser versions produce confusing launch failures.
  • OS dependencies. Chromium needs system libraries (libnss3, libatk, libgbm, and friends). playwright install-deps handles 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:

MethodProtocolBrowser supportTypical use
chromium.connectOverCDP(endpointURL)CDP over WebSocketChromium-based only (Chrome, Edge, Chromium)Attach to a running browser, hosted sessions, existing profiles
chromium.connect(wsEndpoint)Playwright's own protocolChromium, Firefox, WebKitConnect 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-profile

Then 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:

  1. `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.
  2. `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. connectOverCDP needs a WebSocket URL (ws:// or wss://), not the HTTP /json/version URL. Fetch the version endpoint, extract webSocketDebuggerUrl, 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.0 or 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

CriterionLocal playwright installRemote Chrome via CDP
Setup cost1–2 GB download per environmentOne endpoint URL
Cold startImage pull + browser launchSession creation
Version managementYou pin and upgradeRuntime manages
OS dependenciesYou install themHandled by host
Profile persistenceManual --user-data-dirRuntime-managed profiles
Horizontal scaleEach worker installsEach worker connects
DebuggingLocal DevToolsLive viewer or CDP
Best forSingle-machine dev, offline workCI, 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 install from your Dockerfile.
  • You replace chromium.launch() with chromium.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 connectOverCDP timeout 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

  1. Confirm your target browser is Chromium-based. If not, connectOverCDP is off the table.
  2. Get a CDP WebSocket URL from your runtime or a locally launched Chrome.
  3. Replace chromium.launch() with chromium.connectOverCDP(endpoint).
  4. Handle context reuse and cleanup explicitly.
  5. Remove playwright install from 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.