Files
familysync/.planning/phases/07-mobile-test-harness/07-VERIFICATION.md
T
Lucas Berger 982438dc10 style(13-03): apply Prettier formatting across repo
Mechanical reformat — no logic changes. 398 files changed, 19125
insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc
(singleQuote:true, semi:true, tabWidth:2, trailingComma:all,
printWidth:100). Isolated per D-13-08 for reviewability.
2026-06-11 20:35:18 -04:00

20 KiB
Raw Blame History

phase, verified, status, score, overrides_applied
phase verified status score overrides_applied
07-mobile-test-harness 2026-06-11T02:30:00Z passed 4/4 must-haves verified 0

Phase 7: Mobile Test Harness Verification Report

Phase Goal: The assistant can drive the PWA in a mobile-emulated, authenticated browser context against the host-side dev stack, so mobile-only layout and flow defects can be caught automatically instead of only by the operator on real devices. This harness is also the artifact Phase 8 (CI) runs for UI regression. Verified: 2026-06-11T02:30:00Z Status: passed Re-verification: No — initial verification

Goal Achievement

Observable Truths

# Truth Status Evidence
1 An automated run can load the PWA in a mobile-emulated viewport (device profile + mobile UA + touch) and assert on responsive layout / tap targets. VERIFIED playwright.config.ts defines devices['iPhone 14'] (WebKit, 390×844, Mobile Safari UA, hasTouch) and devices['Pixel 7'] (Chromium, 412×915, Chrome Android UA, hasTouch). layout.spec.ts asserts boundingBox geometry (≥44px tap targets, ≥56px FAB, no overflow). All 58 tests pass on both profiles. Harness self-validation proves assertions track rendered geometry, not CSS source.
2 The automated run reaches the authenticated PWA via the existing DEV_AUTH_BYPASS on the host-side dev stack — no manual login and no Authelia/OIDC mocking. VERIFIED playwright.config.ts has no storageState key. global-setup.ts has no OIDC mock. calendar.spec.ts includes an explicit runtime assertion: getByRole('navigation', { name: 'Main navigation' }) is visible and page.url() hostname matches `/^(localhost 127.0.0.1)$/`. Live run confirms both profiles reach authenticated content via DEV_AUTH_BYPASS with no redirect to Authelia.
3 The harness runs repeatably day-over-day without re-capturing any session state (no stale storage-state failures). VERIFIED No storageState key exists anywhere in playwright.config.ts, global-setup.ts, or any spec. global-setup.ts issues TRUNCATE+seed on every run. Two consecutive full runs both produced 58/58 passing in 2728s with identical results. No auth artifact on disk; the bypass is stateless per-request.
4 The harness specs are structured so they can run headlessly in CI (Phase 8) against a dev stack the runner brings up — no dependence on a developer's already-running host stack. VERIFIED baseURL: process.env.PLAYWRIGHT_BASE_URL ?? 'http://localhost:5173' (env-driven). reuseExistingServer: !process.env.CI (CI starts fresh). retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: process.env.CI ? 'github' : 'list'. No hardcoded hosts in any spec file (grep confirms 0 absolute URLs). DB credentials all env-driven via DB_* vars. e2e/README.md documents exact CI env-var contract.

Score: 4/4 truths verified

Required Artifacts

