Files
familysync/.planning/phases/14-desktop-e2e-coverage/14-01-PLAN.md
T

253 lines
18 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: 14-desktop-e2e-coverage
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- apps/pwa/playwright.config.ts
- apps/pwa/e2e/layout.spec.ts
- apps/pwa/e2e/calendar.spec.ts
- apps/pwa/e2e/lists.spec.ts
- apps/pwa/e2e/README.md
autonomous: true
requirements: [SC-1, SC-2, SC-3]
must_haves:
truths:
- "A `desktop` Playwright project (Desktop Chrome, 1280×720, no hasTouch) exists in playwright.config.ts (SC-1, D-06)"
- "`pnpm --filter @familysync/pwa test:e2e` exits 0 on all three projects: iphone, pixel, desktop (SC-2)"
- "The two mobile-only assertions (BottomTabBar safe-area in-viewport, 56×56 FAB geometry) are skipped on desktop only, not deleted, and still run on iphone/pixel (SC-2, D-01, D-02)"
- "Cross-cutting tests (no-overflow, nav-landmark visible, tap targets ≥44px, populated/empty/error states) run unchanged on desktop (SC-2, D-04)"
- "The desktop gate is blocking — same severity as mobile, no continue-on-error carve-out (SC-3, D-03)"
artifacts:
- path: "apps/pwa/playwright.config.ts"
provides: "desktop project entry mirroring iphone/pixel"
contains: "name: 'desktop'"
- path: "apps/pwa/e2e/layout.spec.ts"
provides: "desktop-skip guards on the two mobile-only tests + optional D-04 parity assertion"
contains: "testInfo.project.name === 'desktop'"
- path: "apps/pwa/e2e/README.md"
provides: "harness docs listing the desktop profile"
contains: "desktop"
key_links:
- from: "apps/pwa/playwright.config.ts"
to: "devices['Desktop Chrome']"
via: "spread into the desktop project use block"
pattern: "devices\\['Desktop Chrome'\\]"
- from: "apps/pwa/e2e/layout.spec.ts"
to: "testInfo.project.name"
via: "test.skip first-statement guard keyed on the desktop project name"
pattern: "test\\.skip\\(\\s*testInfo\\.project\\.name === 'desktop'"
---
<objective>
Add a third `desktop` Playwright project (Desktop Chrome, 1280×720, no `hasTouch`) to
`apps/pwa/playwright.config.ts` and make the existing Phase 7 mobile-authored e2e specs
green on that no-touch desktop viewport — so the Phase 8 CI regression gate validates the
desktop layout/flows in addition to the `iphone`/`pixel` mobile profiles.
The real work is the spec-compat pass, not CI plumbing. Phase 8's harness job already runs
`pnpm --filter @familysync/pwa test:e2e`, which executes every configured project; the new
`desktop` project is picked up automatically. `Desktop Chrome` uses the Chromium engine
already installed in CI (`playwright install --with-deps webkit chromium`), so no browser
install or CI plumbing change is required.
Purpose: Earn a desktop-layout regression gate that is real (blocking, deterministic) without
duplicating the shared cross-cutting assertions (D-01) or weakening the mobile assertions (D-02).
Output: an extended `playwright.config.ts`, desktop-skip-guarded `layout.spec.ts`, header-comment
updates across the spec files, and an updated `e2e/README.md`.
</objective>
<execution_context>
@$HOME/.claude/gsd-core/workflows/execute-plan.md
@$HOME/.claude/gsd-core/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/14-desktop-e2e-coverage/14-CONTEXT.md
@.planning/phases/14-desktop-e2e-coverage/14-PATTERNS.md
@apps/pwa/playwright.config.ts
@apps/pwa/e2e/layout.spec.ts
@apps/pwa/e2e/calendar.spec.ts
@apps/pwa/e2e/lists.spec.ts
@apps/pwa/e2e/README.md
@apps/pwa/e2e/global-setup.ts
</context>
<tasks>
<task type="execute">
<name>Task 1: Add the `desktop` project to playwright.config.ts (SC-1, D-06)</name>
<files>apps/pwa/playwright.config.ts</files>
<read_first>
- apps/pwa/playwright.config.ts — the file being modified; mirror the existing `iphone` (lines 36-46) and `pixel` (lines 47-54) project entries exactly.
- .planning/phases/14-desktop-e2e-coverage/14-PATTERNS.md §"apps/pwa/playwright.config.ts — add `desktop` project entry" (the exact new-entry shape and the four key invariants).
- .planning/phases/14-desktop-e2e-coverage/14-CONTEXT.md D-06 (single Desktop Chrome project, no Desktop WebKit, keep the default 1280×720 viewport).
</read_first>
<action>
Append a third project object to the `projects` array (after the `pixel` entry, before the closing `]`):
`name: 'desktop'` with `use: { ...devices['Desktop Chrome'], serviceWorkers: 'block' }`. Mirror the
iphone/pixel shape exactly. Do NOT add a `baseURL` override (inherited from the top-level `use` block,
line 29). Do NOT touch the shared `webServer` block (lines 57-64) or `globalSetup`. Keep `serviceWorkers:
'block'` — mandatory on every project per D-02/Pitfall 15. Do NOT add Desktop WebKit (D-06). Keep the
`devices['Desktop Chrome']` default viewport (1280×720, no `hasTouch`); no custom width override.
Update the file header jsdoc (lines 4-12) so the device-matrix line names all three profiles
(iphone / pixel / desktop) and the example run-commands block reflects a desktop example.
</action>
<verify>
<automated>cd apps/pwa && grep -q "name: 'desktop'" playwright.config.ts && grep -q "devices\['Desktop Chrome'\]" playwright.config.ts && grep -q "serviceWorkers: 'block'" playwright.config.ts && npx playwright test --list --project=desktop >/dev/null 2>&1 && echo OK</automated>
</verify>
<acceptance_criteria>
- `apps/pwa/playwright.config.ts` contains the literal `name: 'desktop'`.
- The desktop entry spreads `devices['Desktop Chrome']` and sets `serviceWorkers: 'block'`.
- No `baseURL` key appears inside the desktop project's `use` block (it is inherited).
- `npx playwright test --list --project=desktop` exits 0 (the project resolves and tests enumerate) — run from apps/pwa.
- The file header jsdoc lists three profiles, not two.
</acceptance_criteria>
<done>The `desktop` project exists, resolves via `--project=desktop`, and mirrors the mobile entries with no baseURL/webServer duplication.</done>
</task>
<task type="execute">
<name>Task 2: Desktop-skip the two mobile-only layout assertions + add D-04 parity (SC-2, D-01, D-02, D-04, D-05)</name>
<files>apps/pwa/e2e/layout.spec.ts</files>
<read_first>
- apps/pwa/e2e/layout.spec.ts — the file being modified; note the exact tests at line 62 ("BottomTabBar is fully in-viewport (Rule 3 — safe-area-inset)"), line 91 ("New Event FAB meets 56×56px touch-target minimum (Rule 1)"), and line 130 ("BottomTabBar is fully in-viewport on /lists (Rule 3)").
- apps/pwa/e2e/calendar.spec.ts lines 56-62 — the established inline `test.skip(condition, reason)` first-statement pattern (the live skip reference in this suite).
- .planning/phases/14-desktop-e2e-coverage/14-PATTERNS.md §"apps/pwa/e2e/layout.spec.ts — spec-compat pass" (which tests skip, which pass unchanged, the D-04 parity block, and the testInfo signature) + §"Shared Patterns — Project-name conditional skip".
- .planning/phases/14-desktop-e2e-coverage/14-CONTEXT.md D-01 (one spec set, no desktop.spec.ts), D-02 (scope/skip, never weaken or delete mobile assertions), D-04 (parity-adapted: desktop toolbar button ≥44px), D-05 (do NOT add "mobile chrome absent on desktop" negative assertions — `New Event` is a shared accessible name).
</read_first>
<action>
Add a desktop-only skip guard to exactly the two mobile-only tests, plus the FAB test, using the
first-statement form `test.skip(testInfo.project.name === 'desktop', '<factual reason>')` with the
`testInfo` fixture added as the second body argument (`async ({ page }, testInfo) => {`):
(a) the line-62 "BottomTabBar is fully in-viewport (Rule 3 — safe-area-inset)" test — reason: on desktop
the `Main navigation` landmark resolves to the DesktopNav sidebar, for which the safe-area-inset
bottom-edge assertion is semantically wrong;
(b) the line-130 "BottomTabBar is fully in-viewport on /lists (Rule 3)" test — same reason;
(c) the line-91 "New Event FAB meets 56×56px" test — reason: on desktop `New Event` resolves to the
toolbar button (CalendarShell.tsx:436-457), not the 56×56 FAB.
For D-04 parity, add a desktop toolbar-button ≥44px height assertion using
`page.getByRole('button', { name: 'New Event' })` and `boundingBox()`; place it so it runs ONLY on
desktop (e.g. a sibling test guarded by `test.skip(testInfo.project.name !== 'desktop', ...)`, or a
conditional desktop branch) — keep it from running on mobile where that name is the FAB.
Do NOT add any negative "mobile chrome absent" assertion (D-05). Do NOT modify the nav-landmark-visible,
44×44 tab-target, PhoneNav-header, settings-button, Rule 2 overflow, or harness self-validation tests —
per PATTERNS they pass unchanged on desktop (the landmark resolves to the sidebar, sidebar links have
minHeight:44px, the DesktopNav title matches `getByText('FamilySync', { exact: true })`). Do NOT weaken
or delete any existing mobile assertion (D-02). Update the file header jsdoc (lines 10-12) to list all
three profiles.
</action>
<verify>
<automated>cd apps/pwa && grep -c "testInfo.project.name === 'desktop'" e2e/layout.spec.ts | grep -qx 3 && grep -q "test.skip(\s*testInfo.project.name !== 'desktop'" e2e/layout.spec.ts && echo OK</automated>
</verify>
<acceptance_criteria>
- `e2e/layout.spec.ts` contains exactly three `test.skip(testInfo.project.name === 'desktop', ...)` guards (the two safe-area tests + the FAB test).
- A desktop parity assertion exists that checks `New Event` toolbar button height ≥ 44px and runs only on desktop (guarded by `testInfo.project.name !== 'desktop'` skip or equivalent desktop-only branch).
- No existing mobile assertion is removed or weakened: the file still contains the `>= 56` FAB-width/height checks and the `>= 44` tab-target checks (grep for `toBeGreaterThanOrEqual(56)` and `toBeGreaterThanOrEqual(44)` both still present).
- No `desktop.spec.ts` file is created (D-01).
- No negative "absent on desktop" assertion is added (D-05) — no new `toHaveCount(0)` / `not.toBeVisible()` keyed on the `New Event` name.
</acceptance_criteria>
<done>layout.spec.ts skips exactly the three mobile-geometry tests on desktop, adds a desktop ≥44px parity assertion, preserves every mobile assertion, and stays a single spec set.</done>
</task>
<task type="execute">
<name>Task 3: Update spec headers + README, then prove the full suite is green on all three projects (SC-2, SC-3, D-03)</name>
<files>apps/pwa/e2e/calendar.spec.ts, apps/pwa/e2e/lists.spec.ts, apps/pwa/e2e/README.md</files>
<read_first>
- apps/pwa/e2e/calendar.spec.ts header (lines 13-15) and apps/pwa/e2e/lists.spec.ts header (lines 16-19) — the "Runs on both device profiles" comment blocks to update; per PATTERNS no structural test changes are needed in these files.
- apps/pwa/e2e/README.md — preamble line 3 ("mobile-emulated (iPhone 14/WebKit + Pixel 7/Chromium)") and the run-commands block lines 37-47; mirror PATTERNS §"apps/pwa/e2e/README.md — docs update only".
- .gitea/workflows/ci.yml lines 241-296 — confirm the harness job runs `pnpm --filter @familysync/pwa test:e2e` (all projects) and installs the `chromium` engine that Desktop Chrome uses; assert no CI plumbing change is required beyond a cosmetic comment.
- .planning/phases/14-desktop-e2e-coverage/14-CONTEXT.md D-03 (desktop gate is blocking; fix flaky specs, never downgrade the gate) and 14-PATTERNS.md §calendar.spec.ts / §lists.spec.ts (all tests pass unchanged on desktop).
</read_first>
<action>
Update the header jsdoc comment block in `calendar.spec.ts` and `lists.spec.ts` to list all three
profiles (iphone / pixel / desktop) — comment-only, no test-body changes (PATTERNS confirms both files
pass unchanged on desktop). In `e2e/README.md`: update the preamble (line 3) to add "Desktop Chrome
(1280×720)" alongside the iPhone/Pixel emulation note, change the full-suite command comment (line 37) to
name all three profiles, and add a `--project=desktop` single-profile example. Confirm — by reading
`.gitea/workflows/ci.yml` — that the harness job already runs every project via `test:e2e` and installs
the `chromium` engine Desktop Chrome uses; the desktop gate is therefore blocking automatically (CI fails
the PR if any desktop test fails) with NO continue-on-error carve-out, satisfying D-03. Do NOT add
`continue-on-error`. If a desktop test flakes, fix the spec (proper waits/locators), never weaken the
assertion or downgrade the gate. Then run the FULL suite locally against all three projects and confirm
exit 0; the dev stack must be up with `DEV_AUTH_BYPASS=true` per e2e/README.md before running.
</action>
<verify>
<automated>set -a; source .env 2>/dev/null; set +a; export DEV_AUTH_BYPASS=true DB_HOST=127.0.0.1 DB_PORT=3306; pnpm --filter @familysync/pwa test:e2e</automated>
</verify>
<acceptance_criteria>
- `pnpm --filter @familysync/pwa test:e2e` exits 0 with all three projects (iphone, pixel, desktop) reported — no failures, no unexpected skips beyond the three desktop-guarded geometry tests + the SW-availability skip.
- `apps/pwa/e2e/README.md` line 3 mentions Desktop Chrome, and the run-commands block contains a `--project=desktop` example.
- `calendar.spec.ts` and `lists.spec.ts` header blocks each list the `desktop` profile.
- `.gitea/workflows/ci.yml` is unchanged except (optionally) the cosmetic step-name comment on line 255 — no `continue-on-error` added, no new browser install needed (chromium already installed for Desktop Chrome).
</acceptance_criteria>
<done>The full e2e suite is green on iphone + pixel + desktop, docs name the desktop profile, and the CI gate blocks on desktop with zero plumbing change.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
This phase adds a test-only Playwright `desktop` project and adjusts existing e2e specs/docs.
It introduces NO new runtime code, NO new request handlers, NO new data flows, and NO new
attack surface in the shipped application. No new trust boundary is created.
| Boundary | Description |
|----------|-------------|
| (none new) | All changes are confined to the e2e test harness (`apps/pwa/e2e/*`, `playwright.config.ts`) and docs; nothing in this phase is reachable in the production app. |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-14-01 | Elevation of Privilege | desktop Playwright project (`serviceWorkers`, `DEV_AUTH_BYPASS`) | mitigate | The `desktop` project MUST set `serviceWorkers: 'block'` (mirrors mobile profiles) and rely ONLY on the test-only `DEV_AUTH_BYPASS` path that is a no-op when `NODE_ENV === 'production'` (devBypass.ts) and absent from `docker-compose.yml`. This phase adds no real-auth bypass and no production-reachable code. |
| T-14-02 | Tampering | npm/pip/cargo installs | accept | No new packages installed. `Desktop Chrome` uses the already-installed Chromium engine (`playwright install --with-deps webkit chromium`); no dependency or lockfile change. Package Legitimacy Gate N/A. |
| T-14-SC | Tampering | supply chain | accept | No package-manager install tasks in this plan; no `[ASSUMED]`/`[SUS]` packages to gate. |
Honest scope statement: there is no genuine new application attack surface here. The only
test-infra concern worth recording is keeping `serviceWorkers: 'block'` on the desktop project
and never letting `DEV_AUTH_BYPASS` leak into a production-reachable path — both already enforced
by existing guards (T-14-01).
</threat_model>
<verification>
- `npx playwright test --list --project=desktop` resolves the new project (run from apps/pwa).
- `pnpm --filter @familysync/pwa test:e2e` exits 0 across iphone + pixel + desktop.
- `grep -c "testInfo.project.name === 'desktop'" apps/pwa/e2e/layout.spec.ts` == 3.
- Mobile assertions intact: `grep "toBeGreaterThanOrEqual(56)" apps/pwa/e2e/layout.spec.ts` still matches the FAB checks.
- No `desktop.spec.ts` exists under `apps/pwa/e2e/`.
- `.gitea/workflows/ci.yml` has no `continue-on-error` on the harness job (desktop gate is blocking, D-03).
- `apps/pwa/e2e/README.md` documents the desktop profile.
</verification>
<success_criteria>
- SC-1: A `desktop` project (Desktop Chrome, 1280×720, no `hasTouch`) exists in `playwright.config.ts`.
- SC-2: The existing e2e specs pass (or are explicitly, justifiably skipped) on the desktop profile — the two mobile-only geometry assertions are desktop-skipped, everything else runs; `pnpm test:e2e` is green on all three projects.
- SC-3: The desktop gate is blocking in CI (no `continue-on-error`), runs automatically via the unchanged `test:e2e` invocation, and the specs are deterministic (D-03).
- D-01..D-06 all satisfied: one spec set (D-01), no weakened/deleted mobile assertions (D-02), blocking gate (D-03), parity ≥44px desktop assertion (D-04), no negative "absent on desktop" assertion (D-05), single Desktop Chrome project no WebKit (D-06).
</success_criteria>
<artifacts_produced>
## Artifacts this phase produces
New symbols / identifiers created by this phase (none are application runtime code — all test-harness):
- **`desktop` Playwright project** — new entry in `apps/pwa/playwright.config.ts` `projects` array; `name: 'desktop'`, `use: { ...devices['Desktop Chrome'], serviceWorkers: 'block' }`. Referenceable via `--project=desktop`.
- **Desktop skip guards in `apps/pwa/e2e/layout.spec.ts`** — three `test.skip(testInfo.project.name === 'desktop', '<reason>')` first-statement guards on: "BottomTabBar is fully in-viewport (Rule 3 — safe-area-inset)" (line ~62), "BottomTabBar is fully in-viewport on /lists (Rule 3)" (line ~130), "New Event FAB meets 56×56px touch-target minimum (Rule 1)" (line ~91).
- **Desktop parity assertion (D-04)** — a new desktop-only check (test or branch) asserting the `New Event` toolbar button height ≥ 44px, guarded so it runs only on the `desktop` project.
- **Header-comment updates** — `layout.spec.ts`, `calendar.spec.ts`, `lists.spec.ts`, and `playwright.config.ts` jsdoc blocks updated to list all three profiles.
- **README update** — `apps/pwa/e2e/README.md` preamble + run-commands block document the `desktop` profile and a `--project=desktop` example.
No new helper module (e.g. no `isDesktop` helper) is introduced — the inline `test.skip` form is used per PATTERNS.md, consistent with the existing skip pattern. No `desktop.spec.ts` file is created (D-01).
</artifacts_produced>
<output>
Create `.planning/phases/14-desktop-e2e-coverage/14-01-SUMMARY.md` when done.
</output>