Files
familysync/.planning/phases/07-mobile-test-harness/07-03-SUMMARY.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

132 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 07-mobile-test-harness
plan: '03'
subsystem: test-harness
tags: [playwright, e2e, layout, tap-targets, overflow, accessibility, self-validation]
dependency_graph:
requires:
- "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)'
provides:
- '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)'
affects:
- '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)'
tech_stack:
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"
key_files:
created:
- apps/pwa/e2e/layout.spec.ts
modified:
- apps/pwa/e2e/global-setup.ts
decisions:
- "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.'
metrics:
duration_seconds: 480
completed_date: '2026-06-11'
tasks_completed: 2
files_changed: 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