20 / 66 · 09 Mobile & Cross-Platform Testing · PWA Testing← prev⊞ allnext →☰ Read as one page
4.2Service Worker Testing
Service workers intercept network requests and enable offline functionality. They operate in a lifecycle that must be tested independently:
// tests/pwa/service-worker.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Service Worker Lifecycle', () => {
test('service worker registers on first visit', async ({ page }) => {
await page.goto('/');
// Wait for SW registration
const swRegistered = await page.evaluate(async () => {
const registration = await navigator.serviceWorker.ready;
return registration.active !== null;
});
expect(swRegistered).toBe(true);
});
test('app works offline after caching', async ({ page, context }) => {
// First visit: let the SW cache resources
await page.goto('/');
await page.waitForLoadState('networkidle');
// Verify critical resources are cached
const cachedUrls = await page.evaluate(async () => {
const cache = await caches.open('app-shell-v1');
const keys = await cache.keys();
return keys.map(k => new URL(k.url).pathname);
});
expect(cachedUrls).toContain('/');
expect(cachedUrls).toContain('/styles/main.css');
expect(cachedUrls).toContain('/scripts/app.js');
// Go offline
await context.setOffline(true);
// Navigate -- should work from cache
await page.goto('/');
await expect(page.locator('h1')).toBeVisible();
// Verify offline indicator is shown
await expect(page.locator('[data-testid="offline-banner"]')).toBeVisible();
// Restore connectivity
await context.setOffline(false);
});
test('stale content is updated when back online', async ({ page, context }) => {
await page.goto('/');
await page.waitForLoadState('networkidle');
// Go offline, navigate
await context.setOffline(true);
await page.goto('/dashboard');
const offlineContent = await page.locator('[data-testid="data-timestamp"]').textContent();
// Come back online
await context.setOffline(false);
await page.reload();
await page.waitForLoadState('networkidle');
const onlineContent = await page.locator('[data-testid="data-timestamp"]').textContent();
// Content should be fresher after reconnection
expect(onlineContent).not.toBe(offlineContent);
});
test('service worker updates when new version deployed', async ({ page }) => {
await page.goto('/');
await page.waitForLoadState('networkidle');
// Check if the SW detects a new version
const updateAvailable = await page.evaluate(async () => {
const reg = await navigator.serviceWorker.getRegistration();
return new Promise((resolve) => {
if (reg?.waiting) {
resolve(true);
return;
}
reg?.addEventListener('updatefound', () => {
resolve(true);
});
setTimeout(() => resolve(false), 5000);
});
});
// If an update is available, verify the update prompt appears
if (updateAvailable) {
await expect(page.locator('[data-testid="update-prompt"]')).toBeVisible();
}
});
});