chore: archive v1.1 phase directories to milestones/v1.1-phases/
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a2890d1542
commit
c7955a46b9
@@ -0,0 +1,252 @@
|
||||
---
|
||||
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>
|
||||
@@ -0,0 +1,109 @@
|
||||
---
|
||||
phase: 14-desktop-e2e-coverage
|
||||
plan: "01"
|
||||
subsystem: e2e-test-harness
|
||||
tags: [playwright, e2e, desktop, testing]
|
||||
dependency_graph:
|
||||
requires: [07-mobile-test-harness, 08-gitea-ci]
|
||||
provides: [desktop-e2e-gate]
|
||||
affects: [ci-harness-job]
|
||||
tech_stack:
|
||||
added: []
|
||||
patterns: [playwright-project-matrix, test.skip-project-name-guard, testInfo-fixture, D-04-parity-assertion]
|
||||
key_files:
|
||||
created: []
|
||||
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
|
||||
- .gitea/workflows/ci.yml
|
||||
decisions:
|
||||
- "D-06: Single Desktop Chrome project only (no Desktop WebKit) — Apple member covered by iphone/WebKit profile"
|
||||
- "D-04 parity assertion: desktop-only test asserting New Event toolbar button height >=44px"
|
||||
- "inline test.skip(testInfo.project.name === 'desktop') pattern used — consistent with calendar.spec.ts SW-block skip"
|
||||
- "CI step-name comment updated cosmetically only — no plumbing change required"
|
||||
metrics:
|
||||
duration: "~5 minutes"
|
||||
completed: "2026-06-12"
|
||||
tasks_completed: 3
|
||||
files_modified: 6
|
||||
---
|
||||
|
||||
# Phase 14 Plan 01: Desktop E2E Coverage Summary
|
||||
|
||||
Added a third `desktop` Playwright project (Desktop Chrome, 1280x720, no hasTouch) to `playwright.config.ts` and made the existing Phase 7 mobile-authored e2e specs green on desktop by desktop-skipping three mobile-only geometry assertions and adding a D-04 parity assertion for the desktop toolbar button.
|
||||
|
||||
## Tasks Completed
|
||||
|
||||
| Task | Name | Commit | Files |
|
||||
|------|------|--------|-------|
|
||||
| 1 | Add desktop project to playwright.config.ts | d3bc696 | apps/pwa/playwright.config.ts |
|
||||
| 2 | Desktop-skip mobile-only assertions + D-04 parity | 2903599 | apps/pwa/e2e/layout.spec.ts |
|
||||
| 3 | Update spec headers + README, prove full suite green | bfc49d1 | apps/pwa/e2e/calendar.spec.ts, lists.spec.ts, README.md, .gitea/workflows/ci.yml |
|
||||
|
||||
## What Was Built
|
||||
|
||||
**SC-1:** A `desktop` Playwright project entry was appended to the `projects` array in `playwright.config.ts` using `devices['Desktop Chrome']` with `serviceWorkers: 'block'`. No `baseURL` override (inherited from top-level `use` block). Header jsdoc updated to list three profiles.
|
||||
|
||||
**SC-2:** `layout.spec.ts` received three `test.skip(testInfo.project.name === 'desktop', reason)` guards on the mobile-only geometry tests:
|
||||
- "BottomTabBar is fully in-viewport (Rule 3 — safe-area-inset)" — BottomTabBar returns null at >=768px; nav resolves to DesktopNav sidebar where safe-area-inset assertion is semantically wrong
|
||||
- "BottomTabBar is fully in-viewport on /lists (Rule 3)" — same reason
|
||||
- "New Event FAB meets 56x56px touch-target minimum (Rule 1)" — on desktop resolves to toolbar button (CalendarShell.tsx:436-457), not the 56px FAB
|
||||
|
||||
A D-04 parity test was added asserting `New Event` toolbar button height >=44px, guarded by `test.skip(testInfo.project.name !== 'desktop')` so it runs only on desktop.
|
||||
|
||||
All mobile assertions are preserved: `toBeGreaterThanOrEqual(56)` and `toBeGreaterThanOrEqual(44)` both still present.
|
||||
|
||||
`calendar.spec.ts` and `lists.spec.ts` required no structural changes — all tests pass unchanged on desktop (nav landmark resolves to DesktopNav sidebar, overflow/state assertions are viewport-agnostic).
|
||||
|
||||
**SC-3:** CI gate is blocking automatically. The harness job in `.gitea/workflows/ci.yml` runs `pnpm --filter @familysync/pwa test:e2e` which executes all configured projects. The `chromium` engine (used by Desktop Chrome) is already installed via `playwright install --with-deps webkit chromium`. No `continue-on-error` added. Step-name comment updated cosmetically only.
|
||||
|
||||
**Full suite result (local verification):** 85 passed, 5 skipped (3 desktop geometry guards + 2 non-desktop parity guards), 0 failed across iphone + pixel + desktop.
|
||||
|
||||
## Decisions Made
|
||||
|
||||
- **testInfo fixture pattern:** Used `test('name', async ({ page }, testInfo) => { test.skip(...); ... })` — consistent with the inline conditional skip in `calendar.spec.ts`
|
||||
- **D-04 parity as sibling test:** Added as a new test in the same `describe` block guarded by `!== 'desktop'`, rather than a conditional branch inside the FAB test, to keep the skip reason explicit and discoverable
|
||||
- **No `desktop.spec.ts`:** D-01 maintained — one spec set, no duplication
|
||||
- **CI cosmetic only:** Step-name comment updated to name all three profiles; no plumbing change required
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None — plan executed exactly as written.
|
||||
|
||||
## Verification Results
|
||||
|
||||
All success criteria met:
|
||||
|
||||
- `playwright.config.ts` contains `name: 'desktop'` with `devices['Desktop Chrome']` and `serviceWorkers: 'block'`
|
||||
- `npx playwright test --list --project=desktop` exits 0 (30 tests enumerated)
|
||||
- `grep -c "testInfo.project.name === 'desktop'" apps/pwa/e2e/layout.spec.ts` == 3
|
||||
- Mobile FAB assertions intact: `toBeGreaterThanOrEqual(56)` present
|
||||
- No `desktop.spec.ts` under `apps/pwa/e2e/`
|
||||
- `.gitea/workflows/ci.yml` has no `continue-on-error` on the harness job
|
||||
- `apps/pwa/e2e/README.md` documents Desktop Chrome and `--project=desktop`
|
||||
- Full suite: 85 passed, 5 skipped (expected), 0 failed
|
||||
|
||||
## Known Stubs
|
||||
|
||||
None.
|
||||
|
||||
## Threat Flags
|
||||
|
||||
No new application runtime code introduced. All changes confined to e2e test harness and docs. `serviceWorkers: 'block'` correctly set on the desktop project (T-14-01 satisfied). No new trust boundary created.
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
Files exist:
|
||||
- apps/pwa/playwright.config.ts: FOUND
|
||||
- apps/pwa/e2e/layout.spec.ts: FOUND
|
||||
- apps/pwa/e2e/calendar.spec.ts: FOUND
|
||||
- apps/pwa/e2e/lists.spec.ts: FOUND
|
||||
- apps/pwa/e2e/README.md: FOUND
|
||||
|
||||
Commits exist:
|
||||
- d3bc696 (Task 1): FOUND
|
||||
- 2903599 (Task 2): FOUND
|
||||
- bfc49d1 (Task 3): FOUND
|
||||
@@ -0,0 +1,122 @@
|
||||
---
|
||||
phase: 14-desktop-e2e-coverage
|
||||
verified: 2026-06-12T00:00:00Z
|
||||
status: passed
|
||||
score: 5/5 must-haves verified
|
||||
overrides_applied: 0
|
||||
---
|
||||
|
||||
# Phase 14: Desktop E2E Coverage Verification Report
|
||||
|
||||
**Phase Goal:** The Phase 8 regression gate exercises the desktop layout and flows, not just mobile. A `desktop` Playwright project (`devices['Desktop Chrome']`, no touch, wide viewport) is added to `apps/pwa/playwright.config.ts`, and the existing mobile-authored specs are reviewed/adjusted (or appropriately skipped) so `pnpm test:e2e` passes on a no-touch desktop viewport as well as the `iphone`/`pixel` profiles.
|
||||
**Verified:** 2026-06-12
|
||||
**Status:** passed
|
||||
**Re-verification:** No — initial verification
|
||||
|
||||
---
|
||||
|
||||
## Goal Achievement
|
||||
|
||||
### Observable Truths
|
||||
|
||||
| # | Truth | Status | Evidence |
|
||||
|---|-------|--------|----------|
|
||||
| 1 | A `desktop` Playwright project (Desktop Chrome, 1280×720, no hasTouch) exists in playwright.config.ts (SC-1, D-06) | VERIFIED | `name: 'desktop'` at line 58; `...devices['Desktop Chrome']` at line 60; `serviceWorkers: 'block'` at line 61; no `baseURL` inside the desktop project block (inherited from top-level `use`); comment explicitly states "no hasTouch (D-06)" |
|
||||
| 2 | `pnpm --filter @familysync/pwa test:e2e` exits 0 on all three projects: iphone, pixel, desktop (SC-2) | VERIFIED | CI run #32 (PR #10 → main): harness job ran `pnpm --filter @familysync/pwa test:e2e --reporter=list,html` with no `--project` filter — all three projects execute; SUMMARY records 85 passed / 5 skipped / 0 failed; commits d3bc696, 2903599, bfc49d1 are present in git history |
|
||||
| 3 | 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) | VERIFIED | `grep -c "testInfo.project.name === 'desktop'" layout.spec.ts` == 3 (three skip guards: line 66, 99, 161); `toBeGreaterThanOrEqual(56)` still present at lines 107–108; `toBeGreaterThanOrEqual(44)` still present at lines 50–51, 59–60, 94–95, 127, 147–148, 156–157, 248; no test body removed |
|
||||
| 4 | Cross-cutting tests (no-overflow, nav-landmark visible, tap targets ≥44px, populated/empty/error states) run unchanged on desktop (SC-2, D-04); D-04 parity (New Event toolbar button ≥44px) asserting only on desktop | VERIFIED | D-04 parity test at lines 114–128 guarded by `testInfo.project.name !== 'desktop'` (confirmed by grep count == 1); overflow, nav-landmark, calendar/lists populated/error/empty state tests have no project-name guards and run on all three profiles; `calendar.spec.ts` and `lists.spec.ts` contain no structural test changes |
|
||||
| 5 | The desktop gate is blocking — no `continue-on-error` carve-out (SC-3, D-03) | VERIFIED | `grep "continue-on-error" .gitea/workflows/ci.yml` returns nothing; harness step runs `pnpm --filter @familysync/pwa test:e2e` (all projects) with no isolation or continue-on-error; Chromium engine already installed via `playwright install --with-deps webkit chromium` (line 242); CI step name updated to name all three profiles (line 255, cosmetic only) |
|
||||
|
||||
**Score:** 5/5 truths verified
|
||||
|
||||
---
|
||||
|
||||
### Required Artifacts
|
||||
|
||||
| Artifact | Expected | Status | Details |
|
||||
|----------|----------|--------|---------|
|
||||
| `apps/pwa/playwright.config.ts` | desktop project entry mirroring iphone/pixel | VERIFIED | `name: 'desktop'`, `...devices['Desktop Chrome']`, `serviceWorkers: 'block'`; no baseURL in project block; header jsdoc lists three profiles |
|
||||
| `apps/pwa/e2e/layout.spec.ts` | desktop-skip guards on two mobile-only tests + D-04 parity assertion | VERIFIED | Exactly 3 `test.skip(testInfo.project.name === 'desktop', ...)` guards; 1 `test.skip(testInfo.project.name !== 'desktop', ...)` parity guard; all prior mobile assertions intact |
|
||||
| `apps/pwa/e2e/README.md` | harness docs listing the desktop profile | VERIFIED | Line 1 preamble mentions "Desktop Chrome (1280×720)"; `--project=desktop` example at line 42; run commands block names all three profiles |
|
||||
|
||||
---
|
||||
|
||||
### Key Link Verification
|
||||
|
||||
| From | To | Via | Status | Details |
|
||||
|------|-----|-----|--------|---------|
|
||||
| `apps/pwa/playwright.config.ts` | `devices['Desktop Chrome']` | spread into desktop project `use` block | VERIFIED | Line 60: `...devices['Desktop Chrome']` |
|
||||
| `apps/pwa/e2e/layout.spec.ts` | `testInfo.project.name` | `test.skip` first-statement guard keyed on `=== 'desktop'` | VERIFIED | Three guards at lines 66–69, 99–102, 161–164; all use first-statement form inside `async ({ page }, testInfo)` |
|
||||
| CI harness | all three projects | `pnpm --filter @familysync/pwa test:e2e` with no `--project` filter | VERIFIED | `.gitea/workflows/ci.yml` line 293; picks up `desktop` automatically |
|
||||
|
||||
---
|
||||
|
||||
### Data-Flow Trace (Level 4)
|
||||
|
||||
Not applicable. Phase produces only test-harness artifacts — no runtime components rendering dynamic data.
|
||||
|
||||
---
|
||||
|
||||
### Behavioral Spot-Checks
|
||||
|
||||
| Behavior | Command | Result | Status |
|
||||
|----------|---------|--------|--------|
|
||||
| desktop project resolves in Playwright | `grep -c "name: 'desktop'" apps/pwa/playwright.config.ts` | 1 | PASS |
|
||||
| Exactly 3 desktop skip guards | `grep -c "testInfo.project.name === 'desktop'" apps/pwa/e2e/layout.spec.ts` | 3 | PASS |
|
||||
| D-04 parity guard present | `grep -c "testInfo.project.name !== 'desktop'" apps/pwa/e2e/layout.spec.ts` | 1 | PASS |
|
||||
| Mobile 56px FAB assertions intact | `grep "toBeGreaterThanOrEqual(56)" apps/pwa/e2e/layout.spec.ts` | 2 matches (lines 107–108) | PASS |
|
||||
| No `desktop.spec.ts` created (D-01) | `ls apps/pwa/e2e/desktop.spec.ts` | NOT FOUND | PASS |
|
||||
| No `continue-on-error` in CI harness | `grep "continue-on-error" .gitea/workflows/ci.yml` | no output | PASS |
|
||||
| Commits from SUMMARY exist | `git log --oneline d3bc696 2903599 bfc49d1` | all three found in history | PASS |
|
||||
|
||||
---
|
||||
|
||||
### Probe Execution
|
||||
|
||||
No probes declared in PLAN or found at `scripts/*/tests/probe-*.sh`. Skipped.
|
||||
|
||||
---
|
||||
|
||||
### Requirements Coverage
|
||||
|
||||
| Requirement | Description | Status | Evidence |
|
||||
|-------------|-------------|--------|---------|
|
||||
| SC-1 | `desktop` project (Desktop Chrome, 1280×720, no `hasTouch`) exists in `playwright.config.ts` | SATISFIED | Verified at `playwright.config.ts:56–63` |
|
||||
| SC-2 | Existing e2e specs pass (or justifiably skipped) on desktop; `pnpm test:e2e` green on all three projects | SATISFIED | 3 mobile-geometry tests desktop-skipped with factual reasons; D-04 parity added; CI run #32 green |
|
||||
| SC-3 | Desktop gate is blocking in CI (no `continue-on-error`), runs automatically, specs deterministic | SATISFIED | No `continue-on-error` in CI yml; harness runs all projects via single `test:e2e` invocation with no filter |
|
||||
|
||||
---
|
||||
|
||||
### Anti-Patterns Found
|
||||
|
||||
| File | Pattern | Severity | Impact |
|
||||
|------|---------|----------|--------|
|
||||
| `apps/pwa/e2e/layout.spec.ts:83–96` | `PhoneNav header is visible` and `PhoneNav settings button` tests run unguarded on desktop, silently re-targeting DesktopNav elements (WR-01 from code review) | INFO | Tests pass by coincidence on desktop; a PhoneNav-specific regression would not be caught on the desktop profile. Not a blocker — both tests still pass and the phase goal (desktop gate green) is achieved. Flagged in 14-REVIEW.md. |
|
||||
| `apps/pwa/e2e/calendar.spec.ts:16` / `lists.spec.ts:19` | Header comment claims "all tests pass unchanged" without qualifying the month-grid/DesktopNav view divergence on desktop (WR-02, WR-03 from code review) | INFO | Comment is misleading but not a correctness failure. No test is broken. |
|
||||
|
||||
No TBD / FIXME / XXX markers found in any phase-modified file.
|
||||
|
||||
---
|
||||
|
||||
### Human Verification Required
|
||||
|
||||
None. All must-haves are verifiable programmatically. CI evidence (PR #10, run #32) confirms full-suite green. No visual, real-time, or device-only checks are required to confirm the phase goal.
|
||||
|
||||
---
|
||||
|
||||
## Gaps Summary
|
||||
|
||||
No gaps. All five must-have truths are verified by direct codebase inspection:
|
||||
|
||||
- `playwright.config.ts` contains the correct `desktop` project entry with all required properties.
|
||||
- `layout.spec.ts` has exactly the right guard count (3 desktop-skip, 1 desktop-only parity) and all prior mobile assertions are intact.
|
||||
- No `desktop.spec.ts` was created.
|
||||
- The CI harness runs all three projects without `continue-on-error`.
|
||||
- README and spec headers document the desktop profile.
|
||||
|
||||
The code review (14-REVIEW.md) identified three warnings (WR-01 misleading test names, WR-02 month-grid claim, WR-03 comment accuracy) and three info items (IN-01 serviceWorkers hoisting, IN-02 missing auth-wait in parity test, IN-03 implicit viewport assumption). None of these affect the phase goal or the correctness of the desktop gate. They are documentation/quality items for a follow-up if desired.
|
||||
|
||||
---
|
||||
|
||||
_Verified: 2026-06-12_
|
||||
_Verifier: Claude (gsd-verifier)_
|
||||
@@ -0,0 +1,151 @@
|
||||
# Phase 14: Desktop E2E Coverage - Context
|
||||
|
||||
**Gathered:** 2026-06-12
|
||||
**Status:** Ready for planning
|
||||
|
||||
<domain>
|
||||
## Phase Boundary
|
||||
|
||||
Add a `desktop` Playwright project (Desktop Chrome, wide viewport, no `hasTouch`) to
|
||||
`apps/pwa/playwright.config.ts` and make the existing Phase 7 mobile-authored e2e specs
|
||||
pass on a 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 already runs
|
||||
`pnpm test:e2e` and will pick up the new project automatically. No new test *capabilities*
|
||||
are added; this is desktop-profile coverage of the layout/state bars Phase 7 already asserts.
|
||||
|
||||
**In scope:** desktop project config; per-spec review so the suite is green on desktop;
|
||||
desktop-equivalent assertions where the mobile layout diverges.
|
||||
**Out of scope:** new flows/features; live event-create against CalDAV (dev user 1 still has
|
||||
no calendar credential — carried from Phase 7); Desktop WebKit; prod-SW / iOS-standalone testing.
|
||||
</domain>
|
||||
|
||||
<decisions>
|
||||
## Implementation Decisions
|
||||
|
||||
### Spec-Compat Strategy
|
||||
- **D-01:** Keep **one set of spec files** (no separate `desktop.spec.ts`). Gate the genuinely
|
||||
mobile-only tests with `test.skip()` keyed on project name (`desktop`), and add desktop-equivalent
|
||||
assertions where the layout diverges. Cross-cutting tests (no-overflow, populated/empty/error
|
||||
states) stay shared and run on all three profiles. Rationale: one source of truth, least
|
||||
duplication, avoids drift between mobile and desktop copies of the same cross-cutting assertions.
|
||||
- **D-02:** Prefer scoping/skip over deletion — do not weaken or remove the existing mobile
|
||||
assertions to make desktop pass. The mobile profiles must remain exactly as authored in Phase 7.
|
||||
|
||||
### Desktop Gate Severity
|
||||
- **D-03:** Desktop-profile failures are **blocking** — same severity as `iphone`/`pixel`.
|
||||
A desktop regression must not be able to land on `main`. No `continue-on-error` / advisory
|
||||
carve-out. (Resolves ROADMAP Phase 14 Success Criterion 3, which deferred blocking-vs-advisory
|
||||
to planning.) Implication: the desktop specs must be deterministic before this merges —
|
||||
if they flake, fix the spec, don't downgrade the gate.
|
||||
|
||||
### Desktop Assertion Depth
|
||||
- **D-04:** **Parity-adapted** coverage. On desktop, positively assert the desktop equivalents:
|
||||
DesktopNav sidebar (`nav[aria-label="Main navigation"]`) visible, nav tap targets, no horizontal
|
||||
overflow, populated/empty/error states render (calendar grid, seeded event, lists card, empty
|
||||
state, error heading + Retry). Skip only the assertions that have no desktop analog.
|
||||
- **D-05:** Do **not** add "mobile chrome is absent on desktop" negative assertions (the rejected
|
||||
"full divergence" option). Reason grounded in code: the `New Event` accessible name is **not**
|
||||
mobile-only — `CalendarShell.tsx:424` renders a desktop-only toolbar "New Event" button sharing
|
||||
the same name as the phone FAB. Asserting its absence would be wrong. Only the **56×56 FAB
|
||||
geometry** check is mobile-specific.
|
||||
|
||||
### Desktop Profile / Viewport
|
||||
- **D-06:** Single desktop project using `devices['Desktop Chrome']` (1280×720), **no Desktop WebKit**.
|
||||
The Apple member is already covered on mobile Safari via the `iphone`/WebKit profile; Desktop
|
||||
Chrome is sufficient for the shared/wall-browser use case. Keep the default viewport — no custom
|
||||
width override unless planning finds the sidebar layout needs more horizontal room than 1280 gives.
|
||||
|
||||
### Claude's Discretion
|
||||
- Exact mechanism for the skip gate (e.g. `test.skip(testInfo.project.name === 'desktop', ...)` vs.
|
||||
`test.describe` blocks split by a `isDesktop` helper vs. project-level `grep`/`grepInvert`) — pick
|
||||
the cleanest that keeps the shared cross-cutting tests un-duplicated. Planner/researcher to choose.
|
||||
- Whether the desktop "New Event" toolbar button gets its own ≥44px tap-target assertion as the
|
||||
desktop parity equivalent of the FAB's 56×56 check (recommended, but optional).
|
||||
</decisions>
|
||||
|
||||
<canonical_refs>
|
||||
## Canonical References
|
||||
|
||||
**Downstream agents MUST read these before planning or implementing.**
|
||||
|
||||
### The harness being extended (Phase 7)
|
||||
- `apps/pwa/playwright.config.ts` — the two-profile matrix to extend with a third `desktop` project;
|
||||
documents the carried-forward auth/SW/baseURL/webServer decisions (D-01..D-10 in its header).
|
||||
- `apps/pwa/e2e/layout.spec.ts` — the spec with the most mobile-only assumptions (BottomTabBar
|
||||
safe-area/in-viewport, 56×56 FAB, PhoneNav chrome, tap targets). Primary compat-pass target.
|
||||
- `apps/pwa/e2e/calendar.spec.ts` — auth-bypass precondition, no-SW precondition, populated/error states.
|
||||
- `apps/pwa/e2e/lists.spec.ts` — populated/empty states.
|
||||
- `apps/pwa/e2e/global-setup.ts` — `/health` readiness poll + deterministic DB reset/seed (shared by
|
||||
all profiles, unchanged by this phase).
|
||||
- `apps/pwa/e2e/README.md` — harness run/guardrail docs; update for the new profile.
|
||||
|
||||
### Phase context / roadmap
|
||||
- `.planning/ROADMAP.md` §"Phase 14: Desktop E2E Coverage" — goal, 3 success criteria, pitfalls.
|
||||
- `.planning/phases/07-mobile-test-harness/07-CONTEXT.md` — original harness decisions to carry forward
|
||||
(DEV_AUTH_BYPASS not storageState, `serviceWorkers: 'block'`, env baseURL, Vite-only webServer).
|
||||
- `.planning/phases/08-gitea-ci/08-CONTEXT.md` — how CI runs `pnpm test:e2e`; confirms desktop project
|
||||
is picked up automatically (only watch desktop-profile runtime/wait in CI).
|
||||
|
||||
### CI entry point (no plumbing change expected, but verify)
|
||||
- `.gitea/workflows/ci.yml` — the harness job that runs the Playwright specs; confirm the new desktop
|
||||
project runs and gates without a config change beyond runtime/wait budget.
|
||||
</canonical_refs>
|
||||
|
||||
<code_context>
|
||||
## Existing Code Insights
|
||||
|
||||
### Reusable Assets
|
||||
- **Existing `playwright.config.ts` project array** — add a third `{ name: 'desktop', use: {
|
||||
...devices['Desktop Chrome'], serviceWorkers: 'block' } }` entry. `serviceWorkers: 'block'`,
|
||||
env baseURL, and the Vite `webServer` are already shared across projects.
|
||||
- **`global-setup.ts`** — DB reset/seed is profile-agnostic; reused unchanged for desktop.
|
||||
|
||||
### Established Patterns
|
||||
- **Breakpoint = 767/768px**, driven by `window.matchMedia('(max-width: 767px)')` in
|
||||
`AppNav.tsx:32`, `BottomTabBar.tsx:24`, `CalendarShell.tsx:75`. Desktop Chrome at 1280px is
|
||||
unambiguously in the desktop branch.
|
||||
- **`getByRole('navigation', { name: 'Main navigation' })`** resolves to the **BottomTabBar** on
|
||||
mobile but the **DesktopNav sidebar** on desktop — same locator, different element, both valid.
|
||||
- **No strict-mode collision on desktop:** `BottomTabBar.tsx:53-57` returns `null` at ≥768px, so the
|
||||
sidebar is the *sole* `Main navigation` landmark on desktop. Most nav-visibility/tap-target tests
|
||||
pass as-authored, just resolving to the sidebar.
|
||||
|
||||
### Integration Points — desktop divergences the compat pass must handle
|
||||
- **BottomTabBar safe-area / "fully in-viewport" tests** (`layout.spec.ts` describe blocks):
|
||||
bottom bar is `null` on desktop → **skip on desktop**.
|
||||
- **"New Event FAB meets 56×56px"** (`layout.spec.ts:91`): on desktop the `New Event` name resolves
|
||||
to the toolbar button (`CalendarShell.tsx:424`), which is not 56×56 → **skip the FAB-geometry test
|
||||
on desktop**; optionally assert the desktop toolbar button's ≥44px target instead (D-04 parity).
|
||||
- **"PhoneNav header is visible"** (`layout.spec.ts:76`): `getByText('FamilySync', {exact:true})` also
|
||||
matches the DesktopNav title (`AppNav.tsx:181`) → likely passes on desktop; verify no second match.
|
||||
- **Settings button** (`/open settings/i`): present on both PhoneNav and DesktopNav → resolves on both.
|
||||
- **Tap-target tests scoped to the nav landmark**: desktop sidebar links have `minHeight: 44px`
|
||||
(`AppNav.tsx:151`) → pass on desktop.
|
||||
</code_context>
|
||||
|
||||
<specifics>
|
||||
## Specific Ideas
|
||||
|
||||
- The gate's whole point is to be real: desktop must be **blocking**, and the specs must be made
|
||||
deterministic to earn that (D-03). "Make it green by weakening the assertion" is explicitly rejected.
|
||||
- Mirror Phase 7's self-validation ethos where cheap: a desktop run should fail loudly if the desktop
|
||||
layout actually regresses, not silently pass because everything was skipped.
|
||||
</specifics>
|
||||
|
||||
<deferred>
|
||||
## Deferred Ideas
|
||||
|
||||
- **Desktop WebKit (Desktop Safari) profile** — considered and declined for this phase (D-06).
|
||||
Revisit only if a desktop-Safari-specific rendering bug is ever reported.
|
||||
- **Live desktop event-create round-trip** — still blocked by dev user 1 having no CalDAV calendar
|
||||
(carried from Phase 7); out of scope here, same as on mobile.
|
||||
|
||||
None of the above are blockers — discussion stayed within phase scope.
|
||||
</deferred>
|
||||
|
||||
---
|
||||
|
||||
*Phase: 14-Desktop E2E Coverage*
|
||||
*Context gathered: 2026-06-12*
|
||||
@@ -0,0 +1,69 @@
|
||||
# Phase 14: Desktop E2E Coverage - Discussion Log
|
||||
|
||||
> **Audit trail only.** Do not use as input to planning, research, or execution agents.
|
||||
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
|
||||
|
||||
**Date:** 2026-06-12
|
||||
**Phase:** 14-Desktop E2E Coverage
|
||||
**Areas discussed:** Spec-compat strategy, Desktop gate severity, Desktop assertion depth, Desktop profile/viewport
|
||||
|
||||
---
|
||||
|
||||
## Spec-Compat Strategy
|
||||
|
||||
| Option | Description | Selected |
|
||||
|--------|-------------|----------|
|
||||
| Conditional skips, shared specs | One set of spec files; gate mobile-only tests with `test.skip(project==='desktop')`, assert desktop equivalents on divergence; cross-cutting tests shared. | ✓ |
|
||||
| Separate desktop spec files | New `desktop.spec.ts`; mobile specs project-filtered. Clear separation, duplicates cross-cutting assertions. | |
|
||||
| Locator-only, no skips | Viewport-agnostic locators, no skips. Impossible where elements don't exist on desktop (FAB, bottom bar). | |
|
||||
|
||||
**User's choice:** Conditional skips, shared specs
|
||||
**Notes:** One source of truth, least duplication. Mobile profiles stay exactly as authored in Phase 7.
|
||||
|
||||
---
|
||||
|
||||
## Desktop Gate Severity
|
||||
|
||||
| Option | Description | Selected |
|
||||
|--------|-------------|----------|
|
||||
| Blocking (same as mobile) | Desktop failures block the merge exactly like iphone/pixel. | ✓ |
|
||||
| Advisory (non-blocking) | Desktop runs/reports but doesn't block merge (continue-on-error). | |
|
||||
|
||||
**User's choice:** Blocking (same as mobile)
|
||||
**Notes:** Resolves ROADMAP Success Criterion 3 (deferred to planning). Implies specs must be deterministic before merge — fix flakes, don't downgrade the gate.
|
||||
|
||||
---
|
||||
|
||||
## Desktop Assertion Depth
|
||||
|
||||
| Option | Description | Selected |
|
||||
|--------|-------------|----------|
|
||||
| Parity-adapted | Assert desktop equivalents (sidebar nav, no overflow, populated/empty/error states, nav tap targets); skip FAB/bottom-bar. | ✓ |
|
||||
| Render-only smoke | Just no-overflow + states render; no desktop-chrome assertions. | |
|
||||
| Full divergence assertions | Also assert mobile chrome absent + desktop-only elements present. | |
|
||||
|
||||
**User's choice:** Parity-adapted
|
||||
**Notes:** Full-divergence rejected on code grounds — `New Event` is not mobile-only (desktop toolbar button shares the name, `CalendarShell.tsx:424`); only the 56×56 FAB geometry is mobile-specific.
|
||||
|
||||
---
|
||||
|
||||
## Desktop Profile / Viewport
|
||||
|
||||
| Option | Description | Selected |
|
||||
|--------|-------------|----------|
|
||||
| Desktop Chrome default, no WebKit | `devices['Desktop Chrome']` (1280×720), single desktop project. | ✓ |
|
||||
| Desktop Chrome at wider width | Override to ~1440×900 for more sidebar/grid real estate. | |
|
||||
| Add Desktop WebKit too | Desktop Chrome + Desktop Safari/WebKit. | |
|
||||
|
||||
**User's choice:** Desktop Chrome default, no WebKit
|
||||
**Notes:** Apple member already covered on mobile Safari (iphone/WebKit). Desktop Chrome sufficient for shared/wall browser.
|
||||
|
||||
## Claude's Discretion
|
||||
|
||||
- Exact skip-gate mechanism (`test.skip(testInfo.project.name === 'desktop')` vs. `isDesktop` describe split vs. project `grep`/`grepInvert`) — pick the cleanest that avoids duplicating cross-cutting tests.
|
||||
- Whether the desktop "New Event" toolbar button gets its own ≥44px tap-target assertion as the desktop parity equivalent of the FAB check (recommended, optional).
|
||||
|
||||
## Deferred Ideas
|
||||
|
||||
- Desktop WebKit profile — declined for this phase; revisit only on a desktop-Safari-specific bug.
|
||||
- Live desktop event-create round-trip — still blocked by dev user 1 having no CalDAV calendar (carried from Phase 7).
|
||||
@@ -0,0 +1,252 @@
|
||||
# Phase 14: Desktop E2E Coverage - Pattern Map
|
||||
|
||||
**Mapped:** 2026-06-12
|
||||
**Files analyzed:** 5 modified files
|
||||
**Analogs found:** 5 / 5 (all files are self-analogs — modifications to existing code)
|
||||
|
||||
---
|
||||
|
||||
## File Classification
|
||||
|
||||
| Modified File | Role | Data Flow | Closest Analog | Match Quality |
|
||||
|---|---|---|---|---|
|
||||
| `apps/pwa/playwright.config.ts` | config | N/A | itself (iphone/pixel project entries) | exact |
|
||||
| `apps/pwa/e2e/layout.spec.ts` | test | request-response | itself (existing describe/skip patterns) | exact |
|
||||
| `apps/pwa/e2e/calendar.spec.ts` | test | request-response | itself (test.skip at line 59 is live reference) | exact |
|
||||
| `apps/pwa/e2e/lists.spec.ts` | test | request-response | itself (all tests pass unchanged on desktop) | exact |
|
||||
| `apps/pwa/e2e/README.md` | docs | N/A | itself | exact |
|
||||
|
||||
---
|
||||
|
||||
## Pattern Assignments
|
||||
|
||||
### `apps/pwa/playwright.config.ts` — add `desktop` project entry
|
||||
|
||||
**Analog:** existing `iphone` and `pixel` project entries (lines 35–54)
|
||||
|
||||
**Current `projects` array** (lines 35–55 — mirror this shape exactly for the third entry):
|
||||
```typescript
|
||||
projects: [
|
||||
{
|
||||
// iPhone 14: 390×844 viewport, WebKit engine, Mobile Safari UA, hasTouch: true
|
||||
name: 'iphone',
|
||||
use: {
|
||||
...devices['iPhone 14'],
|
||||
serviceWorkers: 'block',
|
||||
},
|
||||
},
|
||||
{
|
||||
// Pixel 7: 412×915 viewport, Chromium engine, Chrome Android UA, hasTouch: true
|
||||
name: 'pixel',
|
||||
use: {
|
||||
...devices['Pixel 7'],
|
||||
serviceWorkers: 'block',
|
||||
},
|
||||
},
|
||||
],
|
||||
```
|
||||
|
||||
**New `desktop` entry to add** (append after the `pixel` entry, before the closing `]`):
|
||||
```typescript
|
||||
{
|
||||
// Desktop Chrome: 1280×720 viewport, Chromium engine, no hasTouch (D-06)
|
||||
name: 'desktop',
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
serviceWorkers: 'block',
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
Key invariants to preserve (from file header comments):
|
||||
- `serviceWorkers: 'block'` — same as both mobile profiles; mandatory on every project (D-02/Pitfall 15)
|
||||
- `devices['Desktop Chrome']` — provides 1280×720, no `hasTouch`, desktop UA, Chromium engine
|
||||
- No `baseURL` override in the project entry — it is inherited from the top-level `use` block (line 29)
|
||||
- No `webServer` change — shared block at lines 57–64 applies to all projects automatically
|
||||
|
||||
---
|
||||
|
||||
### `apps/pwa/e2e/layout.spec.ts` — spec-compat pass (primary target)
|
||||
|
||||
**Self-analog** — read carefully; two `test.describe` blocks need desktop skip guards, one test needs a desktop parity assertion, one test passes unchanged.
|
||||
|
||||
#### Skip mechanism in the existing codebase
|
||||
|
||||
The one existing `test.skip` in the suite (from `calendar.spec.ts` lines 59–62) uses the inline conditional form:
|
||||
```typescript
|
||||
test.skip(
|
||||
!swAvailable,
|
||||
'navigator.serviceWorker is unavailable in this context (e.g. WebKit over http://localhost) — block is unobservable here',
|
||||
);
|
||||
```
|
||||
|
||||
This form — `test.skip(condition, reason)` called at the top of the test body — is the established pattern. Do **not** use `test.skip(testInfo.project.name === 'desktop', ...)` with a `testInfo` parameter; the simpler form without `testInfo` is consistent with what already exists.
|
||||
|
||||
For project-name gating the correct signature requires the `testInfo` fixture:
|
||||
```typescript
|
||||
test('...', async ({ page }, testInfo) => {
|
||||
test.skip(testInfo.project.name === 'desktop', 'reason string');
|
||||
// ...rest of test
|
||||
});
|
||||
```
|
||||
|
||||
Use this signature for every mobile-only test that needs a desktop skip. Keep the reason string explicit and factual (consistent with the SW-block skip's style).
|
||||
|
||||
#### Tests in `layout.spec.ts` that need a desktop skip
|
||||
|
||||
**Test at line 62 — "BottomTabBar is fully in-viewport (Rule 3 — safe-area-inset)":**
|
||||
```typescript
|
||||
test('BottomTabBar is fully in-viewport (Rule 3 — safe-area-inset)', async ({ page }) => {
|
||||
// The bar uses env(safe-area-inset-bottom, 0px). In emulation there is no
|
||||
// safe-area-inset, so the bar's bottom edge must be ≤ viewport height.
|
||||
const nav = page.getByRole('navigation', { name: 'Main navigation' });
|
||||
await expect(nav).toBeVisible();
|
||||
const box = await nav.boundingBox();
|
||||
// ...
|
||||
```
|
||||
On desktop `BottomTabBar.tsx:53-57` returns `null` at ≥768px, so this `nav` would resolve to DesktopNav sidebar and the safe-area-inset assertion is semantically wrong for a sidebar. **Skip on desktop.**
|
||||
|
||||
**Test at line 130 — "BottomTabBar is fully in-viewport on /lists (Rule 3)"** — same reason. **Skip on desktop.**
|
||||
|
||||
**Test at line 91 — "New Event FAB meets 56×56px touch-target minimum (Rule 1)":**
|
||||
```typescript
|
||||
test('New Event FAB meets 56×56px touch-target minimum (Rule 1)', async ({ page }) => {
|
||||
// Phone-only FAB — aria-label="New Event", fixed 56×56px (CalendarShell.tsx)
|
||||
const fab = page.getByRole('button', { name: 'New Event' });
|
||||
const box = await fab.boundingBox();
|
||||
expect(box, 'New Event FAB bounding box must not be null').not.toBeNull();
|
||||
expect(box!.width, 'New Event FAB width ≥ 56px').toBeGreaterThanOrEqual(56);
|
||||
expect(box!.height, 'New Event FAB height ≥ 56px').toBeGreaterThanOrEqual(56);
|
||||
});
|
||||
```
|
||||
On desktop `getByRole('button', { name: 'New Event' })` resolves to the **desktop toolbar button** (CalendarShell.tsx:436–457), not the 56×56px FAB. The toolbar button has `minHeight: 44px` but no 56px constraint. **Skip the FAB-geometry (56×56) assertion on desktop.** Per D-04, add a desktop parity block asserting ≥44px instead.
|
||||
|
||||
Desktop parity assertion to add (new test or a conditional branch in the same test):
|
||||
```typescript
|
||||
// Desktop parity: toolbar "New Event" button meets ≥44px minimum (D-04)
|
||||
// CalendarShell.tsx:443 sets minHeight:'44px' on the desktop toolbar button.
|
||||
const toolbarBtn = page.getByRole('button', { name: 'New Event' });
|
||||
const box = await toolbarBtn.boundingBox();
|
||||
expect(box, 'New Event toolbar button bounding box must not be null').not.toBeNull();
|
||||
expect(box!.height, 'New Event toolbar button height ≥ 44px (Rule 1 desktop parity)').toBeGreaterThanOrEqual(44);
|
||||
```
|
||||
|
||||
#### Tests that pass unchanged on desktop (no modification needed)
|
||||
|
||||
- **"BottomTabBar navigation landmark is visible" (lines 35–41 and 108–110):** `getByRole('navigation', { name: 'Main navigation' })` resolves to DesktopNav sidebar on desktop (sole nav landmark at ≥768px). Passes as-authored.
|
||||
- **"Calendar tab meets 44×44px" / "Lists tab meets 44×44px" (lines 43–60, 112–128):** Scoped to the `Main navigation` landmark; DesktopNav sidebar links have `minHeight: 44px` (AppNav.tsx:151). Pass as-authored.
|
||||
- **"PhoneNav header is visible" (line 76):** `getByText('FamilySync', { exact: true })` matches the DesktopNav title text (AppNav.tsx:181). Passes on desktop. Verify no strict-mode collision (CONTEXT.md confirms the PhoneNav `<header>` returns null at ≥768px, leaving the DesktopNav title as the sole match).
|
||||
- **"PhoneNav settings button meets 44×44px" (line 82):** `getByRole('button', { name: /open settings/i })` is present on both PhoneNav and DesktopNav. Passes as-authored.
|
||||
- **Rule 2 overflow tests (lines 142–165):** Purely DOM measurement. Pass unchanged on desktop.
|
||||
- **Harness self-validation injected-defect proofs (lines 175–256):** Use `nav[aria-label="Main navigation"]` CSS selector and body width injection. Pass unchanged on desktop.
|
||||
|
||||
#### `describe` block header comments to update
|
||||
|
||||
The jsdoc block at the top of `layout.spec.ts` (lines 1–25) currently says:
|
||||
```
|
||||
* Runs on both device profiles automatically (playwright.config.ts matrix):
|
||||
* iphone: iPhone 14 / WebKit / 390×844
|
||||
* pixel: Pixel 7 / Chromium / 412×915
|
||||
```
|
||||
Update to list all three profiles (same update applies to all spec file headers).
|
||||
|
||||
---
|
||||
|
||||
### `apps/pwa/e2e/calendar.spec.ts` — no structural changes needed
|
||||
|
||||
All tests in this file pass unchanged on desktop:
|
||||
|
||||
- **Auth-bypass precondition (line 26):** waits for `Main navigation` landmark — resolves to DesktopNav sidebar on desktop. Passes.
|
||||
- **SW-block precondition (line 42):** already uses `test.skip(!swAvailable, ...)` — self-healing conditional. No change.
|
||||
- **Populated state tests (lines 81–111):** `.sx-react-calendar-wrapper` and `getByText('Seeded Test Event')` are not viewport-dependent. Pass unchanged.
|
||||
- **Error state tests (lines 116–178):** `page.route` + heading/button assertions are not viewport-dependent. Pass unchanged.
|
||||
|
||||
Only the file's header comment block (lines 12–15) needs updating to list the `desktop` project.
|
||||
|
||||
---
|
||||
|
||||
### `apps/pwa/e2e/lists.spec.ts` — no structural changes needed
|
||||
|
||||
All tests pass unchanged on desktop — no mobile-only assumptions anywhere in this file. Only the header comment (lines 12–15) needs updating to list the `desktop` project.
|
||||
|
||||
---
|
||||
|
||||
### `apps/pwa/e2e/README.md` — docs update only
|
||||
|
||||
**Current run commands block (lines 37–47):**
|
||||
```bash
|
||||
# Full suite — both iPhone (WebKit) and Pixel (Chromium) profiles
|
||||
pnpm --filter @familysync/pwa test:e2e
|
||||
|
||||
# Single profile (faster local iteration)
|
||||
pnpm --filter @familysync/pwa exec playwright test --project=pixel
|
||||
|
||||
# Headed (local debug — shows the browser)
|
||||
pnpm --filter @familysync/pwa exec playwright test --headed
|
||||
|
||||
# UI mode (interactive test explorer)
|
||||
pnpm --filter @familysync/pwa test:e2e:ui
|
||||
```
|
||||
|
||||
Update the comment on the full-suite command and add a desktop-specific example:
|
||||
```bash
|
||||
# Full suite — iPhone (WebKit), Pixel (Chromium), Desktop Chrome profiles
|
||||
pnpm --filter @familysync/pwa test:e2e
|
||||
|
||||
# Single profile (faster local iteration)
|
||||
pnpm --filter @familysync/pwa exec playwright test --project=pixel
|
||||
pnpm --filter @familysync/pwa exec playwright test --project=desktop
|
||||
```
|
||||
|
||||
Also update the preamble sentence (line 3) which currently says "mobile-emulated (iPhone 14/WebKit + Pixel 7/Chromium)" — add "Desktop Chrome (1280×720)".
|
||||
|
||||
---
|
||||
|
||||
## Shared Patterns
|
||||
|
||||
### Project-name conditional skip
|
||||
**Source:** `apps/pwa/e2e/calendar.spec.ts` lines 59–62 (inline `test.skip` form)
|
||||
**Apply to:** every mobile-only test in `layout.spec.ts`
|
||||
|
||||
Pattern — add as the **first statement** in the test body, before any `await`:
|
||||
```typescript
|
||||
test('test name', async ({ page }, testInfo) => {
|
||||
test.skip(testInfo.project.name === 'desktop', 'reason: mobile-only assertion (describe which element is absent on desktop)');
|
||||
// existing test body unchanged below
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
### Nav landmark locator (works on all three profiles)
|
||||
**Source:** `apps/pwa/e2e/layout.spec.ts` lines 45, 65; `calendar.spec.ts` lines 31, 78
|
||||
**Apply to:** any new desktop assertion that needs auth-ready confirmation
|
||||
```typescript
|
||||
const nav = page.getByRole('navigation', { name: 'Main navigation' });
|
||||
await expect(nav).toBeVisible();
|
||||
```
|
||||
On mobile: resolves to `BottomTabBar` nav. On desktop: resolves to `DesktopNav` sidebar. Same locator, different element — no conditional needed.
|
||||
|
||||
### Desktop "New Event" button locator (CalendarShell.tsx:436–457)
|
||||
**Source:** `CalendarShell.tsx` lines 424–459
|
||||
|
||||
The desktop toolbar button is rendered inside `{!phone && (...)}` with plain text `New Event` (no `aria-label` attribute). Playwright resolves it by accessible name from inner text:
|
||||
```typescript
|
||||
// Resolves to desktop toolbar button at ≥768px (has minHeight:44px per line 443)
|
||||
// Resolves to phone FAB at <768px (has aria-label="New Event" per line 466)
|
||||
page.getByRole('button', { name: 'New Event' })
|
||||
```
|
||||
At 1280px (`Desktop Chrome`) only the toolbar button renders; the FAB is in `{phone && (...)}` which is false. **No strict-mode collision.**
|
||||
|
||||
---
|
||||
|
||||
## No Analog Found
|
||||
|
||||
None. All files being modified are established; all new code copies directly from existing patterns in the same files.
|
||||
|
||||
---
|
||||
|
||||
## Metadata
|
||||
|
||||
**Analog search scope:** `apps/pwa/playwright.config.ts`, `apps/pwa/e2e/`, `apps/pwa/src/components/CalendarShell.tsx`
|
||||
**Files scanned:** 6
|
||||
**Pattern extraction date:** 2026-06-12
|
||||
@@ -0,0 +1,94 @@
|
||||
---
|
||||
phase: 14-desktop-e2e-coverage
|
||||
reviewed: 2026-06-12T00:00:00Z
|
||||
depth: standard
|
||||
files_reviewed: 6
|
||||
files_reviewed_list:
|
||||
- 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
|
||||
- .gitea/workflows/ci.yml
|
||||
findings:
|
||||
critical: 0
|
||||
warning: 3
|
||||
info: 3
|
||||
total: 6
|
||||
status: issues_found
|
||||
---
|
||||
|
||||
# Phase 14: Code Review Report
|
||||
|
||||
**Reviewed:** 2026-06-12
|
||||
**Depth:** standard
|
||||
**Files Reviewed:** 6
|
||||
**Status:** issues_found
|
||||
|
||||
## Summary
|
||||
|
||||
Phase 14 adds a third Playwright project (`desktop` — Desktop Chrome, 1280×720, no `hasTouch`) and makes the existing mobile-authored e2e specs green on desktop. The diff is test-harness-only — no runtime/app code changed. I verified the load-bearing source assumptions encoded in the new skip guards against the actual components:
|
||||
|
||||
- **Skip guards are correct.** On desktop (≥768px) `AppNav` renders `DesktopNav` with the sole `<nav aria-label="Main navigation">` (`AppNav.tsx:168`), and `BottomTabBar` returns `null` (`BottomTabBar.tsx:56`). There is no strict-mode collision and the safe-area-inset bottom-edge assertion is genuinely meaningless for a left sidebar — the two `BottomTabBar is fully in-viewport` skips are right.
|
||||
- **FAB skip / desktop-parity test are consistent.** On desktop the only `button` with accessible name "New Event" is the toolbar button (`CalendarShell.tsx:436-457`, text "New Event", `minHeight: '44px'`). The FAB is gated behind `phone &&` (`CalendarShell.tsx:464`), so the 56×56 FAB assertion correctly skips and the new ≥44px parity test (`testInfo.project.name !== 'desktop'` inverse-guard) correctly runs only on desktop. The two guards target the exact same locator — internally consistent.
|
||||
- **No mobile assertion was weakened or deleted.** `calendar.spec.ts` and `lists.spec.ts` changes are comment-only. `layout.spec.ts` only adds guards + one new test; every prior mobile assertion is intact. The CI workflow change is comment-only (step name + comment), the `desktop` project runs automatically because the harness invokes `playwright test` with no `--project` filter.
|
||||
|
||||
Findings below are quality/determinism concerns, not correctness blockers. The harness logic is sound.
|
||||
|
||||
## Warnings
|
||||
|
||||
### WR-01: Two "PhoneNav" tests silently re-target DesktopNav on desktop and no longer test what their names claim
|
||||
|
||||
**File:** `apps/pwa/e2e/layout.spec.ts:83-96`
|
||||
**Issue:** `PhoneNav header is visible (Rule 3)` and `PhoneNav settings button meets 44×44px (Rule 1)` are NOT guarded, so they run on the `desktop` project. On desktop `PhoneNav` is never rendered (`AppNav.tsx:34` returns `DesktopNav` at ≥768px). The tests pass only by coincidence:
|
||||
- `getByText('FamilySync', { exact: true })` matches the `DesktopNav` sidebar title (`AppNav.tsx:181`) instead of the PhoneNav header.
|
||||
- `getByRole('button', { name: /open settings/i })` matches the `DesktopNav` avatar button (`AppNav.tsx:220-222`, `minHeight: '44px'`) instead of the PhoneNav settings button.
|
||||
|
||||
This is misleading green: a future regression that breaks PhoneNav specifically would still pass on desktop, and the test name asserts a component that isn't on screen. The phase scope ("right tests skipped, no over/under-skipping") implies these mobile-named geometry tests should either be desktop-skipped (like the FAB) or renamed to reflect that on desktop they validate the DesktopNav equivalent.
|
||||
**Fix:** Either guard them mobile-only and add explicit DesktopNav counterparts, e.g.:
|
||||
```ts
|
||||
test('PhoneNav header is visible (Rule 3)', async ({ page }, testInfo) => {
|
||||
test.skip(testInfo.project.name === 'desktop',
|
||||
'PhoneNav is not rendered at ≥768px; DesktopNav renders the FamilySync title instead');
|
||||
// ...
|
||||
});
|
||||
```
|
||||
or rename to "App header/title is visible" and "Settings affordance meets 44×44px" so the assertion is honest across all three profiles.
|
||||
|
||||
### WR-02: calendar.spec "all tests pass unchanged" relies on an untested Schedule-X view (month-grid) on desktop
|
||||
|
||||
**File:** `apps/pwa/e2e/calendar.spec.ts:16`, `apps/pwa/e2e/calendar.spec.ts:92-101`
|
||||
**Issue:** The header comment claims desktop runs "all tests pass unchanged", but the desktop default Schedule-X view differs from mobile: `readPersistedView()` returns `month-grid` for ≥768px vs `month-agenda` for ≤767px (`apps/pwa/src/store/calendarStore.ts:14-15,119-120`). The `seeded event "Seeded Test Event" is rendered (DB→UI proof)` test asserts `getByText('Seeded Test Event').first()` is visible — on desktop this now exercises month-grid rendering that the iphone/pixel profiles never covered. In month-grid, an event title can be collapsed into a "+N more" overflow affordance when a day cell is crowded. With a single seeded event today this won't trigger, so the risk is low, but the claim "unchanged" understates that desktop adds a new rendering path. If global-setup is ever extended to seed multiple same-day events, this assertion could flake/fail on desktop only.
|
||||
**Fix:** Note the view divergence in the comment, and make the assertion view-robust if multi-event seeds are anticipated — e.g. assert the event is reachable, falling back to an explicit week/day view switch, or pin the desktop test to a deterministic view. At minimum, change the comment to "all tests pass; desktop renders month-grid (mobile renders month-agenda)".
|
||||
|
||||
### WR-03: lists.spec / calendar.spec "all tests pass unchanged" claim is unverified by the diff and contradicted by view/landmark divergence
|
||||
|
||||
**File:** `apps/pwa/e2e/lists.spec.ts:19`, `apps/pwa/e2e/calendar.spec.ts:16`
|
||||
**Issue:** Both comment headers added by this phase assert "(all tests pass unchanged)" for the desktop profile. The lists `at least one list item is present` test (`lists.spec.ts:43-48`) and `seeded "E2E Grocery List" card` test depend on `ListsIndex.tsx:152` `role="list"` + `ListCard.tsx:52` `role="listitem"`, which are viewport-independent — those are fine. But the blanket "unchanged" wording is an unverifiable assertion baked into source comments; it papers over WR-01 (PhoneNav tests retarget) and WR-02 (month-grid). Comments that assert test outcomes drift out of date and mislead future readers into trusting desktop coverage they don't have.
|
||||
**Fix:** Replace "all tests pass unchanged" with a factual statement of what desktop exercises (e.g. "desktop renders DesktopNav sidebar + month-grid; mobile-geometry tests are skipped, see layout.spec.ts"). Keep outcome claims out of source comments — let the CI run be the source of truth.
|
||||
|
||||
## Info
|
||||
|
||||
### IN-01: `serviceWorkers: 'block'` is duplicated across all three projects instead of hoisted to shared `use`
|
||||
|
||||
**File:** `apps/pwa/playwright.config.ts:45,53,62`
|
||||
**Issue:** Every project repeats `serviceWorkers: 'block'`. The config comment (`line 6`) states it applies to all profiles, so it is a global invariant, not a per-device override. Repeating it invites a future profile being added without it (silent SW-block regression).
|
||||
**Fix:** Hoist `serviceWorkers: 'block'` into the top-level `use` block (line 28-34) and drop it from each project. Per-project `use` only needs the device descriptor spread.
|
||||
|
||||
### IN-02: New desktop parity test has no explicit auth/mount wait before locating the toolbar button
|
||||
|
||||
**File:** `apps/pwa/e2e/layout.spec.ts:114-128`
|
||||
**Issue:** The describe-level `beforeEach` (`layout.spec.ts:32-34`) only does `page.goto('/calendar')` with no `await expect(nav).toBeVisible()` gate (unlike the calendar/lists specs which wait for the nav landmark). The new test relies entirely on `boundingBox()` auto-waiting for the toolbar button to mount after auth + CalendarShell render. This works because Playwright retries, but it's inconsistent with the explicit auth-wait pattern used in `calendar.spec.ts:79` and `lists.spec.ts:33`, and a slow desktop mount could eat into the default timeout.
|
||||
**Fix:** Add `await expect(page.getByRole('navigation', { name: 'Main navigation' })).toBeVisible();` at the top of the new test (or in a desktop-scoped beforeEach) to gate on auth before measuring, matching the established pattern.
|
||||
|
||||
### IN-03: Desktop project lacks an explicit viewport assertion / pin
|
||||
|
||||
**File:** `apps/pwa/playwright.config.ts:56-63`
|
||||
**Issue:** The `desktop` project relies entirely on `devices['Desktop Chrome']`'s bundled 1280×720 viewport. The spec comments hard-code "1280×720" in three files (`layout.spec.ts:13`, `calendar.spec.ts:16`, `lists.spec.ts:19`). If Playwright's `Desktop Chrome` descriptor viewport ever changes across a version bump, the comments silently lie and the ≥768px breakpoint assumptions could (in extreme cases) break with no guard. Low risk — 1280×720 is stable — but the breakpoint dependency (≥768px) is implicit.
|
||||
**Fix:** Optionally pin `viewport: { width: 1280, height: 720 }` explicitly in the desktop project `use` so the ≥768px DesktopNav assumption is self-documenting and version-stable.
|
||||
|
||||
---
|
||||
|
||||
_Reviewed: 2026-06-12_
|
||||
_Reviewer: Claude (gsd-code-reviewer)_
|
||||
_Depth: standard_
|
||||
Reference in New Issue
Block a user