From d3bc69657b5cfc9e2401332f712fc993045034a5 Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Fri, 12 Jun 2026 08:19:04 -0400 Subject: [PATCH 1/4] feat(14-01): add desktop Playwright project (Desktop Chrome, 1280x720, no hasTouch) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Appended 'desktop' project entry after 'pixel' in playwright.config.ts projects array - Uses devices['Desktop Chrome'] with serviceWorkers: 'block' (D-02/Pitfall 15) - No baseURL override — inherited from top-level use block (D-08) - Updated file header jsdoc: three-profile matrix, added --project=desktop example --- apps/pwa/playwright.config.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/pwa/playwright.config.ts b/apps/pwa/playwright.config.ts index c84a770..7a194ba 100644 --- a/apps/pwa/playwright.config.ts +++ b/apps/pwa/playwright.config.ts @@ -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) - * 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) * webServer: manages Vite only — API+MariaDB+Redis stay compose-managed (D-10) * * Run: * pnpm --filter @familysync/pwa test:e2e * pnpm --filter @familysync/pwa exec playwright test --project=pixel + * pnpm --filter @familysync/pwa exec playwright test --project=desktop */ import { defineConfig, devices } from '@playwright/test'; @@ -52,6 +53,14 @@ export default defineConfig({ 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 From 29035999eab7879f1a271307861002ad1a12abb3 Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Fri, 12 Jun 2026 08:20:06 -0400 Subject: [PATCH 2/4] feat(14-01): desktop-skip three mobile-only layout assertions, add D-04 parity - Added test.skip(testInfo.project.name === 'desktop') to the two safe-area-inset BottomTabBar in-viewport tests (BottomTabBar returns null at >=768px on desktop) - Added test.skip(testInfo.project.name === 'desktop') to the 56x56 FAB geometry test (on desktop 'New Event' resolves to the toolbar button, not the 56px FAB) - Added desktop-only D-04 parity test asserting 'New Event' toolbar button height >=44px guarded by test.skip(testInfo.project.name !== 'desktop') - Updated header jsdoc to list all three profiles including desktop - All mobile assertions preserved (toBeGreaterThanOrEqual(56) and (44) still present) --- apps/pwa/e2e/layout.spec.ts | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/apps/pwa/e2e/layout.spec.ts b/apps/pwa/e2e/layout.spec.ts index ec485af..99072d1 100644 --- a/apps/pwa/e2e/layout.spec.ts +++ b/apps/pwa/e2e/layout.spec.ts @@ -7,9 +7,10 @@ * Rule 3: Critical elements visible and in-viewport on initial load * Rule 4: Accessible names on all interactive elements (role+name locators) * - * Runs on both device profiles automatically (playwright.config.ts matrix): - * iphone: iPhone 14 / WebKit / 390×844 - * pixel: Pixel 7 / Chromium / 412×915 + * Runs on all three device profiles automatically (playwright.config.ts matrix): + * iphone: iPhone 14 / WebKit / 390×844 + * pixel: Pixel 7 / Chromium / 412×915 + * desktop: Desktop Chrome / Chromium / 1280×720 (mobile-only geometry tests skipped) * * STRICT-MODE NOTE: * On mobile viewports (≤767px), AppNav renders PhoneNav as a
element @@ -59,7 +60,8 @@ test.describe('Rule 1/3/4 — BottomTabBar tap targets and in-viewport position' 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 // safe-area-inset, so the bar's bottom edge must be ≤ viewport height. const nav = page.getByRole('navigation', { name: 'Main navigation' }); @@ -88,7 +90,8 @@ test.describe('Rule 1/3/4 — BottomTabBar tap targets and in-viewport position' 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) const fab = page.getByRole('button', { name: 'New Event' }); const box = await fab.boundingBox(); @@ -96,6 +99,17 @@ 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!.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 ── @@ -127,7 +141,8 @@ test.describe('Rule 1/3/4 — BottomTabBar on /lists', () => { 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' }); await expect(nav).toBeVisible(); const box = await nav.boundingBox(); From bfc49d199d40a9a86b52edec1b47be79890ba2ae Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Fri, 12 Jun 2026 08:23:10 -0400 Subject: [PATCH 3/4] chore(14-01): update spec headers + README for desktop profile, cosmetic CI step rename - Updated calendar.spec.ts header to list all three profiles (iphone/pixel/desktop) - Updated lists.spec.ts header to list all three profiles (iphone/pixel/desktop) - Updated e2e/README.md preamble to add 'Desktop Chrome (1280x720)' - Added --project=desktop example to README run-commands block - Updated README full-suite command comment to name all three profiles - Cosmetic: ci.yml step-name and comment updated to mention desktop (no plumbing change) - Full suite verified: 85 passed, 5 skipped (3 desktop geometry + 2 parity guards), 0 failed --- .gitea/workflows/ci.yml | 4 ++-- apps/pwa/e2e/README.md | 5 +++-- apps/pwa/e2e/calendar.spec.ts | 7 ++++--- apps/pwa/e2e/lists.spec.ts | 7 ++++--- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 26afc23..a146c0a 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -252,7 +252,7 @@ jobs: # CI=true makes Playwright start Vite :5173 itself (reuseExistingServer=false), use # 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. - - name: Run harness (start API + Playwright iphone + pixel) + - name: Run harness (start API + Playwright iphone + pixel + desktop) env: CI: 'true' # Use 127.0.0.1 (not localhost): the runner image resolves `localhost` to ::1 first, @@ -284,7 +284,7 @@ jobs: done 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 # `--` separator: `pnpm test:e2e -- ` double-forwards the `--` into # `playwright test -- `, where playwright treats --reporter as a test-file filter → diff --git a/apps/pwa/e2e/README.md b/apps/pwa/e2e/README.md index 606284f..54d2509 100644 --- a/apps/pwa/e2e/README.md +++ b/apps/pwa/e2e/README.md @@ -1,6 +1,6 @@ # 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 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 # Single profile (faster local iteration) pnpm --filter @familysync/pwa exec playwright test --project=pixel +pnpm --filter @familysync/pwa exec playwright test --project=desktop # Headed (local debug — shows the browser) pnpm --filter @familysync/pwa exec playwright test --headed diff --git a/apps/pwa/e2e/calendar.spec.ts b/apps/pwa/e2e/calendar.spec.ts index d73d7b1..f2d2d72 100644 --- a/apps/pwa/e2e/calendar.spec.ts +++ b/apps/pwa/e2e/calendar.spec.ts @@ -10,9 +10,10 @@ * 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. * - * Runs on both device profiles automatically (playwright.config.ts matrix): - * iphone: iPhone 14 / WebKit / 390×844 - * pixel: Pixel 7 / Chromium / 412×915 + * Runs on all three device profiles automatically (playwright.config.ts matrix): + * iphone: iPhone 14 / WebKit / 390×844 + * pixel: Pixel 7 / Chromium / 412×915 + * desktop: Desktop Chrome / Chromium / 1280×720 (all tests pass unchanged) * * Run: * pnpm --filter @familysync/pwa test:e2e diff --git a/apps/pwa/e2e/lists.spec.ts b/apps/pwa/e2e/lists.spec.ts index dedacbc..4e68743 100644 --- a/apps/pwa/e2e/lists.spec.ts +++ b/apps/pwa/e2e/lists.spec.ts @@ -13,9 +13,10 @@ * 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). * - * Runs on both device profiles automatically (playwright.config.ts matrix): - * iphone: iPhone 14 / WebKit / 390×844 - * pixel: Pixel 7 / Chromium / 412×915 + * Runs on all three device profiles automatically (playwright.config.ts matrix): + * iphone: iPhone 14 / WebKit / 390×844 + * pixel: Pixel 7 / Chromium / 412×915 + * desktop: Desktop Chrome / Chromium / 1280×720 (all tests pass unchanged) * * Run: * pnpm --filter @familysync/pwa test:e2e From 492adbc8b331f32928208ad7bfc079ead4158e97 Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Fri, 12 Jun 2026 08:24:31 -0400 Subject: [PATCH 4/4] docs(14-01): complete desktop e2e coverage plan - 85 passed, 5 skipped, 0 failed across iphone + pixel + desktop - SC-1/SC-2/SC-3 all satisfied --- .../14-desktop-e2e-coverage/14-01-SUMMARY.md | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 .planning/phases/14-desktop-e2e-coverage/14-01-SUMMARY.md diff --git a/.planning/phases/14-desktop-e2e-coverage/14-01-SUMMARY.md b/.planning/phases/14-desktop-e2e-coverage/14-01-SUMMARY.md new file mode 100644 index 0000000..300e85f --- /dev/null +++ b/.planning/phases/14-desktop-e2e-coverage/14-01-SUMMARY.md @@ -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