Build-an-X workflow that emits per-SW state-transition tests covering the six `ServiceWorkerState` values per [w3c-github-io/ServiceWorker][sw-spec] (`parsed → installing → installed → activating → activated → redundant`), the `install` / `activate` / `fetch` event handlers per [MDN Service Worker API][mdn-sw], `event.waitUntil()` lifetime extension, `ServiceWorkerGlobalScope.skipWaiting()` and `Clients.claim()` upgrade-path semantics, the `statechange` event on `ServiceWorker` objects, `ServiceWorkerRegistration.update()`, and `navigator.serviceWorker.controller` checks. Output: a Playwright spec file with one test per transition plus a clean upgrade-path test (v1 active → v2 installed/waiting → v2 activated, with claim()). The general Playwright SW harness, `service-worker-mock` unit tests, and cache-strategy assertions live in references/. Use when authoring the baseline lifecycle spec, when a deploy leaves users stuck on the old service worker, or when a site's caching / offline behavior is uncovered.
64
81%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Deep reference for service-worker-lifecycle-tests. Consult for the
deeper recipes past the persistent-context setup and offline worked
example in playwright-sw-harness.md:
version-bump cache invalidation, service-worker-mock unit tests, and
push-notification subscription tests.
Assert a v2 worker deletes the v1 caches when it activates:
test('SW v2 deletes v1 caches on activate', async ({ context, page }) => {
await page.goto('https://localhost:3000');
let sw = context.serviceWorkers()[0]
?? await context.waitForEvent('serviceworker');
const v1Caches = await sw.evaluate(() => caches.keys());
expect(v1Caches).toContain('app-v1');
// Trigger SW update (deploy v2 to test server)
await page.evaluate(() => navigator.serviceWorker.getRegistration().then(r => r?.update()));
// Wait for activation
await page.waitForFunction(() =>
navigator.serviceWorker.controller?.scriptURL.includes('v2')
);
const v2Caches = await sw.evaluate(() => caches.keys());
expect(v2Caches).toContain('app-v2');
expect(v2Caches).not.toContain('app-v1');
});service-worker-mockFor Jest/Vitest unit tests that don't need a browser:
npm install --save-dev service-worker-mockimport makeServiceWorkerEnv from 'service-worker-mock';
beforeEach(() => {
Object.assign(global, makeServiceWorkerEnv());
jest.resetModules();
});
test('install event opens cache and pre-caches assets', async () => {
await import('../src/sw.js');
await self.trigger('install');
expect(self.snapshot().caches['app-v1']).toBeDefined();
expect(self.snapshot().caches['app-v1']['/index.html']).toBeDefined();
});test('push subscription created on registration', async ({ page, context }) => {
await context.grantPermissions(['notifications']);
await page.goto('https://localhost:3000');
const subscription = await page.evaluate(async () => {
const reg = await navigator.serviceWorker.ready;
return reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: '<base64-vapid-key>',
});
});
expect(subscription).toBeDefined();
});Pair with web-push-tests for downstream send/receive assertions.
launchPersistentContext,
context.serviceWorkers(), evaluate(), MV3 lifecycle behavior.