diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index 21ce066a62..f15de9fc4a 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -209,6 +209,7 @@ export abstract class BrowserContext extends SdkObject { await this.setGeolocation(this._options.geolocation); await this.setOffline(!!this._options.offline); await this.setUserAgent(this._options.userAgent); + await this.clearCache(); await this._resetCookies(); await page?.resetForReuse(metadata); @@ -246,6 +247,7 @@ export abstract class BrowserContext extends SdkObject { abstract setUserAgent(userAgent: string | undefined): Promise; abstract setOffline(offline: boolean): Promise; abstract cancelDownload(uuid: string): Promise; + abstract clearCache(): Promise; protected abstract doGetCookies(urls: string[]): Promise; protected abstract doGrantPermissions(origin: string, permissions: string[]): Promise; protected abstract doClearPermissions(): Promise; diff --git a/packages/playwright-core/src/server/chromium/crBrowser.ts b/packages/playwright-core/src/server/chromium/crBrowser.ts index fe74ae5b0b..37c4d7110d 100644 --- a/packages/playwright-core/src/server/chromium/crBrowser.ts +++ b/packages/playwright-core/src/server/chromium/crBrowser.ts @@ -543,6 +543,11 @@ export class CRBrowserContext extends BrowserContext { } } + override async clearCache(): Promise { + for (const page of this._crPages()) + await page._mainFrameSession._networkManager.clearCache(); + } + async cancelDownload(guid: string) { // The upstream CDP method is implemented in a way that no explicit error would be given // regarding the requested `guid`, even if the download is in a state not suitable for diff --git a/packages/playwright-core/src/server/chromium/crNetworkManager.ts b/packages/playwright-core/src/server/chromium/crNetworkManager.ts index c7ce7d4b50..d8a305cd1e 100644 --- a/packages/playwright-core/src/server/chromium/crNetworkManager.ts +++ b/packages/playwright-core/src/server/chromium/crNetworkManager.ts @@ -127,6 +127,14 @@ export class CRNetworkManager { } } + async clearCache() { + // Sending 'Network.setCacheDisabled' with 'cacheDisabled = true' will clear the MemoryCache. + await this._client.send('Network.setCacheDisabled', { cacheDisabled: true }); + if (!this._protocolRequestInterceptionEnabled) + await this._client.send('Network.setCacheDisabled', { cacheDisabled: false }); + await this._client.send('Network.clearBrowserCache'); + } + _onRequestWillBeSent(workerFrame: frames.Frame | undefined, event: Protocol.Network.requestWillBeSentPayload) { // Request interception doesn't happen for data URLs with Network Service. if (this._protocolRequestInterceptionEnabled && !event.request.url.startsWith('data:')) { diff --git a/packages/playwright-core/src/server/firefox/ffBrowser.ts b/packages/playwright-core/src/server/firefox/ffBrowser.ts index 7df8d98b0a..52bc17c1cc 100644 --- a/packages/playwright-core/src/server/firefox/ffBrowser.ts +++ b/packages/playwright-core/src/server/firefox/ffBrowser.ts @@ -358,6 +358,11 @@ export class FFBrowserContext extends BrowserContext { onClosePersistent() {} + override async clearCache(): Promise { + // Clearing only the context cache does not work: https://bugzilla.mozilla.org/show_bug.cgi?id=1819147 + await this._browser._connection.send('Browser.clearCache'); + } + async doClose() { if (!this._browserContextId) { if (this._options.recordVideo) { diff --git a/packages/playwright-core/src/server/webkit/wkBrowser.ts b/packages/playwright-core/src/server/webkit/wkBrowser.ts index ba86f957d2..d62752aece 100644 --- a/packages/playwright-core/src/server/webkit/wkBrowser.ts +++ b/packages/playwright-core/src/server/webkit/wkBrowser.ts @@ -339,6 +339,13 @@ export class WKBrowserContext extends BrowserContext { onClosePersistent() {} + override async clearCache(): Promise { + // We use ephemeral contexts so there is no disk cache. + await this._browser._browserSession.send('Playwright.clearMemoryCache', { + browserContextId: this._browserContextId! + }); + } + async doClose() { if (!this._browserContextId) { await Promise.all(this._wkPages().map(wkPage => wkPage._stopVideo())); diff --git a/tests/library/browsercontext-reuse.spec.ts b/tests/library/browsercontext-reuse.spec.ts index 7367478fcb..f04cf28265 100644 --- a/tests/library/browsercontext-reuse.spec.ts +++ b/tests/library/browsercontext-reuse.spec.ts @@ -127,3 +127,58 @@ test('should reset serviceworker that hangs in importScripts', async ({ reusedCo await page.goto(server.PREFIX + '/page.html'); await expect(page).toHaveTitle('Page Title'); }); + +test('should not cache resources', async ({ reusedContext, server }) => { + test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/19926' }); + const requestCountMap = new Map(); + server.setRoute('/page.html', (req, res) => { + res.setHeader('Content-Type', 'text/html'); + res.setHeader(`Cache-Control`, `max-age=3600`); + const requestCount = requestCountMap.get(req.url) || 0; + res.end(` + + + + + Count: ${requestCount} + + + + + `); + requestCountMap.set(req.url, requestCount + 1); + }); + server.setRoute('/style.css', (req, res) => { + res.setHeader('Content-Type', 'text/css'); + res.setHeader(`Cache-Control`, `max-age=3600`); + res.end(`body { background-color: red; }`); + requestCountMap.set(req.url, (requestCountMap.get(req.url) || 0) + 1); + }); + server.setRoute('/simple.json', (req, res) => { + res.setHeader(`Cache-Control`, `max-age=3600`); + res.setHeader('Content-Type', 'application/json'); + res.end(`{ "foo": "bar" }`); + requestCountMap.set(req.url, (requestCountMap.get(req.url) || 0) + 1); + }); + + { + const context = await reusedContext(); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/page.html'); + await expect(page).toHaveTitle('Count: 0'); + expect(requestCountMap.get('/page.html')).toBe(1); + expect(requestCountMap.get('/style.css')).toBe(1); + expect(requestCountMap.get('/simple.json')).toBe(1); + } + { + const context = await reusedContext(); + const page = context.pages()[0]; + await page.goto(server.PREFIX + '/page.html'); + await expect(page).toHaveTitle('Count: 1'); + expect(requestCountMap.get('/page.html')).toBe(2); + expect(requestCountMap.get('/style.css')).toBe(2); + expect(requestCountMap.get('/simple.json')).toBe(2); + } +});