Artifact Expected Status Details
apps/pwa/playwright.config.ts Two-project device matrix, serviceWorkers block, env baseURL, globalSetup ref, vite-only webServer VERIFIED Contains devices['iPhone 14'], devices['Pixel 7'], serviceWorkers: 'block' on both projects, globalSetup: './e2e/global-setup.ts', PLAYWRIGHT_BASE_URL env pattern, reuseExistingServer: !process.env.CI
apps/pwa/e2e/global-setup.ts /health readiness poll, TRUNCATE+seed for user 1/calendar 10, fail-closed guard VERIFIED Fail-closed guard (NODE_ENV=production throws, DEV_AUTH_BYPASS!='true' throws) runs before any DB connection. Polls ${baseURL}/health 60s. TRUNCATE list_items/list_shares/lists/calendar_events with FK checks disabled. INSERT IGNORE calendars id=10. Seeds one calendar_event, one shared list, list_shares row, two list_items. No @playwright/test import.
apps/pwa/e2e/layout.spec.ts UI-SPEC Rules 1-4 + injected-defect self-validation VERIFIED Contains boundingBox assertions (≥44px Calendar/Lists/Settings tabs, ≥56px FAB), overflow eval on /calendar and /lists, in-viewport check, role+name locators throughout, two addStyleTag self-validation proofs, no absolute URLs.
apps/pwa/e2e/calendar.spec.ts Populated + error states, auth-bypass + SW-block precondition assertions VERIFIED Contains "Couldn't load events" heading assertion, Retry button ≥44px, page.route('/api/events*', ...500) before goto, auth-bypass URL hostname assertion, navigator.serviceWorker.controller null assertion.
apps/pwa/e2e/lists.spec.ts Populated + empty states, no DB mutation VERIFIED Contains "E2E Grocery List" card assertion, "No lists yet" presence/absence checks, network-simulated empty state via page.route('/api/lists', ...), overflow checks in both states.
apps/pwa/e2e/README.md Run instructions, DEV_AUTH_BYPASS guardrail, no-storageState documentation VERIFIED Documents the full run command, security guardrail (production compose MUST NOT set DEVAUTH_BYPASS), all DB* and PLAYWRIGHT_BASE_URL env vars, no storageState file policy, CI usage notes.
apps/pwa/vitest.config.ts exclude: ['e2e/**'] to prevent Vitest/Playwright spec collision VERIFIED Line 17: exclude: ['e2e/**', 'node_modules/**'] inside the test: block.
apps/pwa/tsconfig.e2e.json Separate tsconfig bringing playwright.config.ts and e2e/** into the typecheck gate VERIFIED Extends ./tsconfig.json, include: ["playwright.config.ts", "e2e/**/*"]. The typecheck script runs both: tsc --noEmit && tsc --project tsconfig.e2e.json --noEmit.
apps/pwa/package.json @playwright/test devDependency + test:e2e scripts VERIFIED @playwright/test: 1.60.0 in devDependencies. test:e2e, test:e2e:ui, test:e2e:headed scripts present.
Root package.json test:e2e workspace delegation script VERIFIED "test:e2e": "pnpm --filter @familysync/pwa test:e2e"
From To Via Status Details
playwright.config.ts e2e/global-setup.ts globalSetup: './e2e/global-setup.ts' VERIFIED File exists, is a valid default export async function, runs before any spec
playwright.config.ts PLAYWRIGHT_BASE_URL env baseURL: process.env.PLAYWRIGHT_BASE_URL ?? 'http://localhost:5173' VERIFIED Same pattern used in webServer.url and inside global-setup.ts
global-setup.ts dev MariaDB :3306 mysql2.createConnection with DB_* env vars VERIFIED Uses DB_HOST ?? '127.0.0.1', DB_PORT ?? 3306, DB_USER ?? 'familysync', DB_PASSWORD ?? '', DB_NAME ?? 'familysync' — mirrors apps/api/src/db/client.ts
global-setup.ts calendar_events.calendar_id=10 INSERT IGNORE INTO calendars guard before event insert VERIFIED Line 90: INSERT IGNORE INTO calendars (id, user_id, url, display_name, color, is_shared) VALUES (10, 1, ...) — works on fresh CI DB and populated dev DB
layout.spec.ts BottomTabBar nav getByRole('navigation', { name: 'Main navigation' }) VERIFIED Tests resolve on both profiles; no strict-mode collision (DesktopNav at ≥768px is not rendered on 390/412px viewports)
calendar.spec.ts page.route('/api/events*', ...) Error-state simulation registered before goto VERIFIED Line 98: route registered before page.goto('/calendar'), unrouted on line 116
lists.spec.ts seeded 'E2E Grocery List' card getByRole('button', { name: 'Open list: E2E Grocery List' }) VERIFIED Card resolves on both profiles after global-setup seed

Data-Flow Trace (Level 4)

Not applicable — this phase produces a test harness (spec files and config), not a UI component that renders dynamic data from an API. The harness itself is the data producer for downstream assertions.

Behavioral Spot-Checks (Step 7b)

