BLOG
Download Playwright Browsers Manually: When and How
Learn how to download Playwright browsers manually, when it makes sense, and why hosted Chromium is often the better path for AI agents.
# Download Playwright Browsers Manually: When and How
If you need to download Playwright browsers manually, you are usually solving one of three problems: your CI environment blocks the default installer, you need a pinned browser build for reproducibility, or you want to run automation without re-downloading large browser archives on every machine. Playwright's npx playwright install command handles the common case, but it hides a lot of machinery — download URLs, cache directories, platform-specific builds, and host validation. Understanding that machinery lets you control it.
This guide covers the manual download path end to end: where Playwright stores browsers, how to fetch them yourself, how to point Playwright at a custom location, and when the manual approach stops being worth it. For teams running AI agents or browser-use workloads at scale, the answer is often to skip local browser management entirely and connect to a hosted Chromium runtime over CDP. We will cover both paths so you can decide with real trade-offs in hand.
What "Download Playwright Browsers Manually" Actually Means
Playwright does not ship browsers inside the npm package. When you install playwright or @playwright/test, you get the driver and a CLI. The browsers — Chromium, Firefox, and WebKit builds — are downloaded separately by playwright install, which reads a registry of pinned revisions and fetches the matching archive for your OS and architecture.
"Manually" can mean several things:
- Running
playwright installyourself instead of letting a postinstall hook do it. - Downloading the browser archive directly from the CDN and extracting it to a known path.
- Installing a specific browser only (
playwright install chromium) rather than all three. - Pointing Playwright at a browser binary you already have, bypassing the download entirely.
Each has a different use case. The first is routine. The second is for air-gapped or restricted networks. The third saves disk and time. The fourth is what you want when you connect to a remote browser.
Where Playwright Puts Browsers
By default, Playwright caches browsers in a platform-specific directory:
| Platform | Default cache path |
|---|---|
| Linux | ~/.cache/ms-playwright |
| macOS | ~/Library/Caches/ms-playwright |
| Windows | %USERPROFILE%\AppData\Local\ms-playwright |
You can override this with the PLAYWRIGHT_BROWSERS_PATH environment variable. Setting it to 0 installs browsers inside node_modules instead — useful for bundling, but it bloats your dependency tree and breaks deduplication across projects.
Inside the cache, each browser lives in a versioned folder like chromium-1148 or firefox-1466. The number is Playwright's internal revision, not the upstream browser version. That distinction matters: when you pin a Playwright version, you are also pinning the browser revision it expects. Mismatches between the driver and the browser build cause connection failures that look like CDP errors but are really version skew.
Downloading a Browser Archive Directly
If you cannot run the installer — say, a locked-down build agent — you can fetch the archive yourself. Playwright publishes download URLs in its browsers.json manifest, which ships with the package at node_modules/playwright-core/browsers.json. Each entry has a name, revision, and browserVersion.
The CDN pattern is:
https://playwright.azureedge.net/builds/chromium/<revision>/chromium-linux.zipFor a specific revision, substitute the number from browsers.json. On macOS the archive name is chromium-mac.zip or chromium-mac-arm64.zip; on Windows it is chromium-win64.zip. Extract the archive into $PLAYWRIGHT_BROWSERS_PATH/chromium-<revision>/ and Playwright will find it.
A minimal shell flow:
export PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers
REV=$(node -p "require('playwright-core/browsers.json').browsers.find(b=>b.name==='chromium').revision")
mkdir -p "$PLAYWRIGHT_BROWSERS_PATH/chromium-$REV"
curl -L "https://playwright.azureedge.net/builds/chromium/$REV/chromium-linux.zip" -o /tmp/chromium.zip
unzip -q /tmp/chromium.zip -d "$PLAYWRIGHT_BROWSERS_PATH/chromium-$REV"Two things to watch. First, the archive must match your platform exactly — a Linux build will not run on macOS. Second, Playwright validates the presence of a marker file (INSTALLATION_COMPLETE) in some versions; if you extract manually and the browser is not detected, check whether that file exists and create it if needed.
Installing a Single Browser Instead of All Three
Most automation stacks only need Chromium. Running playwright install chromium skips Firefox and WebKit, cutting download time and disk usage substantially. If you use @playwright/test with multi-browser projects, you can still install only what your config references.
npx playwright install chromium
npx playwright install --with-deps chromium # also installs OS libraries on LinuxThe --with-deps flag matters on bare Linux images. Chromium needs shared libraries like libnss3, libatk1.0-0, and libgbm1. Without them, the browser binary exists but fails to launch with a cryptic error. On Debian-based images, --with-deps runs apt-get install for you; on Alpine or distroless images, you install them yourself.
Pointing Playwright at an Existing Browser
If you already have a Chrome or Chromium binary, you can skip Playwright's download entirely by passing executablePath:
import { chromium } from 'playwright';
const browser = await chromium.launch({
executablePath: '/usr/bin/google-chrome-stable',
headless: true,
});This works, but it couples your tests to whatever version is installed on the host. Playwright's protocol layer targets specific browser revisions; a much newer or older Chrome can break selectors, network interception, or tracing. For CI, pinning the Playwright-managed build is usually safer than relying on a system Chrome.
The Real Cost of Manual Browser Management
Manual downloads solve the immediate problem but create ongoing ones:
- Version drift. Every Playwright upgrade may require a new browser revision. Multiply that across a fleet of CI runners and developer machines.
- Cache invalidation. Docker layers that cache browsers get stale; you either rebuild often or accept outdated builds.
- Platform matrix. Linux, macOS Intel, macOS ARM, and Windows each need their own archive. A team of ten developers on mixed hardware means four download paths to maintain.
- Disk and bandwidth. Three browsers per revision, per machine, adds up. On ephemeral CI, you pay the download cost on every run unless you cache aggressively.
- No shared state. Local browsers cannot be shared across machines. Each agent or test run gets its own isolated instance, which is fine for correctness but expensive for concurrency.
None of this is fatal for a small team running a handful of tests. It becomes a real operational burden when you are running AI agents that need persistent profiles, proxy routing, or dozens of concurrent sessions.
When to Stop Downloading and Connect Instead
The alternative to downloading browsers is connecting to one that already runs somewhere else. Playwright supports this natively through chromium.connectOverCDP(), which attaches to a remote Chrome instance over the Chrome DevTools Protocol. The browser runs on a server; your code runs wherever you want.
import { chromium } from 'playwright';
// Connect to a hosted Chromium session over CDP.
// The endpoint comes from your runtime provider; no local browser download needed.
const browser = await chromium.connectOverCDP(
'wss://cdp.remote-browser.dev/session/<session-id>'
);
const context = browser.contexts()[0] ?? await browser.newContext();
const page = await context.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
const title = await page.title();
console.log('Page title:', title);
// Reuse the same session for follow-up work, then disconnect.
await page.close();
await browser.close();This pattern eliminates the download step entirely. There is no playwright install, no cache directory, no platform matrix. The trade-off is that you now depend on a network connection and a provider's uptime. For production AI agents, that trade is usually worth it — you get session isolation, persistent profiles, and a live viewer for debugging without building any of it yourself.
For a deeper look at how this fits into agent architectures, see Remote Browser for AI Agents and Remote Browser Online.
Local Download vs Hosted Runtime: A Comparison
| Dimension | Manual local download | Hosted Chromium runtime |
|---|---|---|
| Setup | playwright install or manual archive extraction | Connect over CDP with a session URL |
| Version management | You pin and upgrade per machine | Provider manages revisions |
| Platform support | One archive per OS/arch | Any client that speaks CDP |
| Concurrency | Limited by local CPU/RAM | Scales with provider capacity |
| Persistent profiles | Manual, per-machine | Built into the session model |
| Debugging | Local traces and screenshots | Live viewer plus traces |
| Cost model | Compute and bandwidth you own | Metered per browser-hour — see /pricing |
| Best for | Small test suites, offline dev | AI agents, browser-use, production automation |
The table is not a verdict. If you run ten tests a day on one machine, manual downloads are fine. If you run agents that need to log into sites, maintain sessions across hours, or run in parallel, the hosted model removes a category of work.
Production Criteria for Choosing
Before committing to either path, answer these questions:
- How many concurrent sessions do you need? Local browsers are bounded by the machine. If you need more than a handful, you need remote capacity.
- Do sessions need to persist? Login flows, multi-step workflows, and agents that resume work all benefit from persistent profiles. Local browsers can do this with
launchPersistentContext, but the profile lives on one machine. - How often do you upgrade Playwright? Frequent upgrades mean frequent browser re-downloads. A hosted runtime absorbs that churn.
- What is your debugging story? Local traces are excellent. Remote sessions need a live viewer or equivalent to be debuggable. Remote Browser provides one.
- What are your network constraints? Air-gapped environments may force manual downloads. Everything else can connect out.
If you are evaluating hosted options, the documentation covers session creation, CDP endpoints, and profile handling. For a broader comparison of remote browser approaches, see Remote Web Browser and Remote Control Browser.
Practical Notes on the Manual Path
If you do need manual downloads, a few details save time:
- Use `PLAYWRIGHT_BROWSERS_PATH` consistently. Set it in your Dockerfile, CI config, and local shell so every environment agrees on where browsers live.
- Cache the cache. In CI, cache the browser directory keyed on the Playwright version. This turns a multi-minute download into a cache hit.
- Pin Playwright and browsers together. Do not mix a Playwright version with a browser revision it does not expect. Read
browsers.jsonwhen in doubt. - Install only what you use.
playwright install chromiumis almost always enough. - Check the marker file. If a manually extracted browser is not detected, look for
INSTALLATION_COMPLETEin the browser directory.
For the authoritative reference on browser management and CDP connection semantics, see the Playwright browser download documentation and the Chrome DevTools Protocol documentation.
Summary
Downloading Playwright browsers manually is a legitimate technique with clear use cases: restricted networks, pinned builds, and minimal installs. The mechanics are straightforward once you know where browsers live, how revisions are named, and how to override the cache path. The cost is ongoing maintenance — version drift, platform matrices, and cache management.
For AI agents and browser-use workloads, that maintenance is usually not where you want to spend engineering time. Connecting to a hosted Chromium session over CDP removes the download step, gives you persistent profiles and a live viewer, and scales beyond what a single machine can do. Start with the manual path if your needs are small; move to a hosted runtime when concurrency, persistence, or debugging requirements outgrow it. Current usage details are on the /pricing page.