CtrlK
BlogDocsLog inGet started
Tessl Logo

testland/service-worker-lifecycle-tests

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()). Use when authoring the baseline lifecycle spec, or when a deploy leaves users stuck on the old service worker - scoped to the state machine and the `skipWaiting` / `clients.claim` upgrade path, not to general service-worker assertion or cache-strategy patterns.

69

Quality

87%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

upgrade-path.mdreferences/

Lifecycle coverage matrix and v1 -> v2 upgrade-path spec

Companion detail for service-worker-lifecycle-tests. The per-transition test cells (Steps 2 to 8) are the runnable core and stay in SKILL.md; this file holds the coverage matrix and the worked upgrade-path spec.

Coverage matrix

tests/sw-lifecycle-coverage.yaml maps each spec to its state-machine cell. CI gates on every matrix row having at least one passing test.

# tests/sw-lifecycle-coverage.yaml
matrix:
  fresh_install:
    spec: "first install transitions parsed → installing → installed → activating → activated"
    states: [parsed, installing, installed, activating, activated]
    ref: sw-spec ServiceWorkerState enum
  waituntil:
    spec: "SW install with slow precache stays in installing until waitUntil resolves"
    states: [installing]
    ref: mdn-sw waitUntil semantics
  skipwaiting:
    spec: "skipWaiting() makes v2 active without page reload"
    states: [installed, activating, activated]
    ref: mdn-skipwaiting
  claim:
    spec: "clients.claim() makes v2 control the page mid-session"
    states: [activated]
    ref: mdn-claim
  redundant:
    spec: "old SW transitions to redundant after v2 activates"
    states: [redundant]
    ref: sw-spec ServiceWorkerState enum
  controller_semantics:
    spec: "controller is null on first hard-reload, set after activation"
    ref: mdn-sw controller property
  updatefound:
    spec: "updatefound fires when a new SW is found"
    ref: mdn-sw updatefound event

Worked example: a v1 -> v2 upgrade-path test

For an SW that uses skipWaiting() + Clients.claim():

// tests/sw-upgrade-path.spec.ts
import { test, expect } from '@playwright/test';

test('v1 → v2 upgrade: skip waiting + claim, old cache deleted', async ({ page, context }) => {
  // 1. Land on v1 and confirm it controls the page.
  await page.goto('https://localhost:3000/?sw-version=1');
  await page.waitForFunction(() => navigator.serviceWorker.controller !== null);

  const v1Cache = await page.evaluate(async () => {
    const names = await caches.keys();
    return names.find(n => n.endsWith('-v1'));
  });
  expect(v1Cache).toBeTruthy();

  // 2. Trigger v2 deploy.
  await page.evaluate(async () => {
    const reg = await navigator.serviceWorker.getRegistration();
    await reg!.update();
  });

  // 3. Wait for the controller to flip to v2 (skipWaiting + claim).
  const v2Controller = await page.waitForFunction(() => {
    const c = navigator.serviceWorker.controller;
    return c && c.scriptURL.endsWith('sw-v2.js') ? c.scriptURL : null;
  }, { timeout: 10_000 });
  expect(await v2Controller.jsonValue()).toMatch(/sw-v2/);

  // 4. Confirm v1 caches are deleted by v2's activate handler.
  const remainingCaches = await page.evaluate(() => caches.keys());
  expect(remainingCaches.some((n: string) => n.endsWith('-v1'))).toBe(false);
  expect(remainingCaches.some((n: string) => n.endsWith('-v2'))).toBe(true);
});

This single test exercises four state transitions (installed → activating in v2, activated → redundant in v1) plus the cache-cleanup convention.

Source references:

references

SKILL.md

tile.json