Stop Downloading Duplicate Browsers: Use System Chrome with Puppeteer and Playwright
Table of Contents

Browser automation tools are convenient until each project quietly downloads another browser. On macOS, Puppeteer and Playwright can each keep several gigabytes of Chromium builds in their caches, even when Google Chrome is already installed and up to date.
For local automation that only needs Chrome, I use the system browser instead. That reclaimed more than 4 GB on my machine and still lets Puppeteer, Playwright Test, Playwright CLI, and Playwright MCP do their jobs.
This is a local-development optimization, not a replacement for Playwright’s pinned browsers. Keep reading for the trade-offs.
Check what is using the space #
Puppeteer and Playwright keep their downloaded browsers in separate macOS caches. Check their sizes before deleting anything:
du -sh ~/.cache/puppeteer 2>/dev/null
du -sh ~/Library/Caches/ms-playwright 2>/dev/null
~/.cache/puppeteer contains Puppeteer’s downloaded Chrome or Chrome for Testing builds. ~/Library/Caches/ms-playwright contains the browsers installed for Playwright.
First make sure that regular Google Chrome is installed at the standard macOS location:
test -x "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
&& echo "Google Chrome is available"
Point Puppeteer at system Chrome #
Set PUPPETEER_EXECUTABLE_PATH to make Puppeteer use the already-installed Google Chrome executable:
export PUPPETEER_EXECUTABLE_PATH="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
Add that export to the shell profile you use for local automation, such as ~/.zshrc, if it should persist between terminals.
Puppeteer can also be configured explicitly in code. This is useful when a project should be self-contained instead of relying on its caller’s environment:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
headless: true,
});
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.pdf({ path: 'example.pdf', format: 'A4' });
await browser.close();
Run a real workflow, such as PDF generation, before removing the cache. It confirms that permissions, launch arguments, and any fonts used by your automation work with system Chrome.
Configure Playwright to use Chrome #
Playwright treats installed, branded browsers as channels. Choosing the chrome channel selects the regular Google Chrome installation rather than Playwright’s bundled Chromium.
For Playwright Test, configure the project with channel: 'chrome':
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'Google Chrome',
use: { ...devices['Desktop Chrome'], channel: 'chrome' },
},
],
});
For one-off browsing with Playwright CLI, select the same channel:
playwright-cli open https://example.com --browser=chrome
Playwright MCP accepts the browser either as an argument or an environment variable:
npx @playwright/mcp@latest --browser chrome
export PLAYWRIGHT_MCP_BROWSER=chrome
If Chrome is installed somewhere nonstandard, Playwright MCP also provides --executable-path for an explicit executable location.
Optionally default Playwright CLI to Chrome #
Passing --browser=chrome is explicit and works well for one-off commands. If every local playwright-cli open command should use regular Chrome, add this wrapper to ~/.zshrc:
playwright-cli() {
if [[ "$1" == "open" ]]; then
local arg
for arg in "${@:2}"; do
if [[ "$arg" == --browser || "$arg" == --browser=* ]]; then
command playwright-cli "$@"
return
fi
done
command playwright-cli open --browser=chrome "${@:2}"
return
fi
command playwright-cli "$@"
}
Reload the shell configuration:
source ~/.zshrc
The wrapper changes only open, so commands such as playwright-cli snapshot still pass through unchanged. It also preserves an explicit choice, allowing a different browser when needed:
playwright-cli open https://example.com
playwright-cli open https://example.com --browser=firefox
command playwright-cli calls the installed executable directly instead of the shell function, preventing the wrapper from recursively calling itself.
Skipping downloads is not browser selection #
This environment variable prevents Playwright from downloading browser binaries:
export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
It does not tell Playwright which browser to launch. Without channel: 'chrome', --browser=chrome, PLAYWRIGHT_MCP_BROWSER=chrome, or another explicit configuration, a workflow may still expect Playwright’s bundled Chromium.
Likewise, PUPPETEER_EXECUTABLE_PATH selects an executable for Puppeteer; it does not remove browser files which have already been downloaded.
Clean caches after verification #
Once the Puppeteer script and relevant Playwright workflows succeed with Chrome, remove the downloaded browser caches:
rm -rf ~/.cache/puppeteer
rm -rf ~/Library/Caches/ms-playwright
Run the size check again to confirm the disk space has been recovered:
du -sh ~/.cache/puppeteer ~/Library/Caches/ms-playwright 2>/dev/null
The next project that needs a managed browser can download it again. If cache location is the concern rather than cache size, Puppeteer supports PUPPETEER_CACHE_DIR and Playwright supports PLAYWRIGHT_BROWSERS_PATH to store downloads elsewhere.
Keep managed browsers where they matter #
System Chrome is a good choice for local Chrome-only automation. It is not a substitute for the browser binaries pinned by Playwright:
- Use Playwright-managed browsers when testing Chromium, Firefox, or WebKit.
- Use them in CI when reproducibility matters more than saving local disk space.
- Keep the official Playwright Docker or DevContainer image for containerized tests. Its matching browser binaries live inside the container image, so deleting host caches does not affect those runs.
The practical split is simple: use system Chrome for local automation that targets Chrome, and keep Playwright’s managed browsers where cross-browser coverage and repeatability are part of the requirement.