Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f75bc4239f | ||
|
|
214e59d6e2 | ||
|
|
1c7775a022 | ||
|
|
492adbc8b3 | ||
|
|
bfc49d199d | ||
|
|
29035999ea | ||
|
|
d3bc69657b | ||
|
|
5c3bd5c1ea | ||
|
|
8742dd43a4 | ||
|
|
d8cdbcfc14 |
@@ -252,7 +252,7 @@ jobs:
|
|||||||
# CI=true makes Playwright start Vite :5173 itself (reuseExistingServer=false), use
|
# CI=true makes Playwright start Vite :5173 itself (reuseExistingServer=false), use
|
||||||
# retries:2/workers:1, and apply reporter:'github' — which --reporter=list,html overrides
|
# retries:2/workers:1, and apply reporter:'github' — which --reporter=list,html overrides
|
||||||
# because Gitea does not render github annotations (Pitfall 5 / D-06). Both projects run.
|
# because Gitea does not render github annotations (Pitfall 5 / D-06). Both projects run.
|
||||||
- name: Run harness (start API + Playwright iphone + pixel)
|
- name: Run harness (start API + Playwright iphone + pixel + desktop)
|
||||||
env:
|
env:
|
||||||
CI: 'true'
|
CI: 'true'
|
||||||
# Use 127.0.0.1 (not localhost): the runner image resolves `localhost` to ::1 first,
|
# Use 127.0.0.1 (not localhost): the runner image resolves `localhost` to ::1 first,
|
||||||
@@ -284,7 +284,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
echo "API ready at :3000"
|
echo "API ready at :3000"
|
||||||
|
|
||||||
# Run the Phase 7 harness across both profiles; preserve its exit code, always kill the API.
|
# Run the Phase 7/14 harness across all three profiles (iphone, pixel, desktop); preserve its exit code, always kill the API.
|
||||||
# Call the pwa test:e2e script DIRECTLY (single pnpm layer) and append --reporter without a
|
# Call the pwa test:e2e script DIRECTLY (single pnpm layer) and append --reporter without a
|
||||||
# `--` separator: `pnpm <root> test:e2e -- <args>` double-forwards the `--` into
|
# `--` separator: `pnpm <root> test:e2e -- <args>` double-forwards the `--` into
|
||||||
# `playwright test -- <args>`, where playwright treats --reporter as a test-file filter →
|
# `playwright test -- <args>`, where playwright treats --reporter as a test-file filter →
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
|
|||||||
- [ ] **Phase 11: Per-Event Reminders** - Reminder selector on the event form (incl. "None") serialized as VALARM, with a variable-lead scheduler that honors each event's choice
|
- [ ] **Phase 11: Per-Event Reminders** - Reminder selector on the event form (incl. "None") serialized as VALARM, with a variable-lead scheduler that honors each event's choice
|
||||||
- [ ] **Phase 12: Initial Setup Wizard** - First-run validated bootstrap of env/VAPID/DB/OIDC + first app password, reusing the admin route surface
|
- [ ] **Phase 12: Initial Setup Wizard** - First-run validated bootstrap of env/VAPID/DB/OIDC + first app password, reusing the admin route surface
|
||||||
- [x] **Phase 13: Real Lint Gate (ESLint)** - Wire ESLint flat config (typescript-eslint + React) across both apps so the Phase 8 CI lint slot actually fails on violations instead of no-op'ing (completed 2026-06-12)
|
- [x] **Phase 13: Real Lint Gate (ESLint)** - Wire ESLint flat config (typescript-eslint + React) across both apps so the Phase 8 CI lint slot actually fails on violations instead of no-op'ing (completed 2026-06-12)
|
||||||
- [ ] **Phase 14: Desktop E2E Coverage** - Add a Desktop Chrome Playwright profile + make the mobile-authored specs desktop-safe so the Phase 8 regression gate validates desktop, not just mobile
|
- [x] **Phase 14: Desktop E2E Coverage** - Add a Desktop Chrome Playwright profile + make the mobile-authored specs desktop-safe so the Phase 8 regression gate validates desktop, not just mobile (completed 2026-06-12)
|
||||||
|
|
||||||
## Phase Details
|
## Phase Details
|
||||||
|
|
||||||
@@ -252,7 +252,11 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
|
|||||||
- The real work is the spec-compat pass, not CI plumbing — Phase 8 reused the Phase 7 harness unchanged, so the config addition is small but specs authored for touch/mobile need per-spec review.
|
- The real work is the spec-compat pass, not CI plumbing — Phase 8 reused the Phase 7 harness unchanged, so the config addition is small but specs authored for touch/mobile need per-spec review.
|
||||||
- Desktop WebKit is optional — the Apple member is already covered on mobile Safari via `iphone`; Desktop Chrome is likely sufficient for a shared/wall browser.
|
- Desktop WebKit is optional — the Apple member is already covered on mobile Safari via `iphone`; Desktop Chrome is likely sufficient for a shared/wall browser.
|
||||||
|
|
||||||
**Plans**: TBD
|
**Plans**: 1 plan
|
||||||
|
Plans:
|
||||||
|
|
||||||
|
- [x] 14-01-PLAN.md — Add the `desktop` Playwright project, desktop-skip the two mobile-only layout assertions (+ D-04 parity), update spec/README docs, and prove `pnpm test:e2e` is green on iphone + pixel + desktop with a blocking CI gate.
|
||||||
|
|
||||||
**UI hint**: no
|
**UI hint**: no
|
||||||
|
|
||||||
## Progress
|
## Progress
|
||||||
@@ -272,7 +276,7 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
|
|||||||
| 11. Per-Event Reminders | v1.1 | 0/? | Not started | - |
|
| 11. Per-Event Reminders | v1.1 | 0/? | Not started | - |
|
||||||
| 12. Initial Setup Wizard | v1.1 | 0/? | Not started | - |
|
| 12. Initial Setup Wizard | v1.1 | 0/? | Not started | - |
|
||||||
| 13. Real Lint Gate (ESLint) | v1.1 | 3/3 | Complete | 2026-06-12 |
|
| 13. Real Lint Gate (ESLint) | v1.1 | 3/3 | Complete | 2026-06-12 |
|
||||||
| 14. Desktop E2E Coverage | v1.1 | 0/? | Not started | - |
|
| 14. Desktop E2E Coverage | v1.1 | 1/1 | Complete | 2026-06-12 |
|
||||||
|
|
||||||
## Backlog
|
## Backlog
|
||||||
|
|
||||||
@@ -280,7 +284,7 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
|
|||||||
|
|
||||||
**Goal:** [Captured for future planning] Abstract the calendar backend behind a provider interface so Fastmail/CalDAV is one implementation among potentially many. Shipping with a single provider is fine, but the broker, sync, and event-expansion layers should be structured so additional providers (e.g. other CalDAV hosts, Google Calendar, generic ICS feeds) can be added without rework. Captures the "provider" seam as an explicit architectural concern.
|
**Goal:** [Captured for future planning] Abstract the calendar backend behind a provider interface so Fastmail/CalDAV is one implementation among potentially many. Shipping with a single provider is fine, but the broker, sync, and event-expansion layers should be structured so additional providers (e.g. other CalDAV hosts, Google Calendar, generic ICS feeds) can be added without rework. Captures the "provider" seam as an explicit architectural concern.
|
||||||
**Requirements:** TBD
|
**Requirements:** TBD
|
||||||
**Plans:** 3/4 plans executed
|
**Plans:** 1/1 plans complete
|
||||||
|
|
||||||
Plans:
|
Plans:
|
||||||
|
|
||||||
@@ -470,6 +474,7 @@ Plans:
|
|||||||
**The footgun:** branch protection currently requires three contexts — `CI / fast-checks`, `CI / api`, `CI / harness`. The naive fix (`paths-ignore` on the workflow, or path-filtering `harness`) **deadlocks**: on a docs-only PR the required `harness`/`api` contexts never report, so the PR can never merge. A required check that never reports is worse than a slow one.
|
**The footgun:** branch protection currently requires three contexts — `CI / fast-checks`, `CI / api`, `CI / harness`. The naive fix (`paths-ignore` on the workflow, or path-filtering `harness`) **deadlocks**: on a docs-only PR the required `harness`/`api` contexts never report, so the PR can never merge. A required check that never reports is worse than a slow one.
|
||||||
|
|
||||||
**Solution (Option A — aggregate gate):**
|
**Solution (Option A — aggregate gate):**
|
||||||
|
|
||||||
- Add a cheap `changes` detector job: `git diff --name-only base...HEAD`, set `code=true` unless every changed path matches `docs/` or `*.md`.
|
- Add a cheap `changes` detector job: `git diff --name-only base...HEAD`, set `code=true` unless every changed path matches `docs/` or `*.md`.
|
||||||
- Gate the heavy jobs: `api` and `harness` get `needs: changes` + `if: needs.changes.outputs.code == 'true'`.
|
- Gate the heavy jobs: `api` and `harness` get `needs: changes` + `if: needs.changes.outputs.code == 'true'`.
|
||||||
- Add an always-running `gate` job: `needs: [fast-checks, api, harness]`, `if: always()`, passes when each dependency `result` is `success` OR `skipped`.
|
- Add an always-running `gate` job: `needs: [fast-checks, api, harness]`, `if: always()`, passes when each dependency `result` is `success` OR `skipped`.
|
||||||
|
|||||||
+15
-15
@@ -2,16 +2,16 @@
|
|||||||
gsd_state_version: 1.0
|
gsd_state_version: 1.0
|
||||||
milestone: v1.1
|
milestone: v1.1
|
||||||
milestone_name: Operability & Polish
|
milestone_name: Operability & Polish
|
||||||
status: verifying
|
status: executing
|
||||||
stopped_at: Phase 13 Plan 03 complete (Prettier reformat + CI format gate + green baseline)
|
stopped_at: Phase 14 context gathered
|
||||||
last_updated: "2026-06-12T00:59:51.634Z"
|
last_updated: "2026-06-12T12:14:45.983Z"
|
||||||
last_activity: 2026-06-12
|
last_activity: 2026-06-12 -- Phase 14 execution started
|
||||||
progress:
|
progress:
|
||||||
total_phases: 17
|
total_phases: 18
|
||||||
completed_phases: 3
|
completed_phases: 3
|
||||||
total_plans: 11
|
total_plans: 12
|
||||||
completed_plans: 11
|
completed_plans: 11
|
||||||
percent: 18
|
percent: 17
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project State
|
# Project State
|
||||||
@@ -21,14 +21,14 @@ progress:
|
|||||||
See: .planning/PROJECT.md (updated 2026-06-10)
|
See: .planning/PROJECT.md (updated 2026-06-10)
|
||||||
|
|
||||||
**Core value:** One color-coded family calendar (shared + personal) and shared lists from a single low-friction PWA — cross-ecosystem, no app store
|
**Core value:** One color-coded family calendar (shared + personal) and shared lists from a single low-friction PWA — cross-ecosystem, no app store
|
||||||
**Current focus:** Phase 13 — real-lint-gate-eslint
|
**Current focus:** Phase 14 — desktop-e2e-coverage
|
||||||
|
|
||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Phase: 999.1
|
Phase: 14 (desktop-e2e-coverage) — EXECUTING
|
||||||
Plan: Not started
|
Plan: 1 of 1
|
||||||
Status: Phase complete — ready for verification
|
Status: Executing Phase 14
|
||||||
Last activity: 2026-06-12
|
Last activity: 2026-06-12 -- Phase 14 execution started
|
||||||
|
|
||||||
## Performance Metrics
|
## Performance Metrics
|
||||||
|
|
||||||
@@ -209,9 +209,9 @@ Recent decisions affecting current work:
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-06-12T00:44:39.612Z
|
Last session: 2026-06-12T11:57:10.391Z
|
||||||
Stopped at: Phase 13 Plan 03 complete (Prettier reformat + CI format gate + green baseline)
|
Stopped at: Phase 14 context gathered
|
||||||
Resume file: None
|
Resume file: .planning/phases/14-desktop-e2e-coverage/14-CONTEXT.md
|
||||||
|
|
||||||
## Operator Next Steps
|
## Operator Next Steps
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
> Generated from `.planning/intel/api-map.json`. Do not edit by hand.
|
> Generated from `.planning/intel/api-map.json`. Do not edit by hand.
|
||||||
|
|
||||||
|
> **Warning:** api-map.json is stale (>24 hours old). Data below may be out of date.
|
||||||
|
|
||||||
## `GET /health`
|
## `GET /health`
|
||||||
|
|
||||||
- **method:** GET
|
- **method:** GET
|
||||||
|
|||||||
@@ -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,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
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# E2E Test Harness
|
# E2E Test Harness
|
||||||
|
|
||||||
Playwright test harness for the FamilySync PWA — mobile-emulated (iPhone 14/WebKit + Pixel 7/Chromium), authenticated via `DEV_AUTH_BYPASS`, deterministically seeded, runs headlessly in CI.
|
Playwright test harness for the FamilySync PWA — mobile-emulated (iPhone 14/WebKit + Pixel 7/Chromium) and Desktop Chrome (1280×720), authenticated via `DEV_AUTH_BYPASS`, deterministically seeded, runs headlessly in CI.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -34,11 +34,12 @@ environment (not only the API's). Source the DB credentials from the repo-root `
|
|||||||
set -a; source .env; set +a
|
set -a; source .env; set +a
|
||||||
export DEV_AUTH_BYPASS=true DB_HOST=127.0.0.1 DB_PORT=3306
|
export DEV_AUTH_BYPASS=true DB_HOST=127.0.0.1 DB_PORT=3306
|
||||||
|
|
||||||
# Full suite — both iPhone (WebKit) and Pixel (Chromium) profiles
|
# Full suite — iPhone (WebKit), Pixel (Chromium), Desktop Chrome profiles
|
||||||
pnpm --filter @familysync/pwa test:e2e
|
pnpm --filter @familysync/pwa test:e2e
|
||||||
|
|
||||||
# Single profile (faster local iteration)
|
# Single profile (faster local iteration)
|
||||||
pnpm --filter @familysync/pwa exec playwright test --project=pixel
|
pnpm --filter @familysync/pwa exec playwright test --project=pixel
|
||||||
|
pnpm --filter @familysync/pwa exec playwright test --project=desktop
|
||||||
|
|
||||||
# Headed (local debug — shows the browser)
|
# Headed (local debug — shows the browser)
|
||||||
pnpm --filter @familysync/pwa exec playwright test --headed
|
pnpm --filter @familysync/pwa exec playwright test --headed
|
||||||
|
|||||||
@@ -10,9 +10,10 @@
|
|||||||
* Requires the dev stack running with DEV_AUTH_BYPASS=true (see e2e/README.md).
|
* Requires the dev stack running with DEV_AUTH_BYPASS=true (see e2e/README.md).
|
||||||
* global-setup seeds 'Seeded Test Event' on calendar_id=10 for user_id=1.
|
* global-setup seeds 'Seeded Test Event' on calendar_id=10 for user_id=1.
|
||||||
*
|
*
|
||||||
* Runs on both device profiles automatically (playwright.config.ts matrix):
|
* Runs on all three device profiles automatically (playwright.config.ts matrix):
|
||||||
* iphone: iPhone 14 / WebKit / 390×844
|
* iphone: iPhone 14 / WebKit / 390×844
|
||||||
* pixel: Pixel 7 / Chromium / 412×915
|
* pixel: Pixel 7 / Chromium / 412×915
|
||||||
|
* desktop: Desktop Chrome / Chromium / 1280×720 (all tests pass unchanged)
|
||||||
*
|
*
|
||||||
* Run:
|
* Run:
|
||||||
* pnpm --filter @familysync/pwa test:e2e
|
* pnpm --filter @familysync/pwa test:e2e
|
||||||
|
|||||||
@@ -7,9 +7,10 @@
|
|||||||
* Rule 3: Critical elements visible and in-viewport on initial load
|
* Rule 3: Critical elements visible and in-viewport on initial load
|
||||||
* Rule 4: Accessible names on all interactive elements (role+name locators)
|
* Rule 4: Accessible names on all interactive elements (role+name locators)
|
||||||
*
|
*
|
||||||
* Runs on both device profiles automatically (playwright.config.ts matrix):
|
* Runs on all three device profiles automatically (playwright.config.ts matrix):
|
||||||
* iphone: iPhone 14 / WebKit / 390×844
|
* iphone: iPhone 14 / WebKit / 390×844
|
||||||
* pixel: Pixel 7 / Chromium / 412×915
|
* pixel: Pixel 7 / Chromium / 412×915
|
||||||
|
* desktop: Desktop Chrome / Chromium / 1280×720 (mobile-only geometry tests skipped)
|
||||||
*
|
*
|
||||||
* STRICT-MODE NOTE:
|
* STRICT-MODE NOTE:
|
||||||
* On mobile viewports (≤767px), AppNav renders PhoneNav as a <header> element
|
* On mobile viewports (≤767px), AppNav renders PhoneNav as a <header> element
|
||||||
@@ -59,7 +60,13 @@ test.describe('Rule 1/3/4 — BottomTabBar tap targets and in-viewport position'
|
|||||||
expect(box!.height, 'Lists tab height ≥ 44px').toBeGreaterThanOrEqual(44);
|
expect(box!.height, 'Lists tab height ≥ 44px').toBeGreaterThanOrEqual(44);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('BottomTabBar is fully in-viewport (Rule 3 — safe-area-inset)', async ({ page }) => {
|
test('BottomTabBar is fully in-viewport (Rule 3 — safe-area-inset)', async ({
|
||||||
|
page,
|
||||||
|
}, testInfo) => {
|
||||||
|
test.skip(
|
||||||
|
testInfo.project.name === 'desktop',
|
||||||
|
'On desktop the Main navigation landmark resolves to the DesktopNav sidebar; the safe-area-inset bottom-edge assertion is semantically wrong for a sidebar (BottomTabBar returns null at ≥768px)',
|
||||||
|
);
|
||||||
// The bar uses env(safe-area-inset-bottom, 0px). In emulation there is no
|
// 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.
|
// safe-area-inset, so the bar's bottom edge must be ≤ viewport height.
|
||||||
const nav = page.getByRole('navigation', { name: 'Main navigation' });
|
const nav = page.getByRole('navigation', { name: 'Main navigation' });
|
||||||
@@ -88,7 +95,11 @@ test.describe('Rule 1/3/4 — BottomTabBar tap targets and in-viewport position'
|
|||||||
expect(box!.height, 'Settings button height ≥ 44px').toBeGreaterThanOrEqual(44);
|
expect(box!.height, 'Settings button height ≥ 44px').toBeGreaterThanOrEqual(44);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('New Event FAB meets 56×56px touch-target minimum (Rule 1)', async ({ page }) => {
|
test('New Event FAB meets 56×56px touch-target minimum (Rule 1)', async ({ page }, testInfo) => {
|
||||||
|
test.skip(
|
||||||
|
testInfo.project.name === 'desktop',
|
||||||
|
'On desktop getByRole(button, New Event) resolves to the toolbar button (CalendarShell.tsx:436-457), not the 56×56 FAB; FAB geometry assertion does not apply to the toolbar button',
|
||||||
|
);
|
||||||
// Phone-only FAB — aria-label="New Event", fixed 56×56px (CalendarShell.tsx)
|
// Phone-only FAB — aria-label="New Event", fixed 56×56px (CalendarShell.tsx)
|
||||||
const fab = page.getByRole('button', { name: 'New Event' });
|
const fab = page.getByRole('button', { name: 'New Event' });
|
||||||
const box = await fab.boundingBox();
|
const box = await fab.boundingBox();
|
||||||
@@ -96,6 +107,25 @@ test.describe('Rule 1/3/4 — BottomTabBar tap targets and in-viewport position'
|
|||||||
expect(box!.width, 'New Event FAB width ≥ 56px').toBeGreaterThanOrEqual(56);
|
expect(box!.width, 'New Event FAB width ≥ 56px').toBeGreaterThanOrEqual(56);
|
||||||
expect(box!.height, 'New Event FAB height ≥ 56px').toBeGreaterThanOrEqual(56);
|
expect(box!.height, 'New Event FAB height ≥ 56px').toBeGreaterThanOrEqual(56);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// D-04 parity: desktop toolbar "New Event" button meets ≥44px minimum (Rule 1)
|
||||||
|
// CalendarShell.tsx:443 sets minHeight:'44px' on the desktop toolbar button.
|
||||||
|
// Guarded to run only on desktop — the FAB test above covers mobile profiles.
|
||||||
|
test('New Event toolbar button meets 44px touch-target minimum on desktop (Rule 1 — D-04 parity)', async ({
|
||||||
|
page,
|
||||||
|
}, testInfo) => {
|
||||||
|
test.skip(
|
||||||
|
testInfo.project.name !== 'desktop',
|
||||||
|
'Desktop parity assertion — toolbar button only renders at ≥768px (Desktop Chrome); FAB test covers iphone/pixel',
|
||||||
|
);
|
||||||
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Rule 1/3/4 repeated on /lists ──
|
// ── Rule 1/3/4 repeated on /lists ──
|
||||||
@@ -127,7 +157,11 @@ test.describe('Rule 1/3/4 — BottomTabBar on /lists', () => {
|
|||||||
expect(box!.height).toBeGreaterThanOrEqual(44);
|
expect(box!.height).toBeGreaterThanOrEqual(44);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('BottomTabBar is fully in-viewport on /lists (Rule 3)', async ({ page }) => {
|
test('BottomTabBar is fully in-viewport on /lists (Rule 3)', async ({ page }, testInfo) => {
|
||||||
|
test.skip(
|
||||||
|
testInfo.project.name === 'desktop',
|
||||||
|
'On desktop the Main navigation landmark resolves to the DesktopNav sidebar; the safe-area-inset bottom-edge assertion is semantically wrong for a sidebar (BottomTabBar returns null at ≥768px)',
|
||||||
|
);
|
||||||
const nav = page.getByRole('navigation', { name: 'Main navigation' });
|
const nav = page.getByRole('navigation', { name: 'Main navigation' });
|
||||||
await expect(nav).toBeVisible();
|
await expect(nav).toBeVisible();
|
||||||
const box = await nav.boundingBox();
|
const box = await nav.boundingBox();
|
||||||
|
|||||||
@@ -13,9 +13,10 @@
|
|||||||
* The empty state is simulated by routing /api/lists to return [] BEFORE navigation —
|
* The empty state is simulated by routing /api/lists to return [] BEFORE navigation —
|
||||||
* this keeps the seeded DB intact (T-07-11 / D-06 deterministic seed).
|
* this keeps the seeded DB intact (T-07-11 / D-06 deterministic seed).
|
||||||
*
|
*
|
||||||
* Runs on both device profiles automatically (playwright.config.ts matrix):
|
* Runs on all three device profiles automatically (playwright.config.ts matrix):
|
||||||
* iphone: iPhone 14 / WebKit / 390×844
|
* iphone: iPhone 14 / WebKit / 390×844
|
||||||
* pixel: Pixel 7 / Chromium / 412×915
|
* pixel: Pixel 7 / Chromium / 412×915
|
||||||
|
* desktop: Desktop Chrome / Chromium / 1280×720 (all tests pass unchanged)
|
||||||
*
|
*
|
||||||
* Run:
|
* Run:
|
||||||
* pnpm --filter @familysync/pwa test:e2e
|
* pnpm --filter @familysync/pwa test:e2e
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
/**
|
/**
|
||||||
* Playwright configuration — Phase 7 Mobile Test Harness
|
* Playwright configuration — Phase 7 Mobile Test Harness + Phase 14 Desktop
|
||||||
*
|
*
|
||||||
* Two-profile device matrix: iPhone 14/WebKit + Pixel 7/Chromium
|
* Three-profile device matrix: iPhone 14/WebKit + Pixel 7/Chromium + Desktop Chrome
|
||||||
* Auth: DEV_AUTH_BYPASS=true on the API (never storageState — D-01/Pitfall 14)
|
* Auth: DEV_AUTH_BYPASS=true on the API (never storageState — D-01/Pitfall 14)
|
||||||
* SW: serviceWorkers: 'block' on both profiles (D-02/Pitfall 15)
|
* SW: serviceWorkers: 'block' on all profiles (D-02/Pitfall 15)
|
||||||
* baseURL: env-driven PLAYWRIGHT_BASE_URL (D-08/Rule 8)
|
* baseURL: env-driven PLAYWRIGHT_BASE_URL (D-08/Rule 8)
|
||||||
* webServer: manages Vite only — API+MariaDB+Redis stay compose-managed (D-10)
|
* webServer: manages Vite only — API+MariaDB+Redis stay compose-managed (D-10)
|
||||||
*
|
*
|
||||||
* Run:
|
* Run:
|
||||||
* pnpm --filter @familysync/pwa test:e2e
|
* pnpm --filter @familysync/pwa test:e2e
|
||||||
* pnpm --filter @familysync/pwa exec playwright test --project=pixel
|
* pnpm --filter @familysync/pwa exec playwright test --project=pixel
|
||||||
|
* pnpm --filter @familysync/pwa exec playwright test --project=desktop
|
||||||
*/
|
*/
|
||||||
import { defineConfig, devices } from '@playwright/test';
|
import { defineConfig, devices } from '@playwright/test';
|
||||||
|
|
||||||
@@ -52,6 +53,14 @@ export default defineConfig({
|
|||||||
serviceWorkers: 'block',
|
serviceWorkers: 'block',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Desktop Chrome: 1280×720 viewport, Chromium engine, no hasTouch (D-06)
|
||||||
|
name: 'desktop',
|
||||||
|
use: {
|
||||||
|
...devices['Desktop Chrome'],
|
||||||
|
serviceWorkers: 'block',
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
// D-10: manage Vite only; API+MariaDB+Redis are compose-managed
|
// D-10: manage Vite only; API+MariaDB+Redis are compose-managed
|
||||||
|
|||||||
Reference in New Issue
Block a user