diff --git a/packages/playwright-core/src/server/webkit/wkPage.ts b/packages/playwright-core/src/server/webkit/wkPage.ts index 4cb5dbdd5e3f4..e5d065f784aae 100644 --- a/packages/playwright-core/src/server/webkit/wkPage.ts +++ b/packages/playwright-core/src/server/webkit/wkPage.ts @@ -211,8 +211,8 @@ export class WKPage implements PageDelegate { })); } promises.push(session.send('Network.setExtraHTTPHeaders', { headers: headersArrayToObject(this._calculateExtraHTTPHeaders(), false /* lowerCase */) })); - if (contextOptions.offline) - promises.push(session.send('Network.setEmulateOfflineState', { offline: true })); + if (contextOptions.offline !== undefined) + promises.push(session.send('Network.setEmulateOfflineState', { offline: contextOptions.offline })); promises.push(session.send('Page.setTouchEmulationEnabled', { enabled: !!contextOptions.hasTouch })); if (contextOptions.timezoneId) { promises.push(session.send('Page.setTimeZone', { timeZone: contextOptions.timezoneId }). diff --git a/tests/library/browsercontext-service-worker-policy.spec.ts b/tests/library/browsercontext-service-worker-policy.spec.ts index 1923a2dc4e6b3..5f660269d75be 100644 --- a/tests/library/browsercontext-service-worker-policy.spec.ts +++ b/tests/library/browsercontext-service-worker-policy.spec.ts @@ -20,6 +20,83 @@ it('should allow service workers by default', async ({ page, server }) => { await expect(page.evaluate(() => window['registrationPromise'])).resolves.toBeTruthy(); }); +for (const [mode, workerResponse] of Object.entries({ + 'literal': `new Response('

offline

', { headers: { 'Content-Type': 'text/html' } })`, + 'cache-only': `caches.match('/offline.html')`, + 'network-fallback': `fetch(event.request).catch(() => caches.match('/offline.html'))`, + 'preload-fallback': `(async () => { + try { + return await event.preloadResponse || await fetch(event.request); + } catch { + return caches.match('/offline.html'); + } + })()`, +})) { + it(`should allow ${mode} service worker navigation while offline`, { + annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/42775' }, + }, async ({ context, page, server, browserName, isElectron, isFrozenWebkit }) => { + it.skip(isElectron); + it.skip(isFrozenWebkit, 'Requires offline emulation in the network process.'); + it.fixme(browserName === 'firefox' && (mode === 'network-fallback' || mode === 'preload-fallback'), 'Firefox does not apply offline emulation to service worker requests.'); + + let networkRequests = 0; + server.setRoute('/target.html', (request, response) => { + ++networkRequests; + response.setHeader('Cache-Control', 'no-store'); + response.end('online'); + }); + server.setRoute('/offline.html', (request, response) => { + response.setHeader('Content-Type', 'text/html'); + response.end('

offline

'); + }); + server.setRoute('/sw.js', (request, response) => { + response.setHeader('Content-Type', 'application/javascript'); + response.end(` + self.addEventListener('install', event => event.waitUntil((async () => { + const cache = await caches.open('offline'); + await cache.add('/offline.html'); + await self.skipWaiting(); + })())); + self.addEventListener('activate', event => event.waitUntil((async () => { + ${mode === 'preload-fallback' ? 'await self.registration.navigationPreload.enable();' : ''} + await self.clients.claim(); + })())); + self.addEventListener('fetch', event => { + if (event.request.mode === 'navigate') + event.respondWith(${workerResponse}); + }); + `); + }); + + await page.goto(server.EMPTY_PAGE); + await page.evaluate(async () => { + await navigator.serviceWorker.register('/sw.js'); + await navigator.serviceWorker.ready; + }); + await page.waitForFunction(() => !!navigator.serviceWorker.controller); + await context.setOffline(true); + + const response = await page.goto(server.PREFIX + '/target.html'); + expect(response.status()).toBe(200); + expect(response.fromServiceWorker()).toBe(true); + await expect(page.locator('h1')).toHaveText('offline'); + expect(await page.evaluate(() => navigator.onLine)).toBe(false); + expect(networkRequests).toBe(0); + + const newPage = await context.newPage(); + const newResponse = await newPage.goto(server.PREFIX + '/target.html'); + expect(newResponse.status()).toBe(200); + expect(newResponse.fromServiceWorker()).toBe(true); + await expect(newPage.locator('h1')).toHaveText('offline'); + expect(await newPage.evaluate(() => navigator.onLine)).toBe(false); + expect(networkRequests).toBe(0); + + await context.setOffline(false); + expect(await page.evaluate(() => fetch('/target.html').then(response => response.text()))).toBe('online'); + expect(networkRequests).toBe(1); + }); +} + it.describe('block', () => { it.use({ serviceWorkers: 'block' });