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' });