You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Feature]is it possible to reuse static resouces cache of previous test run ? #22865
I want to reuse all the static resource cache from Test Case 1 in Test Case 2, so that this test case will not download the resources anymore, it will be very useful when it accesses some websites with large file resources
In Isolation mode, Is there a way to achieve it?
PS: Just want to reuse the static resources not cookies or storage.
Reacted by Victor Gladkikh, Sander, Kamil Chmielewski, Shubham Kanodia, Sean Lafferty, Danylo, Andrei Skindzer, Zdravko Donev, Oleh Bonchuk, Dimitris Marlagkoutsos and 94 more
changed the title [-][Feature]is it possible to reuse previous test run static resouces cache?[/-][+][Feature]is it possible to reuse static resouces cache of previous test run ?[/+]on May 6, 2023
We don't share the disk cache between the isolated contexts. Leaving this open to collect more upvotes.
Reacted by Victor Gladkikh, Romain Primet, grvas, Kristoffer Rasmussen, Olivier Beaulieu, Borja Arias, Alex Faunt, Annick Walkenhorst, Hendrik Sparenberg, Abhijith Shivaswamy and 3 more
This would be an useful feature to have and I've been asked about reducing the number of calls on our test server which we are running our test against, 600+ test cases all requesting the same resource over and over again. mocking and interception is used in some cases but majority of the time pages require script bundles to work properly, reusing them would be fantastic.
Reacted by Romain Primet, Danylo, victoriareed, DPros, Zeke Zhang, Noam Shapira, Kristoffer Rasmussen, cpmotion and Pierre-Louis Dagues
This would be an useful feature to have and I've been asked about reducing the number of calls on our test server which we are running our test against, 600+ test cases all requesting the same resource over and over again. mocking and interception is used in some cases but majority of the time pages require script bundles to work properly, reusing them would be fantastic.
Disk/memory resource caching can speed up tests quite a bit — and this is especially true for VR tests where page load might often be the most time-consuming task.
Pavel Feldman (@pavelfeldman) I can share some stats on how much of a difference this can make with a large internal test suite. Could you please point me to how and where I can go about (temporarily) make this change — in case it doesn't need a large overhaul of things.
Reacted by Noam Shapira, Kristoffer Rasmussen and Islam
As we already have something like page.context() to persist and share between contexts, could be there a similar concept of sharing loaded resources ? Like integrate service worker to respond for these calls or something..
We've rewritten our tests to not use the playwright testing framework and instead use cucumber.js
Reusing a page context between tests (while still maintaining test isolation via some backend specific multitenancy/postgres tricks) made our tests much, much, much faster.
+1 for cache reuse across test runs. Playwright's browser binaries + test artifacts can consume several GB, especially in CI where fresh installs happen on every run.
On local macOS development, Playwright browsers are cached at ~/Library/Caches/ms-playwright/ and can grow to 1-3GB per browser version. Combined with other dev caches (node_modules, Docker, Xcode DerivedData, Homebrew), it adds up fast.
For anyone looking to audit and manage these caches locally, I built ClearDisk a free, open-source macOS menu bar utility that monitors 44+ developer cache paths and shows size breakdowns at a glance. Useful for keeping disk space healthy when working with multiple heavy toolchains.
# Quick check of Playwright cache size
du -sh ~/Library/Caches/ms-playwright/ 2>/dev/null
# Remove old browser versions (keeps latest)
npx playwright install --with-deps
Hi Pavel Feldman (@pavelfeldman) ,
I see that there is a merged PR that references this issue and I'm quite excited about this feature, but I didn't find any docs on the added httpCache property of the TestConfig.
Can this cache already be used? If so, how and in which version of playwright?
In a ~2,500-spec Playwright suite for a large Next.js app, this one documented side effect ("Enabling routing disables http cache") turned out to be our single biggest per-test cost, and it is invisible per test: it just reads as "the app is slow to load". Posting the numbers here because #7220 (the "don't turn the cache off when I add a route" half of this) was closed as a duplicate of this issue, and that half needs no cross-run cache at all, only not switching off the one Chromium already has for the context.
What we measured (Playwright 1.64.x, bundled Chromium, same machine, same production bundle)
With any page.route() registered, even one for a single unrelated API URL, every document load re-fetches all 87–91 JS chunks + fonts + CSS over HTTP/1.1. A second navigation to the same document: 3.4–3.7 s. With no route registered the cache serves them and the same navigation takes ~0.5 s.
Across the suite (2,400 specs mocking API calls with page.route): per-case mean 22 s. Moving the mocks to an in-page fetch/XHR patch installed with addInitScript (interception off, cache on) was part of what brought it to 8.5 s mean / 5.8 s p50. Other changes contributed, so the controlled number is the per-navigation one above.
Minimal repro (single file below: static server with 90 scripts + 10 stylesheets served Cache-Control: immutable, 20 ms per response, fresh context per scenario, navigations #2/#3 are warm):
scenario
warm navigation
server hits
cached (transferSize 0)
no route
54–123 ms
0
100
page.route('**/api/never', r => r.fulfill(...))
418–451 ms
100
0
page.route('**/*', r => r.continue())
474–514 ms
100
0
With 0 ms server latency: 60–99 ms vs 173–190 ms vs 222–259 ms.
Mechanism, as I read crNetworkManager.ts: enabling interception (any route, also httpCredentials or proxy credentials) sends Network.setCacheDisabled({ cacheDisabled: true }) next to Fetch.enable({ patterns: [{ urlPattern: '*', requestStage: 'Request' }] }). The cache-off part is Playwright's choice, not something CDP needs: with that one call flipped to false locally, a catch-all route in a fresh page of a warm context still saw 101/101 requests, and route.continue() served all 100 assets from the cache (0 server hits, 160–230 ms). What the cache-off buys is same-process re-navigations, where the renderer reuses fresh assets from its memory cache without issuing a request, so the handler is not called (1/101 in that case). A second CDP session sending Network.setCacheDisabled(false) does not undo it (the flag appears to be OR'd across sessions).
Ask
An opt-in to keep the HTTP cache while routes are registered (a context or route option), with the memory-cache caveat documented. For the overwhelmingly common use, mocking no-store API endpoints, the caveat changes nothing.
Until then, make the one line in the page.route / browserContext.route docs say what it costs (every navigation after the first re-downloads every asset) and point at an in-page fetch patch via addInitScript as the way to mock APIs without paying it. The network guide does not mention the cache at all today.
// Repro: any page.route() registration turns off Chromium's HTTP cache for the// whole page, so every navigation re-downloads every asset.//// npm i [email protected] && npx playwright install chromium// node repro.mjs # 90 assets, no server latency// LATENCY_MS=20 node repro.mjs # 20 ms per asset response (a dev server)// ASSETS=200 node repro.mjs//// Static server: index.html + N script chunks + 10 stylesheets, all served with// `Cache-Control: public, max-age=31536000, immutable` over HTTP/1.1. Each// scenario gets a fresh browser context, loads the page 3 times and reports the// 2nd/3rd navigation (the 1st one fills the cache). "server hits" counts asset// requests that reached the server; "cached" counts Resource Timing entries// with transferSize === 0 as seen by the page itself.importhttpfrom'node:http';import{performance}from'node:perf_hooks';import{chromium}from'playwright';constASSETS=Number(process.env.ASSETS||90);constLATENCY_MS=Number(process.env.LATENCY_MS||0);constCSS=10;constjs=(i)=>`// chunk ${i}\n`+`window.__c${i}=function(){return ${i}};\n`.repeat(400);constcss=(i)=>`.c${i}{color:#${(i*1234567).toString(16).slice(0,6).padStart(6,'0')}}\n`.repeat(200);consthtml='<!doctype html><html><head><meta charset="utf-8">'+Array.from({length: CSS},(_,i)=>`<link rel="stylesheet" href="/css/${i}.css">`).join('')+'</head><body><div id="app">app</div>'+Array.from({length: ASSETS},(_,i)=>`<script src="/js/${i}.js" async></script>`).join('')+'</body></html>';letserverHits=0;constserver=http.createServer((req,res)=>{constsend=(type,body,cacheable)=>{res.writeHead(200,{'content-type': type,'cache-control': cacheable ? 'public, max-age=31536000, immutable' : 'no-store',});res.end(body);};constm=req.url.match(/^\/(js|css)\/(\d+)\.(js|css)$/);if(m){serverHits++;constbody=m[1]==='js' ? js(+m[2]) : css(+m[2]);consttype=m[1]==='js' ? 'text/javascript' : 'text/css';if(LATENCY_MS)setTimeout(()=>send(type,body,true),LATENCY_MS);elsesend(type,body,true);return;}if(req.url==='/')returnsend('text/html',html,false);res.writeHead(404).end();});awaitnewPromise((r)=>server.listen(0,'127.0.0.1',r));constorigin=`http://127.0.0.1:${server.address().port}`;constscenarios={'no route': async()=>{},"page.route('**/api/never')": async(page)=>{awaitpage.route('**/api/never',(route)=>route.fulfill({status: 200,body: '{}'}));},"page.route('**/*', continue)": async(page,counters)=>{awaitpage.route('**/*',(route)=>{counters.routed++;returnroute.continue();});},};constbrowser=awaitchromium.launch();console.log(`playwright ${(awaitimport('playwright/package.json',{with: {type: 'json'}})).default.version}, ${browser.browserType().name()}${browser.version()}, ${ASSETS} js + ${CSS} css, latency ${LATENCY_MS} ms`);console.log('scenario'.padEnd(32),'nav','wall ms'.padStart(8),'server hits'.padStart(12),'cached'.padStart(7),'routed'.padStart(7));for(const[name,setup]ofObject.entries(scenarios)){constcontext=awaitbrowser.newContext();constpage=awaitcontext.newPage();constcounters={routed: 0};awaitsetup(page,counters);for(letnav=1;nav<=3;nav++){serverHits=0;counters.routed=0;constt0=performance.now();awaitpage.goto(origin+'/',{waitUntil: 'load'});constms=performance.now()-t0;constcached=awaitpage.evaluate(()=>performance.getEntriesByType('resource').filter((e)=>e.transferSize===0).length,);console.log(name.padEnd(32),`#${nav}`,ms.toFixed(0).padStart(8),String(serverHits).padStart(12),String(cached).padStart(7),String(counters.routed).padStart(7));}awaitcontext.close();}awaitbrowser.close();server.close();
Reacted by Sam Ottenhoff, Mathieu Laurent, Josef Bredreck, mOGrs, Lance Jeffers, Aaron Agarunov, Kye Hohenberger, Charles Xu, Georgii Dolzhykov, Bruno Ferreira and 7 more
We're closing all P3-collecting-feedback issues and retiring the label. #43813 explains why. This doesn't mean the request is wrong. If it still matters to you, please open a new issue against the latest Playwright version with your concrete use case and link back here.
Activity
[-][Feature]is it possible to reuse previous test run static resouces cache?[/-][+][Feature]is it possible to reuse static resouces cache of previous test run ?[/+]pavelfeldman commentedon May 8, 2023
We don't share the disk cache between the isolated contexts. Leaving this open to collect more upvotes.
Jabbar2010 commentedon May 9, 2023
Pavel Feldman (@pavelfeldman) Hi, is there another way to achieve it and reuse the disk cache?
pavelfeldman commentedon May 9, 2023
Not at this point. Using caching HTTP proxy would solve it, but I get how it is somewhat hard to set up.
rolandz83 commentedon May 10, 2023
This would be an useful feature to have and I've been asked about reducing the number of calls on our test server which we are running our test against, 600+ test cases all requesting the same resource over and over again. mocking and interception is used in some cases but majority of the time pages require script bundles to work properly, reusing them would be fantastic.
Jabbar2010 commentedon May 10, 2023
Agree
pastelsky commentedon Jul 7, 2023
Disk/memory resource caching can speed up tests quite a bit — and this is especially true for VR tests where page load might often be the most time-consuming task.
Pavel Feldman (@pavelfeldman) I can share some stats on how much of a difference this can make with a large internal test suite. Could you please point me to how and where I can go about (temporarily) make this change — in case it doesn't need a large overhaul of things.
drRobertDev commentedon Jul 2, 2024
should be good to implement it. why waste network resources to redownload something you have in other context.
statyan commentedon Dec 13, 2024
As we already have something like page.context() to persist and share between contexts, could be there a similar concept of sharing loaded resources ? Like integrate service worker to respond for these calls or something..
thomas-colgrove commentedon Feb 3, 2025
This would probably make my tests 3x faster - currently its almost faster to run them in serial mode to avoid disk cache penalty.
uptickmetachu commentedon Feb 3, 2025
We've rewritten our tests to not use the playwright testing framework and instead use cucumber.js
Reusing a page context between tests (while still maintaining test isolation via some backend specific multitenancy/postgres tricks) made our tests much, much, much faster.
xoewrl commentedon Feb 20, 2025
A workaround using Route
bysiber commentedon Mar 5, 2026
+1 for cache reuse across test runs. Playwright's browser binaries + test artifacts can consume several GB, especially in CI where fresh installs happen on every run.
On local macOS development, Playwright browsers are cached at
~/Library/Caches/ms-playwright/and can grow to 1-3GB per browser version. Combined with other dev caches (node_modules, Docker, Xcode DerivedData, Homebrew), it adds up fast.For anyone looking to audit and manage these caches locally, I built ClearDisk a free, open-source macOS menu bar utility that monitors 44+ developer cache paths and shows size breakdowns at a glance. Useful for keeping disk space healthy when working with multiple heavy toolchains.
vitalets commentedon May 27, 2026
One more workaround is playwright-network-cache. Originally, I've built it for caching API calls, but it works fine for static assets as well.
Here's an example for caching
*.pngimages with 24 hours TTL:MatsLP commentedon Aug 20, 2026
Hi Pavel Feldman (@pavelfeldman) ,
I see that there is a merged PR that references this issue and I'm quite excited about this feature, but I didn't find any docs on the added
httpCacheproperty of theTestConfig.Can this cache already be used? If so, how and in which version of playwright?
alexanderdavide commentedon Sep 9, 2026
MatsLP, it was reverted.
vjeux commentedon Oct 23, 2026
In a ~2,500-spec Playwright suite for a large Next.js app, this one documented side effect ("Enabling routing disables http cache") turned out to be our single biggest per-test cost, and it is invisible per test: it just reads as "the app is slow to load". Posting the numbers here because #7220 (the "don't turn the cache off when I add a route" half of this) was closed as a duplicate of this issue, and that half needs no cross-run cache at all, only not switching off the one Chromium already has for the context.
What we measured (Playwright 1.64.x, bundled Chromium, same machine, same production bundle)
page.route()registered, even one for a single unrelated API URL, every document load re-fetches all 87–91 JS chunks + fonts + CSS over HTTP/1.1. A second navigation to the same document: 3.4–3.7 s. With no route registered the cache serves them and the same navigation takes ~0.5 s.page.route): per-case mean 22 s. Moving the mocks to an in-pagefetch/XHR patch installed withaddInitScript(interception off, cache on) was part of what brought it to 8.5 s mean / 5.8 s p50. Other changes contributed, so the controlled number is the per-navigation one above.Minimal repro (single file below: static server with 90 scripts + 10 stylesheets served
Cache-Control: immutable, 20 ms per response, fresh context per scenario, navigations #2/#3 are warm):page.route('**/api/never', r => r.fulfill(...))page.route('**/*', r => r.continue())With 0 ms server latency: 60–99 ms vs 173–190 ms vs 222–259 ms.
Mechanism, as I read
crNetworkManager.ts: enabling interception (any route, alsohttpCredentialsor proxy credentials) sendsNetwork.setCacheDisabled({ cacheDisabled: true })next toFetch.enable({ patterns: [{ urlPattern: '*', requestStage: 'Request' }] }). The cache-off part is Playwright's choice, not something CDP needs: with that one call flipped tofalselocally, a catch-all route in a fresh page of a warm context still saw 101/101 requests, androute.continue()served all 100 assets from the cache (0 server hits, 160–230 ms). What the cache-off buys is same-process re-navigations, where the renderer reuses fresh assets from its memory cache without issuing a request, so the handler is not called (1/101 in that case). A second CDP session sendingNetwork.setCacheDisabled(false)does not undo it (the flag appears to be OR'd across sessions).Ask
no-storeAPI endpoints, the caveat changes nothing.page.route/browserContext.routedocs say what it costs (every navigation after the first re-downloads every asset) and point at an in-pagefetchpatch viaaddInitScriptas the way to mock APIs without paying it. The network guide does not mention the cache at all today.Environment: Playwright 1.64.1 (latest), Chromium 156.0.8078.4 (headless shell), Node 24.13.0, CentOS Stream 9, Linux 6.16.
repro.mjs(npm i [email protected] && npx playwright install chromium && LATENCY_MS=20 node repro.mjs)pavelfeldman commentedon Oct 31, 2026
We're closing all
P3-collecting-feedbackissues and retiring the label. #43813 explains why. This doesn't mean the request is wrong. If it still matters to you, please open a new issue against the latest Playwright version with your concrete use case and link back here.