chore: merge executor worktree (worktree-agent-aaafe3c94f936d618)
This commit is contained in:
@@ -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 →
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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,8 @@ 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 +90,8 @@ 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 +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!.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 +141,8 @@ 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