Skip to content

[ci] Cache Chrome binary across CI runs for Browser Run tests#13981

Merged
petebacondarwin merged 2 commits into
mainfrom
pbd/ci/cache-chrome-binary
May 20, 2026
Merged

[ci] Cache Chrome binary across CI runs for Browser Run tests#13981
petebacondarwin merged 2 commits into
mainfrom
pbd/ci/cache-chrome-binary

Conversation

@petebacondarwin

@petebacondarwin petebacondarwin commented May 20, 2026

Copy link
Copy Markdown
Contributor

No tracked issue; follow-up to #13971 and #13980.

What

Browser Run tests in packages/miniflare/test/plugins/browser/index.spec.ts and the fixtures/browser-run fixture call into @puppeteer/browsers to ensure Chrome is downloaded into the global Wrangler cache. Every CI run currently re-downloads ~150 MB of Chrome from scratch because the cache directory is per-runner-instance and not shared between runs.

Cache it across runs.

How

Add an actions/cache@v4 step keyed on the OS plus the Chrome version hardcoded in packages/miniflare/src/index.ts (126.0.6478.182), restoring/saving:

  • ~/.cache/.wrangler/chrome (Linux)
  • ~/Library/Caches/.wrangler/chrome (macOS)
  • ~/AppData/Local/xdg.cache/.wrangler/chrome (Windows)

actions/cache@v4 silently skips paths that don't exist for the current OS, so listing all three in a single step keeps the workflow flat.

The cache step runs for the packages-and-tools suite on all three OSes (miniflare browser spec runs everywhere) and for the fixtures suite on macOS + Windows (the Browser Run fixture is excluded on Ubuntu because of AppArmor — pptr.dev/troubleshooting).

Why

