BLOG
Playwright Supported Browsers: Chromium, Firefox, WebKit
Playwright supported browsers explained: Chromium, Firefox, and WebKit, how browser installs work, and how CDP connections change what you can run.
# Playwright Supported Browsers: Chromium, Firefox, WebKit
Playwright supported browsers are Chromium, Firefox, and WebKit. Each ships as a patched build that Playwright maintains, not the stock browser you have installed on your machine. Chromium covers Chrome and Edge, Firefox covers Mozilla's engine, and WebKit covers Safari's rendering engine. That is the whole list for browserType.launch(). The moment you switch to connectOverCDP(), the list collapses to Chromium only, and that distinction is where most production confusion starts.
This guide covers what each browser actually is, how installation works, and what changes when you connect Playwright to a remote browser instead of launching one locally.
What "supported" means in Playwright
Playwright does not drive your system Chrome. It downloads its own browser binaries, built from upstream source with patches that expose automation hooks. Those patches are why page.route() can intercept requests reliably and why browserContext isolation behaves consistently across engines.
Three consequences follow from that design:
- Version pinning is per Playwright release. Playwright 1.x maps to specific Chromium, Firefox, and WebKit revisions. Upgrading Playwright can change the browser underneath you.
- You cannot point Playwright at an arbitrary browser build.
executablePathexists, but it is a sharp tool. Mismatched builds produce subtle failures rather than clean errors. - WebKit is not Safari. It is the WebKit engine with Playwright's patches. Good for rendering and layout coverage, not a substitute for testing real Safari on real hardware.
If you need the full matrix, the Playwright documentation is the authoritative source on which revisions ship with which release.
The three engines and what they cover
| Engine | browserName | Covers | Notes |
|---|---|---|---|
| Chromium | chromium | Chrome, Edge, Brave, Opera | Only engine that supports CDP |
| Firefox | firefox | Firefox, Firefox ESR | Uses Playwright's patched build |
| WebKit | webkit | Safari (engine-level) | Not a Safari binary; no CDP |
| Chrome (channel) | chromium + channel: 'chrome' | Real Google Chrome | Uses your installed Chrome, not the bundled build |
| Edge (channel) | chromium + channel: 'msedge' | Real Microsoft Edge | Same channel mechanism |
The channel option is worth understanding. channel: 'chrome' tells Playwright to launch the Chrome already installed on the system rather than its bundled Chromium. You get real Chrome branding and codec support, but you lose the guarantee that the build matches what Playwright tested against. In CI, that is usually a bad trade. On a developer laptop where you need to reproduce a user-reported bug, it is often the right one.
Installing browsers: the default path
Playwright installs browsers through its CLI, not through npm. Installing the package alone gives you the API but no binaries.
npm install -D @playwright/test
npx playwright installThat downloads all three engines. In practice you rarely want all three. CI images get large fast, and most test suites only exercise one or two engines.
# Single engine
npx playwright install chromium
# Multiple engines
npx playwright install chromium firefox
# With system dependencies (Linux CI)
npx playwright install --with-deps chromium--with-deps matters on Linux. Playwright's browsers need shared libraries — fonts, codecs, windowing stubs — that minimal container images do not include. Without them you get launch failures that look like Playwright bugs but are missing libnss3 or libatk.
Where the binaries live
Playwright caches browsers in a platform-specific directory:
- Linux:
~/.cache/ms-playwright - macOS:
~/Library/Caches/ms-playwright - Windows:
%USERPROFILE%\AppData\Local\ms-playwright
You can override this with PLAYWRIGHT_BROWSERS_PATH. That is the standard trick for baking browsers into a Docker layer so CI does not re-download them on every run.
Installing manually
playwright install browsers manually is a real search because the automatic path fails in restricted environments. The manual route is:
- Set
PLAYWRIGHT_BROWSERS_PATHto a writable directory. - Run
npx playwright installon a machine with network access. - Copy the resulting directory into your image or artifact store.
- Set the same environment variable at runtime.
There is no supported way to hand-download a browser zip and drop it in. Playwright validates build revisions, and a mismatched binary fails at launch. If your environment cannot reach Playwright's CDN, the practical answer is usually to move the browser off that environment entirely — which is what the remote-browser approach below does.
What changes with connectOverCDP
browserType.connectOverCDP() attaches Playwright to a browser that is already running and already exposing a DevTools Protocol endpoint. It does not launch anything.
import { chromium, Browser, BrowserContext, Page } from 'playwright';
async function connectToRemote(): Promise<void> {
const browser: Browser = await chromium.connectOverCDP(
'wss://your-session-host/cdp'
);
// A CDP connection returns existing contexts, not a blank one.
const contexts: BrowserContext[] = browser.contexts();
const context: BrowserContext = contexts[0] ?? (await browser.newContext());
const page: Page = context.pages()[0] ?? (await context.newPage());
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
console.log(await page.title());
// Disconnect without killing the remote browser.
await browser.close();
}
connectToRemote().catch(console.error);Three things about this code matter more than they look.
`browser.contexts()` is not empty. A launched browser starts with zero contexts. A connected browser starts with whatever the remote process already has. Code that assumes contexts()[0] is fresh will leak state between runs.
`browser.close()` disconnects, it does not terminate. With connectOverCDP, closing the connection leaves the remote browser running. If you want it gone, you need to close it through whatever API owns its lifecycle.
CDP is Chromium-only. firefox.connectOverCDP() and webkit.connectOverCDP() are not supported. If your test matrix includes Firefox or WebKit, those engines must be launched locally or through a provider that launches them for you.
Why playwright connect to remote chrome not working is such a common search
The failure modes are consistent and mostly diagnosable.
The endpoint is HTTP, not WebSocket. connectOverCDP wants a WebSocket URL (ws:// or wss://). Passing http://localhost:9222 fails. You need the /json/version response's webSocketDebuggerUrl, or a provider that hands you a wss:// URL directly.
Chrome is not listening on a debuggable port. A normal Chrome launch does not expose CDP. You need --remote-debugging-port=9222, and since Chrome 136 that flag is ignored for the default profile directory as a security measure. You must also pass --user-data-dir pointing somewhere non-default.
The port is bound to localhost only. In containers, --remote-debugging-address=0.0.0.0 is often required before anything outside the container can reach it.
A proxy or tunnel strips the upgrade. WebSocket connections need the Upgrade header to survive. Some corporate proxies and load balancers drop it silently, producing a connection that opens and then immediately closes.
Version skew. Playwright's CDP client targets a range of Chromium versions. Connecting to a much newer or much older Chrome can produce protocol errors that look like Playwright bugs.
The playwright connect to remote chrome download query is a related confusion: people expect a download step. There is not one. connectOverCDP downloads nothing. The browser already exists somewhere; you are attaching to it.
Local launch vs. remote connection
| Dimension | launch() | connectOverCDP() |
|---|---|---|
| Engines | Chromium, Firefox, WebKit | Chromium only |
| Browser binary | Downloaded by Playwright | Already running remotely |
| Startup cost | Seconds per launch | Connection handshake only |
| State on connect | Clean | Whatever the remote session holds |
| Scaling | Bound by host CPU/RAM | Bound by provider capacity |
| Debugging | Local, direct | Requires a live viewer or CDP tooling |
| Profile persistence | Manual via launchPersistentContext | Managed by the remote runtime |
The trade-off is not "remote is better." It is that local launch gives you engine coverage and total control, while remote connection gives you isolation, persistence, and the ability to run many sessions without provisioning machines. Most production stacks end up using both: local launches for the Firefox and WebKit legs of a test matrix, remote Chromium for agent workloads and anything that needs to survive a process restart.
Where remote Chromium fits
If your workload is Chromium-only — which covers most AI agent and browser-use scenarios — running the browser remotely removes an entire class of operational problems. You stop installing browsers in CI, stop fighting --with-deps, and stop worrying about whether a container has enough memory for four parallel contexts.
Remote Browser provides hosted Chromium sessions with CDP access, so the connectOverCDP code above works against a wss:// endpoint without any local browser install. Sessions support persistent profiles, configurable browser settings, and a live viewer for debugging. Playwright, Puppeteer, and Selenium clients all connect to the same session.
For the broader architecture, see Remote Browser for AI agents. For the connection mechanics in more depth, Remote Web Browser covers the runtime model, and Remote Control Browser covers driving sessions from code and agents.
The Chrome extension question
Searches for playwright chrome extension and playwright mcp chrome extension usually come from people trying to control their existing logged-in browser. Playwright does not ship a Chrome extension, and there is no supported extension-based control path.
What people actually want is one of two things:
- Attach to a running Chrome — that is
connectOverCDPagainst a Chrome started with--remote-debugging-port, subject to the profile-directory restriction noted above. - Give an MCP client browser access — that is an MCP server that exposes browser tools, which then connects to a browser over CDP. The extension is not the mechanism; CDP is.
If you are trying to reuse a logged-in session, the more reliable pattern is a persistent profile on a remote session rather than attaching to your desktop browser. It survives restarts, does not depend on your laptop being awake, and does not require weakening Chrome's default security posture.
Choosing a configuration
A few rules that hold up in practice:
- Test matrix with all three engines → install browsers locally, run
launch(). Accept the CI image size. - Chromium-only agent or scraping workload → connect to a remote session. Skip the install entirely.
- Need real Chrome behavior (codecs, DRM, extensions) →
channel: 'chrome'locally, or a remote session configured with matching settings. - Need sessions to survive process restarts → persistent profiles on a remote runtime. Local
launchPersistentContextworks but ties the profile to one machine. - Debugging a flaky remote connection → check the URL scheme first, then the port binding, then the proxy. In that order.
Current session limits and pricing are on the pricing page. Setup details for connecting a client are in the documentation.
Summary
Playwright supports three engines: Chromium, Firefox, and WebKit, each as a Playwright-maintained build installed via npx playwright install. Chromium additionally supports channel variants for real Chrome and Edge. connectOverCDP narrows that to Chromium only, but in exchange you get to attach to a browser that already exists — which is what makes remote browser sessions practical for agent workloads and long-running automation. Know which side of that line your workload sits on, and most of the confusing error messages stop being confusing.