Files
familysync/.planning/phases/07-mobile-test-harness/07-03-SUMMARY.md
T

7.6 KiB
Raw Blame History

phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics
07-mobile-test-harness 03 test-harness
playwright
e2e
layout
tap-targets
overflow
accessibility
self-validation
requires provides affects
apps/pwa/playwright.config.ts (07-01) — iphone/pixel project matrix, serviceWorkers: 'block'
apps/pwa/e2e/global-setup.ts (07-02) — /health readiness gate + DB seed (calendar_id=10, E2E Grocery List)
apps/pwa/e2e/layout.spec.ts — UI-SPEC Rules 1-4 assertions (tap targets, overflow, in-viewport, accessible names)
Harness self-validation: addStyleTag injected-defect proofs for Rule 1 + Rule 2
30 tests (15 per profile) — all passing on iphone (WebKit) and pixel (Chromium)
Phase 07 plan 04 (calendar.spec.ts / lists.spec.ts share the same harness foundation)
Phase 08 CI (layout.spec.ts is a PR regression step)
added patterns
boundingBox() — rendered geometry measurement, not CSS-declared values
page.evaluate(() => scrollWidth/clientWidth) — DOM overflow measurement
page.addStyleTag + handle.evaluate(el => el.remove()) — injected-defect proof pattern
getByRole('navigation', { name }) scoping — avoids strict-mode collision between BottomTabBar and DesktopNav
getByText('FamilySync', { exact: true }) — avoids matching 'Install FamilySync' install-prompt
created modified
apps/pwa/e2e/layout.spec.ts
apps/pwa/e2e/global-setup.ts
D-03-SCOPE-NAV: On mobile profiles (390px/412px), AppNav renders PhoneNav as <header> (not a nav landmark) — only BottomTabBar exposes <nav aria-label='Main navigation'>. No strict-mode collision in practice, but tap-target locators are scoped inside the nav landmark for robustness.
D-03-PHONENAV-TEXT: getByText('FamilySync', { exact: true }) required — the InstallPrompt renders 'Install FamilySync', which getByText('FamilySync') without exact:true matches as a substring, causing a strict-mode violation on WebKit.
D-03-SELF-VALIDATION: Self-validation proofs use addStyleTag + handle.evaluate(el => el.remove()) to inject and remove the defect style within the same test. No page.reload() needed — handle removal is synchronous and immediately clears the injected CSS.
duration_seconds completed_date tasks_completed files_changed
480 2026-06-11 2 2

Phase 07 Plan 03: layout.spec.ts Layout Assertions Summary

One-liner: layout.spec.ts enforcing UI-SPEC Rules 1-4 (tap targets ≥44/56px, no horizontal overflow, in-viewport, accessible names) on iPhone/WebKit + Pixel/Chromium with addStyleTag injected-defect proofs — 30 tests, 0 failures.

What Was Built

apps/pwa/e2e/layout.spec.ts with four describe blocks covering:

Rule 1/3/4 — BottomTabBar on /calendar (8 tests per profile)

  • Navigation landmark visible (Rule 4 — accessible name proof)
  • Calendar tab boundingBox ≥ 44×44px (Rule 1)
  • Lists tab boundingBox ≥ 44×44px (Rule 1)
  • BottomTabBar bottom edge ≤ viewport height (Rule 3 — in-viewport, safe-area-inset)
  • PhoneNav header "FamilySync" visible (Rule 3)
  • Settings button boundingBox ≥ 44×44px (getByRole('button', { name: /open settings/i }))
  • New Event FAB boundingBox ≥ 56×56px (Rule 1 — larger threshold)

Rule 1/3/4 — BottomTabBar on /lists (4 tests per profile)

  • Navigation landmark visible on /lists
  • Calendar tab ≥ 44×44px on /lists
  • Lists tab ≥ 44×44px on /lists
  • BottomTabBar in-viewport on /lists

Rule 2 — No horizontal overflow (2 tests per profile)

  • scrollWidth ≤ clientWidth on /calendar
  • scrollWidth ≤ clientWidth on /lists