Two benefits:

  1. Cuts ~150 MB of repeat downloads off cold CI runs for affected matrix jobs. Browser Run tests on Windows currently spend a meaningful chunk of their wall-clock time just downloading Chrome.
  2. Reduces the attack surface for the intermittent partial-extraction race that surfaces as The browser folder (...) exists but the executable (...) is missing (diagnostic in #13971, in-process recovery in #13980). When the cache is warm and the binary is already fully extracted, install() short-circuits and the race can't fire at all.

These two PRs are complementary, not alternatives:

Maintenance note

The cache key has the Chrome version literally in it: chrome-${{ runner.os }}-126.0.6478.182. When that version is bumped in packages/miniflare/src/index.ts, this key needs a manual bump too. The cost of forgetting is just one extra download (the new version misses the cache, downloads, then populates the new key) — no functional impact, no broken tests. Avoiding hashFiles('packages/miniflare/src/index.ts') because that file is ~3,200 lines and changes for unrelated reasons constantly, which would defeat the cache.

Test plan

  • pnpm check:format
  • Will rely on this PR's own CI to confirm the cache step parses and runs.

The first run on this PR will be a cache miss (no key exists yet). The cache populates from this run. Subsequent runs on this branch — and any PRs branched off main after this lands — will be cache hits.


  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows: this is a CI workflow change. Validation will be visible on this PR's own CI run — the cache step will appear in the job logs with "Cache not found" on first run (expected), then populate on save.
    • Additional testing not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: CI-only workflow change with no user-facing or API surface.

Open in Devin Review

@petebacondarwin petebacondarwin added the ci:no-changeset-required Skip pull request checks for a valid changeset label May 20, 2026
@changeset-bot

changeset-bot Bot commented May 20, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 4f6f5ce

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-project-automation github-project-automation Bot moved this to Untriaged in workers-sdk May 20, 2026
@workers-devprod workers-devprod requested review from a team and jamesopstad and removed request for a team May 20, 2026 08:07
@workers-devprod

workers-devprod commented May 20, 2026

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • ✅ @cloudflare/wrangler
Show detailed file reviewers

@ask-bonk

ask-bonk Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Now I have the key information from xdg-portable:

  • Linux: cache = ~/.cache (so xdgAppPaths(".wrangler").cache() = ~/.cache/.wrangler)
  • macOS: cache = ~/Library/Caches (so = ~/Library/Caches/.wrangler)
  • Windows: cache = %LOCALAPPDATA%\xdg.cache (so = %LOCALAPPDATA%\xdg.cache\.wrangler)

But wait -- the PR uses ~/AppData/Local/xdg.cache/.wrangler/chrome for Windows. On GitHub Actions Windows runners, %LOCALAPPDATA% is typically C:\Users\runneradmin\AppData\Local, and ~ (home) resolves to C:\Users\runneradmin. So ~/AppData/Local/xdg.cache/.wrangler/chrome effectively maps to C:\Users\runneradmin\AppData\Local\xdg.cache\.wrangler\chrome, which should be the same as %LOCALAPPDATA%\xdg.cache\.wrangler\chrome. That looks correct.

But the cacheDir passed to install() is getGlobalWranglerCachePath() which returns xdgAppPaths(".wrangler").cache(). This is the isolated xdg cache path, which for the xdg-app-paths library would be <xdg-cache>/.wrangler. Then @puppeteer/browsers install() puts Chrome under <cacheDir>/chrome/<platform>/<buildId>/....

So the actual path to Chrome files on Linux would be: ~/.cache/.wrangler/chrome/...

The PR caches:

  • ~/.cache/.wrangler/chrome (Linux)
  • ~/Library/Caches/.wrangler/chrome (macOS)
  • ~/AppData/Local/xdg.cache/.wrangler/chrome (Windows)

This looks correct. Let me also double-check what @puppeteer/browsers puts under cacheDir:

The install() function from @puppeteer/browsers uses cacheDir as the base and creates <cacheDir>/chrome/<platform>/<buildId>/.... So the chrome subdirectory under the cache dir is indeed where the browser files go. The PR paths match this.

Let me also verify the if condition logic is correct by checking the matrix:

The matrix is {os: [macos-latest, ubuntu-latest, windows-latest], suite: [packages-and-tools, fixtures]}. The condition is:

steps.changes.outputs.everything_but_markdown == 'true' && 
(matrix.suite == 'packages-and-tools' || 
 (matrix.suite == 'fixtures' && matrix.os != 'ubuntu-latest'))

This means:

  • packages-and-tools on all OSes: cache step runs (all 3)
  • fixtures on macOS and Windows: cache step runs (2)
  • fixtures on Ubuntu: cache step does NOT run

This matches the comment that the browser fixture is skipped on Ubuntu (AppArmor) but miniflare spec runs everywhere.

The change looks clean and correct. The version matches, the paths match the xdg library behavior, the condition logic is sound, and the degradation mode (cache miss just triggers a download) is safe.

LGTM

github run

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

@pkg-pr-new

pkg-pr-new Bot commented May 20, 2026

Copy link
Copy Markdown
create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@13981

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@13981

miniflare

npm i https://pkg.pr.new/miniflare@13981

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@13981

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@13981

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@13981

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/@cloudflare/vitest-pool-workers@13981

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@13981

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@13981

wrangler

npm i https://pkg.pr.new/wrangler@13981

commit: 4f6f5ce

Comment thread .github/workflows/test-and-check.yml Outdated
Browser Run tests in `packages/miniflare/test/plugins/browser/index.spec.ts`
and the `fixtures/browser-run` fixture call into `@puppeteer/browsers`
to ensure Chrome is downloaded into the global Wrangler cache. Every
CI run currently re-downloads ~150 MB of Chrome from scratch because
the cache directory is per-runner-instance and not shared between runs.

Add an `actions/cache@v4` step keyed on the OS + the Chrome version
hardcoded in `packages/miniflare/src/index.ts` (`126.0.6478.182")`,
restoring/saving `~/.cache/.wrangler/chrome` (Linux),
`~/Library/Caches/.wrangler/chrome` (macOS), and
`~/AppData/Local/xdg.cache/.wrangler/chrome` (Windows).

Benefits:
- Cuts ~150 MB and the associated download time off cold CI runs.
- Reduces the surface area for the intermittent partial-extraction
  race that surfaces as `The browser folder (...) exists but the
  executable (...) is missing` (see #13971 for the diagnostic that
  exposed this, #13980 for the in-process recovery layer). When the
  cache is warm and the binary is already extracted, this race can't
  fire at all because `install()` short-circuits.

The cache step runs for the `packages-and-tools` suite on all three
OSes and for the `fixtures` suite on macOS + Windows (the Browser Run
fixture is excluded on Ubuntu because of AppArmor).

When the Chrome version in `packages/miniflare/src/index.ts` changes,
the cache key here needs to be bumped manually. A miss only triggers a
fresh download — no functional impact.
Move the `126.0.6478.182` Chrome version literal out of `packages/miniflare/src/index.ts` into a dedicated `packages/miniflare/src/plugins/browser-rendering/browser-version.ts` module, and switch the `actions/cache` key to `hashFiles()` over that file. Bumping the constant in one place now automatically invalidates the CI cache without a separate workflow edit.
@petebacondarwin petebacondarwin force-pushed the pbd/ci/cache-chrome-binary branch from be05bea to 4f6f5ce Compare May 20, 2026 12:14

@workers-devprod workers-devprod left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codeowners reviews satisfied

@github-project-automation github-project-automation Bot moved this from Untriaged to Approved in workers-sdk May 20, 2026
@petebacondarwin petebacondarwin merged commit 25c3d9f into main May 20, 2026
58 checks passed
@github-project-automation github-project-automation Bot moved this from Approved to Done in workers-sdk May 20, 2026
@petebacondarwin petebacondarwin deleted the pbd/ci/cache-chrome-binary branch May 20, 2026 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:no-changeset-required Skip pull request checks for a valid changeset

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants