feat(test-runner): add httpCache config option#41687
Conversation
e9ef052 to
1ff8228
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Skn0tt
left a comment
There was a problem hiding this comment.
I'm not seeing typical caching headers like Vary or Accept-Encoding be mentioned, which seems fishy. Am I misunderstanding something?
This is intentional, typical caching headers in dev / testing flow are not aggressive enough. We might want to have some finer grained controls for that though. |
1ff8228 to
0d1d6c4
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
0d1d6c4 to
0ebab49
Compare
There was a problem hiding this comment.
Pull request overview
Adds a new testConfig.httpCache option to Playwright Test that spins up a caching MITM proxy for the run, recording matching GET responses to disk and replaying them on subsequent runs (shared across workers), with documentation and coverage tests.
Changes:
- Introduce
httpCacheconfig (single entry or array) withdirandmatch(glob/RegExp/policy callback) and resolve cache directories relative to the config file. - Add a cache-proxy test-runner plugin that starts one (or more, per distinct upstream proxy) caching proxy server(s) and routes browsers/contexts through them.
- Add
ResponseCacheon-disk format + runner tests + API docs/type updates.
Reviewed changes
Copilot reviewed 11 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| utils/generate_types/overrides-test.d.ts | Adds HttpCachePolicy type override for generated test types. |
| tests/playwright-test/cache-proxy.spec.ts | New integration tests covering record/replay, HTTPS MITM, policy matching, coalescing, proxy chaining, and multi-cache routing. |
| packages/utils/network.ts | Exports shouldBypassProxy and allows passing agentOptions into createProxyAgent. |
| packages/playwright/types/test.d.ts | Adds httpCache config typing and HttpCachePolicy export; adds FullConfig.httpCache. |
| packages/playwright/src/runner/testRunner.ts | Wires cache-proxy plugin into runner plugin initialization. |
| packages/playwright/src/plugins/DEPS.list | Updates allowed imports for the plugins folder (cache proxy + common config). |
| packages/playwright/src/plugins/cacheProxyPlugin.ts | Implements runner plugin to initialize caches, start proxy servers, and publish them via env var. |
| packages/playwright/src/plugins/cacheProxy/server.ts | Implements caching proxy server with MITM HTTPS, upstream fetching, and request coalescing. |
| packages/playwright/src/plugins/cacheProxy/cache.ts | Implements on-disk response cache (index + blobs) with versioning. |
| packages/playwright/src/index.ts | Routes browser/context traffic through the cache proxy when enabled; enables ignoreHTTPSErrors for MITM. |
| packages/playwright/src/common/config.ts | Resolves httpCache config entries (dir resolution) and adds proxySettingsKey export. |
| docs/src/test-api/class-testconfig.md | Documents TestConfig.httpCache option and usage patterns. |
| docs/src/test-api/class-fullconfig.md | Documents FullConfig.httpCache as a resolved form of TestConfig.httpCache. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
0ebab49 to
9a13faa
Compare
Records browser responses to disk and replays them on later runs. When
`httpCache: { dir, match }` is set, the runner starts a single caching
MITM proxy and routes all browsers through it. GET/2xx responses are
stored verbatim (raw bytes + headers) and matched by an optional URL
glob/regex; https is intercepted with a self-signed cert. One proxy owns
the cache for the whole run, so all workers share it and concurrent
identical misses coalesce into one upstream fetch.
9a13faa to
12edbc0
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Test results for "MCP"5 failed 7755 passed, 1249 skipped Merge workflow run. |
Test results for "tests 1"1 flaky49808 passed, 1156 skipped Merge workflow run. |
|
Hi, I'm the Playwright bot and I took a first look at the CI failures. 🟢 All 5 failures are pre-existing MCP flakes — this PR is clearThe latest report shows 5 failed tests, all in DetailsOverall: none of the failures reach code this PR touches. The diff is scoped to the caching proxy plugin and test-runner config; the failing tests all live under Pre-existing flake / infra (counts from the aggregated test-results DB, 871 runs in window):
Each of these fails on SHAs this PR has no bearing on (including other people's PRs and mainline pushes), which is the proof they're flakes rather than caused here. Triaged by the Playwright bot - agent run |
yury-s
left a comment
There was a problem hiding this comment.
Some AI findings:
1. Every GET is store-and-forwarded. With no match, _select() claims every GET, so _fetchUpstream() buffers the whole body before responding. SSE/long-polling GETs hang forever, large downloads sit fully in runner memory, TTFB becomes TTLB for uncacheable responses. Storability is decidable from response headers — stream through when the response isn't storable.
2. Miss-coalescing ignores request headers. _fetchCoalesced() keys on method+URL+identity only. Worker A revalidates with If-None-Match while worker B requests the same URL cold → B gets a bodiless 304 it can't satisfy. Concurrent Range requests cross-serve one 206. Exclude Range/If-* requests from coalescing, or strip If-* from the shared fetch.
3. Plain ws:// breaks. Browsers CONNECT for both ws:// and wss:// through an HTTP proxy (RFC 6455 §4.1), and _onConnect() always pipes the tunnel into the TLS server, so a plaintext WS handshake dies. Sniff the first byte (0x16 = TLS) to route. Only wss:// is tested.
4. Proxy errors crash the whole run. The proxy lives in the runner process: proxyRes in _passThrough() has no 'error' handler (upstream ECONNRESET mid-body → uncaught exception), _onRequest() is fire-and-forget so a throwing match callback → unhandled rejection, and there's no req.on('error') for client aborts.
5. TLS verification is disabled end-to-end. rejectUnauthorized: false upstream plus forced ignoreHTTPSErrors: true (overriding an explicit user false). A spoofed response isn't a one-off — it's persisted and replayed on every later run. Verify upstream certs by default; at minimum document this loudly.
6. Routing gaps. _onUpgrade() dials origins directly, bypassing the configured upstream proxy — WS fails in proxy-only networks. test.use({ proxy }) is silently dropped (cacheProxySettings() keys off project.use.proxy). Remote browsers via connectOptions get an unreachable 127.0.0.1 proxy and 502 everything.
7. No non-Chromium coverage. <-loopback> is Chromium-only bypass syntax; Firefox works by accident (allow_hijacking_localhost=true in playwright.cfg), WebKit gets the literal in --proxy-bypass-list. Failure mode is a silent no-op (cache never hit). Needs at least one Firefox and one WebKit test.
|
|
||
| const upstreams = new Map<string, ProxySettings | undefined>(); | ||
| for (const project of config.projects) | ||
| upstreams.set(proxySettingsKey(project.project.use.proxy), project.project.use.proxy); |
There was a problem hiding this comment.
maybe use one global proxy per cache if needed?
| return undefined; | ||
| cacheProxyServers ??= JSON.parse(process.env.PLAYWRIGHT_TEST_CACHE_PROXY) as Record<string, string>; | ||
| const server = cacheProxyServers[config.proxySettingsKey((project.use as PlaywrightTestOptions).proxy)]; | ||
| return server ? { server, bypass: '<-loopback>' } : undefined; |
| return; // No cache yet. | ||
| } | ||
| // Discard an incompatible cache instead of mis-parsing it. | ||
| if (await this._onDiskVersion() !== CACHE_VERSION) { |
There was a problem hiding this comment.
nit: run this before reading index file content
Summary
httpCache: { dir, match }test config option. When set, the runner starts a single caching MITM proxy and routes every browser through it: matching responses are recorded todiron the first run and replayed from disk on later runs.match(glob/RegExp) selects which request URLs are cached; https is intercepted with a self-signed cert.Fixes: #22865