Playwright Uninstall Browsers: Clean Up and Switch to Remote
If you need to playwright uninstall browsers, the command is short: npx playwright uninstall. What takes longer is deciding whether you should be installing those browser binaries at all. Playwright downloads Chromium, Firefox, and WebKit into a cache directory that can quietly grow past a gigabyte per project, and on CI runners or agent hosts that cache is pure overhead. This guide covers the uninstall mechanics, what actually gets deleted, and when moving to a hosted runtime removes the problem entirely.
What playwright uninstall Actually Does
Playwright keeps browser binaries in a versioned cache, not in node_modules. The location depends on your OS:
| Platform | Default browser cache path |
|---|---|
| Linux | ~/.cache/ms-playwright |
| macOS | ~/Library/Caches/ms-playwright |
| Windows | %USERPROFILE%\AppData\Local\ms-playwright |
Running npx playwright uninstall removes every browser revision in that cache. It does not touch your package.json, your node_modules, or your test files. It also does not remove the playwright or @playwright/test npm packages — those stay installed and will re-download browsers the next time you run a test that needs them.
If you want to remove a single browser instead of all of them, pass a flag:
# Remove only Chromium
npx playwright uninstall --only-shell
# Remove everything (default behavior)
npx playwright uninstall
Note that --only-shell targets the headless shell build. For full control over which browser families exist on disk, the more reliable approach is to delete the cache directory directly and then reinstall only what you need:
rm -rf ~/.cache/ms-playwright
npx playwright install chromium
That two-step pattern — wipe, then install one browser — is the cleanest way to shrink a bloated cache without guessing at flags.
Why the Cache Grows So Fast
Each Playwright release pins specific browser revisions. When you upgrade Playwright, the new version expects a different Chromium build, so it downloads a fresh copy rather than reusing the old one. The previous revision stays on disk until you clean it up. On a project that upgrades Playwright monthly, you can accumulate four or five Chromium builds plus their headless shells without noticing.
The same dynamic hits container images. A Dockerfile that runs npx playwright install --with-deps bakes a large volume of browser binaries into a layer. If you build that image on every commit, you are paying for storage and transfer on binaries that never change between builds.
There is also the dependency surface. --with-deps pulls in system libraries for font rendering, audio, and codec support. On minimal base images those packages can conflict with what your application already ships. Uninstalling browsers does not remove those system packages, so a full cleanup sometimes means rebuilding the image from a leaner base.
Uninstalling on CI and Container Hosts
On ephemeral CI runners, the cache usually disappears with the runner, so uninstalling is unnecessary. The cost shows up in install time instead: a cold playwright install adds tens of seconds to every job. Teams often cache the browser directory between runs to avoid that, which reintroduces the version-drift problem — a stale cache can serve a browser revision that no longer matches the installed Playwright version.
The practical rules:
- Ephemeral runners: install browsers fresh each run, skip the cache, accept the download cost.
-
Persistent runners: pin the Playwright version, cache the browser directory keyed on that version, and run
npx playwright uninstallbefore re-caching when you bump it. -
Containers: install browsers in a build stage, copy only what the runtime needs, and never run
playwright installat container start.
If you are running browser automation at any real volume, this maintenance work is the signal that local binaries may be the wrong model. A hosted runtime moves the browser lifecycle off your machines entirely — see Remote Browser for AI agents for how that fits together.
When Uninstalling Is the Wrong Fix
Uninstalling browsers solves a disk-space problem. It does not solve the reasons teams usually start looking at it:
- Version drift between local and CI. Your laptop has Chromium 141, CI has 139, and a rendering difference breaks a test.
- Headless detection. Local Chromium with default flags is easy to fingerprint, and uninstalling does not change that.
- Concurrency limits. One machine can only run so many browser processes before memory pressure kills sessions.
-
Session persistence. Local browsers lose cookies and storage state when the process exits unless you wire up
storageStatemanually.
If any of those are your actual problem, the uninstall command is a detour. The better move is to stop managing browser binaries locally and connect to a browser that already runs somewhere else.
Switching from Local Browsers to a Remote Runtime
Playwright's connectOverCDP lets you attach to a Chromium instance over the DevTools Protocol instead of launching a local one. The browser runs on a remote host; your code stays where it is. This is the same mechanism documented in the Playwright CDP guide and the Chrome DevTools Protocol specification.
Here is the shape of it in TypeScript:
import { chromium, Browser, BrowserContext, Page } from 'playwright';
interface RemoteSession {
cdpUrl: string;
sessionId: string;
}
async function connectToRemoteBrowser(session: RemoteSession): Promise<{
browser: Browser;
context: BrowserContext;
page: Page;
}> {
// Connect to a hosted Chromium instance over CDP.
// The browser is already running; no local binaries required.
const browser = await chromium.connectOverCDP(session.cdpUrl, {
timeout: 30_000,
});
// Reuse the existing context so persistent profile state is preserved.
const contexts = browser.contexts();
const context = contexts.length > 0 ? contexts[0] : await browser.newContext();
const pages = context.pages();
const page = pages.length > 0 ? pages[0] : await context.newPage();
page.setDefaultTimeout(45_000);
return { browser, context, page };
}
async function runTask(cdpUrl: string, sessionId: string) {
const { browser, page } = await connectToRemoteBrowser({ cdpUrl, sessionId });
try {
await page.goto('https://example.com/dashboard', {
waitUntil: 'domcontentloaded',
});
await page.getByRole('button', { name: 'Export' }).click();
await page.waitForEvent('download');
} finally {
// Close the connection, not the remote browser, if the session
// is managed by the runtime and should stay alive.
await browser.close();
}
}
Two details matter in production. First, browser.close() on a CDP connection disconnects your client — whether it also terminates the remote browser depends on how the runtime manages session lifetime. Second, browser.contexts() returns contexts that already exist on the remote side, which is how you inherit logged-in state without replaying a login flow.
Once you are on this model, the local cache question disappears. There is nothing to uninstall because nothing was installed. You can read more about the connection patterns in Remote Web Browser and the documentation.
What You Still Install Locally
Moving to a remote runtime does not eliminate all local setup. You still need:
- The
playwrightorpuppeteernpm package, which provides the client library and protocol bindings. - Your test runner, assertions, and any fixtures.
- Optionally, a browser for local debugging — many teams keep one Chromium install for stepping through a failing test by hand.
What you drop is the requirement that every machine running automation has a matching, patched, correctly-versioned browser binary. That is the part that scales badly.
Comparison: Local Playwright Browsers vs. Hosted Runtime
| Concern | Local Playwright browsers | Hosted Chromium runtime |
|---|---|---|
| Disk footprint | 300 MB–1 GB+ per cache, grows with upgrades | None on your machines |
| Version consistency | Drifts across dev, CI, and prod | Controlled by the runtime |
| Install/upgrade cost | Re-download on every Playwright bump | No local install step |
| Concurrency | Bounded by host memory and CPU | Scales with the runtime's capacity |
| Session persistence | Manual storageState handling |
Persistent profiles managed server-side |
| Headless detection | Default flags are fingerprintable | Configurable browser settings |
| Debugging | Local DevTools | Live viewer plus CDP access |
| Cost model | Compute you already pay for | Metered per browser-hour — see /pricing |
The trade-off is real: a hosted runtime adds a network hop and a per-hour cost. For a handful of tests on a developer laptop, local browsers are simpler. For agent workloads that run continuously, need persistent logins, or fan out across many concurrent sessions, the local model becomes the bottleneck.
Cleaning Up Without Breaking Your Setup
If you are staying local for now, here is a safe cleanup sequence:
- Check what is on disk:
du -sh ~/.cache/ms-playwrighton Linux or macOS. - List installed revisions:
npx playwright install --dry-runshows what the current version expects. - Remove stale revisions by deleting the cache directory, then reinstall only the browsers your test suite actually uses.
- Pin your Playwright version in
package.jsonso upgrades are deliberate rather than automatic. - If you use Docker, install browsers in a separate build stage and copy the cache forward.
That keeps the cache bounded. It does not fix version drift or detection, but it stops the disk from filling up.
Deciding Between the Two
Ask one question: does the browser need to live on the same machine as the code that drives it? For a local debugging session, yes. For a test suite in CI, no. For an AI agent that needs a persistent logged-in session, a stable IP, and the ability to run dozens of tasks in parallel, definitely not.
The playwright uninstall command is a useful maintenance tool, and you should know it exists. But if you find yourself running it often, the underlying issue is that you are treating browser binaries as something you own and maintain. A hosted runtime treats them as infrastructure someone else runs. For teams building agent workflows, that shift is usually worth more than the disk space it saves. Start with the Remote Browser documentation to see how sessions, profiles, and CDP access are exposed, and check /pricing for current rates before you commit to a migration.
Top comments (0)