Behavior Command Result Status
Exactly two projects (iphone, pixel) reported playwright test --list Lists 29 iphone + 29 pixel = 58 tests; both project names confirmed PASS
Full 58-test suite passes (run 1) pnpm --filter @familysync/pwa test:e2e 58 passed (28.0s) PASS
Full 58-test suite passes (run 2 — idempotency) pnpm --filter @familysync/pwa test:e2e 58 passed (27.4s) PASS
Typecheck gate covers e2e files pnpm --filter @familysync/pwa typecheck Exit 0 (both tsc --noEmit and tsc --project tsconfig.e2e.json --noEmit) PASS
No storageState or toHaveScreenshot in harness grep across config + all specs 0 matches (1 comment-only hit in config) PASS
No absolute URLs in spec files grep for https?://localhost in e2e/*.spec.ts 0 matches PASS

Probe Execution

No conventional scripts/*/tests/probe-*.sh probes declared for this phase.

Requirements Coverage

Requirement Source Plan Description Status Evidence
TEST-01 Plans 01, 03, 04 Drive PWA in mobile-emulated viewport (device profile + mobile UA + touch) for automated UI/layout verification SATISFIED devices['iPhone 14'] + devices['Pixel 7'] in config with hasTouch; layout.spec.ts measures boundingBox; 29 tests per profile pass
TEST-02 Plans 01, 02, 04 Automated runs reach authenticated PWA via DEV_AUTH_BYPASS (no manual login, no OIDC mock) SATISFIED No storageState; fail-closed guard requires DEV_AUTH_BYPASS=true; calendar.spec.ts asserts auth-bypass at runtime; live runs confirm

Anti-Patterns Found

File Line Pattern Severity Impact
None found No TBD/FIXME/XXX/TODO/PLACEHOLDER markers in any harness file

Code review WR-04 identified that lists.spec.ts uses the exact string /api/lists instead of the glob /api/lists* for the empty-state route mock (inconsistent with the calendar spec's /api/events* glob). This is an advisory warning from the review — the harness currently passes because the endpoint has no query params. This is catalogued but does not block goal achievement; the phase goal is achieved and this is a robustness concern for future maintenance.

Human Verification Required

None. All success criteria are verifiable programmatically and were verified via live execution.

The following items from the code review are advisory warnings (not blockers for phase goal):

  • WR-01: Readiness gate accepts any 2xx — could mis-fire if Authelia redirects to a 200 login page. Mitigated in practice by the fail-closed DEV_AUTH_BYPASS guard which ensures the API is running in bypass mode before the test process starts.
  • WR-02: Deadline-expiry uses Date.now() >= deadline inference instead of an explicit boolean. Low practical risk given the 60s window and 1s poll interval.
  • WR-03: Health gate polls Vite's proxied /health rather than the API directly. Works correctly via the Vite proxy; a downed API would produce a 5xx not a 200.
  • WR-04: /api/lists exact match vs glob — brittle to future query param additions.
  • WR-05: unroute not in finally — an assertion failure could leave a mock active for subsequent tests in the same worker.
  • WR-06: Self-validation comment says "page.reload drops inline style tags" but code uses el.remove() — stale comment; code is correct.
  • WR-07: SW-controller assertion passes vacuously on WebKit where navigator.serviceWorker may be undefined.

These are carried from the code review as advisory only; none block the phase goal.

Gaps Summary

No gaps against the four success criteria — all verified against the actual codebase and confirmed by live execution.


Post-verification addendum (deep code review, 2026-06-11)

A deep cross-file code review run after this verification found that two calendar.spec.ts "populated state" assertions were vacuous — they targeted CalendarShell's EmptyState (dead code, never rendered) and the always-rendered Schedule-X wrapper, so they could not have failed if the seed regressed. This did not invalidate the four success criteria (SC-1's layout/tap-target coverage is layout.spec.ts, which carries its own injected-defect self-validation and remains sound), but it was a real coverage gap in the calendar populated-state tests.

Resolved in commit 53c3ca5: replaced with a genuine DB→UI proof (getByText('Seeded Test Event') visible in the grid), verified non-vacuous (passes with the seed on both profiles; with /api/events mocked to [] the title is absent, so the assertion would fail), and re-anchored the seed to noon-today so it sits deterministically inside the rendered current-month view. Full 58-test suite passes on both profiles. See 07-REVIEW.md BL-01/BL-02.


Verified: 2026-06-11T02:30:00Z Verifier: Claude (gsd-verifier) Addendum: 2026-06-11 — deep review BL-01/BL-02 resolved (commit 53c3ca5)