Cache browser binaries with
actions/cacheto avoid downloading 150MB+ on every CI run.
Playwright and Puppeteer download full browser binaries (Chromium, Firefox, WebKit) during installation. Without caching, every workflow run downloads these binaries from scratch, adding 30-90 seconds of network time depending on the browser set and runner region.
Cache Playwright Browsers
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
- name: Install Playwright Chromium
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: npx playwright install chromium
- name: Install Playwright system deps
run: npx playwright install-deps chromium
- name: Run browser tasks
run: node scripts/generate-screenshots.js
How This Works
actions/cachechecks for a cached copy of~/.cache/ms-playwrightmatching the lock file hash.- On a cache hit, browser binaries are restored in ~5 seconds instead of downloaded in ~60 seconds.
- The
install chromiumstep is skipped entirely on cache hits. - System dependencies (
install-deps) must run every time because they install OS-level packages (libnss3,libatk1.0, etc.) that are not persisted across runner instances.
Cache Puppeteer Browsers
Puppeteer stores browsers in a different default location. The pattern is the same with a different cache path:
- name: Cache Puppeteer browsers
id: puppeteer-cache
uses: actions/cache@v4
with:
path: ~/.cache/puppeteer
key: puppeteer-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
- name: Install Puppeteer browser
if: steps.puppeteer-cache.outputs.cache-hit != 'true'
run: npx puppeteer browsers install chrome
Cache Key Strategy
The cache key playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }} ensures:
- OS specificity: Browser binaries are platform-specific. A Linux-cached browser will not work on macOS or Windows runners.
- Version tracking: When
playwrightorpuppeteeris upgraded inpackage-lock.json, the hash changes, and a fresh download is triggered with the correct browser version.
For monorepos or workspaces with multiple lock files, point hashFiles to the relevant lock file:
key: playwright-${{ runner.os }}-${{ hashFiles('apps/web/package-lock.json') }}
Install Only What You Need
Both Playwright and Puppeteer support installing individual browsers. If your workflow only needs Chromium, skip the rest:
# Playwright: install only Chromium (skip Firefox, WebKit)
npx playwright install chromium
# Puppeteer: install only Chrome
npx puppeteer browsers install chrome
Installing all browsers downloads 400MB+ and triples the cache size. Install only what your workflow actually uses.
Combining Cache with Conditional Steps
If some workflow runs do not need a browser at all, combine caching with conditional execution to skip both the cache restore and the install step:
- name: Detect if screenshots needed
id: changes
run: |
if git diff --name-only HEAD~1 -- 'src/content/**' | grep -q .; then
echo "needs_browser=true" >> "$GITHUB_OUTPUT"
fi
- name: Cache Playwright browsers
if: steps.changes.outputs.needs_browser == 'true'
id: playwright-cache
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
- name: Install Playwright Chromium
if: steps.changes.outputs.needs_browser == 'true' && steps.playwright-cache.outputs.cache-hit != 'true'
run: npx playwright install chromium
This avoids restoring a 150MB cache on runs that will never use a browser.
Key Points
- Browser binaries are often the largest single download in Node.js CI workflows.
- Cache
~/.cache/ms-playwright(Playwright) or~/.cache/puppeteer(Puppeteer) usingactions/cache. - Include
package-lock.jsonhash in the cache key so version upgrades trigger a fresh download. - System dependencies (
install-deps) must run on every workflow run, even on cache hits. - Install only the specific browsers your workflow uses to minimize download and cache size.
Related Tips
View all tips →Write Files Incrementally in Build Scripts to Avoid Unnecessary Rebuilds
Compare generated content against existing files before writing. This prevents unnecessary git diffs, cache invalidation, and downstream rebuilds.
Skip Expensive CI Steps Using git diff Instead of File Timestamps
File modification timestamps are unreliable in GitHub Actions. Use git diff to detect actual changes and conditionally skip expensive workflow steps.