docs(07-03): complete layout.spec.ts plan
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
---
|
||||
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
|
||||
Reference in New Issue
Block a user