Harness self-validation — injected defects (2 tests per profile)

  • Rule 1 proof: injects nav[aria-label="Main navigation"] a { height: 20px !important }, asserts height < 44, removes, asserts height ≥ 44 — proves boundingBox tracks rendered geometry
  • Rule 2 proof: injects body { width: 2000px !important }, asserts scrollWidth > clientWidth, removes, asserts scrollWidth ≤ clientWidth — proves overflow detection is live

Total: 30 tests (15 iphone, 15 pixel), 0 failures.

Verification Evidence

  • playwright test e2e/layout.spec.ts (both profiles): 30 passed
  • grep -E "https?://" apps/pwa/e2e/layout.spec.ts → empty (no absolute URLs)
  • grep -c addStyleTag apps/pwa/e2e/layout.spec.ts → 2 (both self-validation proofs present)
  • All locators use getByRole(..., { name }) or scoped-within-nav — no CSS selector fallback
  • No strict-mode "resolved to N elements" errors in either profile run

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] WebKit strict-mode violation: getByText('FamilySync') matched 2 elements

  • Found during: Task 1 first iphone run
  • Issue: getByText('FamilySync') without exact:true also matched the <div>Install FamilySync</div> text in the InstallPrompt component, causing a strict-mode violation on WebKit (where the install prompt was visible).
  • Fix: Changed to getByText('FamilySync', { exact: true }) — matches only the <span>FamilySync</span> in PhoneNav.
  • Files modified: apps/pwa/e2e/layout.spec.ts
  • Commit: 52e14a8

2. [Rule 3 - Blocking] global-setup.ts: MariaDB TIMESTAMP rejected ISO 8601 format

  • Found during: Task 1 execution — global-setup failed before any spec could run
  • Issue: futureStart.toISOString().replace(/\.\d+Z$/, 'Z') produces '2026-06-12T05:58:35Z' (with T separator), which MariaDB TIMESTAMP rejects with Incorrect datetime value. MariaDB requires 'YYYY-MM-DD HH:MM:SS' format.
  • Fix: Added .replace('T', ' ') and removed the trailing Z — produces '2026-06-12 05:58:35' which MariaDB TIMESTAMP accepts.
  • Files modified: apps/pwa/e2e/global-setup.ts
  • Commit: d3c6726 (fix(07-02))

3. [Observation] DesktopNav nav landmark absent on mobile profiles — no strict-mode risk

  • Found during: Component analysis before writing locators
  • Issue: The plan warned about strict-mode collision between BottomTabBar nav and DesktopNav nav, both named "Main navigation". In practice, on mobile profiles (390px/412px), AppNav renders PhoneNav (a <header>, not a nav), so DesktopNav's nav is absent. No collision occurs.
  • Fix: Still scoped tap-target locators inside getByRole('navigation', { name: 'Main navigation' }) for defensive robustness against any future layout change.
  • Files modified: None (design decision, no code change)

Known Stubs

None.

Threat Surface Scan

No new network endpoints, auth paths, or schema changes. layout.spec.ts is a test-only file. Threat mitigations from plan:

  • T-07-07 (injected style leakage): Each self-validation test removes the injected style via handle.evaluate(el => el.remove()) within the same test before completing. Styles are page-scoped and do not persist across navigations or test contexts.
  • T-07-08 (hardcoded host): grep -E "https?://" apps/pwa/e2e/layout.spec.ts returns empty — all navigation uses relative paths (/calendar, /lists) that resolve against playwright.config.ts baseURL.
  • T-07-12 (SW intercept): serviceWorkers: 'block' is set per-context in playwright.config.ts (Plan 01). Geometry assertions (boundingBox, scrollWidth) cannot be satisfied by a cached SW response, so the assertions remain authoritative even if the block were bypassed.

Self-Check: PASSED

  • apps/pwa/e2e/layout.spec.ts — exists (git show --stat 52e14a8)
  • apps/pwa/e2e/global-setup.ts — modified (fix commit d3c6726)
  • Fix commit d3c6726 — exists
  • Task commit 52e14a8 — exists
  • 30 tests passing on both profiles — verified by final run output