diff --git a/apps/pwa/e2e/calendar.spec.ts b/apps/pwa/e2e/calendar.spec.ts index d364048..8de24a0 100644 --- a/apps/pwa/e2e/calendar.spec.ts +++ b/apps/pwa/e2e/calendar.spec.ts @@ -38,19 +38,33 @@ test.describe('TEST-02 preconditions — auth bypass and SW block', () => { ) }) - test('no service-worker controller (serviceWorkers: block enforced)', async ({ page }) => { + test('no service-worker registration (serviceWorkers: block enforced)', async ({ page }) => { await page.goto('/calendar') // serviceWorkers: 'block' in playwright.config.ts prevents SW registration. - // navigator.serviceWorker.controller is null when no SW is controlling the page. - // Note: navigator.serviceWorker may be undefined in some contexts (e.g. non-https), - // but in the dev server context it is defined. Treat undefined as no-controller (safe). - const controller = await page.evaluate(() => { - if (typeof navigator === 'undefined') return null - if (!('serviceWorker' in navigator)) return null - return navigator.serviceWorker.controller - }) - expect(controller, 'Service worker controller should be null (serviceWorkers:block enforced)').toBeNull() + // + // WR-07: asserting `navigator.serviceWorker.controller === null` is near-vacuous — + // (a) on WebKit over plain http://localhost, `serviceWorker` is often *absent* from + // navigator (secure-context strictness), so the old guard returned null and the + // assertion passed without ever proving the block worked; and + // (b) `controller` is null on a first uncontrolled load even when SW *is* available, + // regardless of the block setting. + // Instead probe getRegistration() — when SW is available and `block` is in effect, no + // registration exists, so it resolves to undefined. Where `serviceWorker` is absent + // entirely (WebKit/http), skip rather than let an unavailable API masquerade as a pass. + const swAvailable = await page.evaluate( + () => typeof navigator !== 'undefined' && 'serviceWorker' in navigator, + ) + test.skip( + !swAvailable, + 'navigator.serviceWorker is unavailable in this context (e.g. WebKit over http://localhost) — block is unobservable here', + ) + + const registration = await page.evaluate(() => navigator.serviceWorker.getRegistration()) + expect( + registration, + 'No service worker should be registered (serviceWorkers:block enforced)', + ).toBeUndefined() }) }) @@ -116,9 +130,9 @@ test.describe('Rule 5 — calendar error state (API mocked to 500)', () => { const retryBtn = page.getByRole('button', { name: 'Retry' }) await expect(retryBtn).toBeVisible() - - // Unroute so the mock does not leak to subsequent tests (T-07-11) - await page.unroute('/api/events*') + // No manual unroute (WR-05): Playwright gives each test a fresh page/context, so route + // handlers do not leak across tests. A trailing unroute also never runs if an `expect` + // above throws — it was misleading "cleanup" that guaranteed nothing. }) test('Retry button meets 44px touch-target minimum in error state (Rule 1)', async ({ @@ -137,8 +151,7 @@ test.describe('Rule 5 — calendar error state (API mocked to 500)', () => { const box = await retryBtn.boundingBox() expect(box, 'Retry button bounding box must not be null').not.toBeNull() expect(box!.height, 'Retry button height must be ≥ 44px (Rule 1)').toBeGreaterThanOrEqual(44) - - await page.unroute('/api/events*') + // No manual unroute (WR-05): per-test context isolation handles route cleanup. }) test('no horizontal overflow in error state (Rule 2)', async ({ page }) => { @@ -160,7 +173,6 @@ test.describe('Rule 5 — calendar error state (API mocked to 500)', () => { overflow.scrollWidth, `scrollWidth (${overflow.scrollWidth}) must be ≤ clientWidth (${overflow.clientWidth}) in error state`, ).toBeLessThanOrEqual(overflow.clientWidth) - - await page.unroute('/api/events*') + // No manual unroute (WR-05): per-test context isolation handles route cleanup. }) })