Mechanical reformat — no logic changes. 398 files changed, 19125 insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc (singleQuote:true, semi:true, tabWidth:2, trailingComma:all, printWidth:100). Isolated per D-13-08 for reviewability.
183 lines
15 KiB
Markdown
183 lines
15 KiB
Markdown
---
|
|
phase: 07-mobile-test-harness
|
|
plan: 01
|
|
type: execute
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified:
|
|
- apps/pwa/package.json
|
|
- apps/pwa/playwright.config.ts
|
|
- apps/pwa/vitest.config.ts
|
|
- apps/pwa/tsconfig.json
|
|
- package.json
|
|
autonomous: true
|
|
requirements: [TEST-01, TEST-02]
|
|
user_setup: []
|
|
|
|
must_haves:
|
|
truths:
|
|
- 'playwright test --list reports exactly two projects: iphone and pixel'
|
|
- 'Vitest does NOT pick up e2e/*.spec.ts files (no glob collision)'
|
|
- 'Both WebKit and Chromium browser engines are installed for @playwright/test'
|
|
- 'tsc --noEmit passes in apps/pwa with the new playwright.config.ts and e2e/ files in scope'
|
|
artifacts:
|
|
- path: 'apps/pwa/playwright.config.ts'
|
|
provides: 'Two-project device matrix (iPhone/WebKit, Pixel/Chromium), serviceWorkers block, env baseURL, globalSetup ref, vite-only webServer, trace/artifact config'
|
|
contains: "devices['iPhone 14']"
|
|
- path: 'apps/pwa/vitest.config.ts'
|
|
provides: "exclude e2e/** so Vitest's default *.spec.ts glob does not collide with Playwright specs"
|
|
contains: 'exclude'
|
|
- path: 'apps/pwa/package.json'
|
|
provides: '@playwright/test devDependency + test:e2e scripts'
|
|
contains: 'test:e2e'
|
|
key_links:
|
|
- from: 'apps/pwa/playwright.config.ts'
|
|
to: 'apps/pwa/e2e/global-setup.ts'
|
|
via: 'globalSetup config option'
|
|
pattern: 'globalSetup.*global-setup'
|
|
- from: 'apps/pwa/playwright.config.ts'
|
|
to: 'PLAYWRIGHT_BASE_URL env'
|
|
via: 'use.baseURL env-driven'
|
|
pattern: 'PLAYWRIGHT_BASE_URL'
|
|
---
|
|
|
|
<objective>
|
|
Stand up the Playwright test-harness foundation in `apps/pwa`: add `@playwright/test` as a dev dependency, install the WebKit + Chromium browser engines, author `playwright.config.ts` with the two-profile device matrix (iPhone/WebKit + Pixel/Chromium), and isolate the new `e2e/*.spec.ts` glob from the existing Vitest `*.spec.ts` default glob. This is the blocking dependency for the seed plan and all spec plans.
|
|
|
|
Purpose: Every downstream plan (global-setup, layout/calendar/lists specs) imports from `@playwright/test` and runs under this config. Nothing else in the phase can land until the config matrix, browser engines, and glob isolation exist.
|
|
|
|
Output: `apps/pwa/playwright.config.ts`, the `@playwright/test` dev dep + installed browsers, `vitest.config.ts` exclude, package.json scripts, and `e2e/` brought into the `tsc --noEmit` gate.
|
|
</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/07-mobile-test-harness/07-CONTEXT.md
|
|
@.planning/phases/07-mobile-test-harness/07-RESEARCH.md
|
|
@.planning/phases/07-mobile-test-harness/07-PATTERNS.md
|
|
@.planning/phases/07-mobile-test-harness/07-VALIDATION.md
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Install @playwright/test + browser engines, wire package.json scripts</name>
|
|
<files>apps/pwa/package.json, package.json</files>
|
|
<read_first>
|
|
- apps/pwa/package.json — current scripts block (`dev`, `build`, `preview`, `typecheck`, `test`) and devDependencies; mirror naming
|
|
- package.json (root) — workspace script convention: `pnpm --filter @familysync/<app> <script>`, `verb:modifier` naming (e.g. `dev:pwa`, `typecheck`)
|
|
- .planning/phases/07-mobile-test-harness/07-RESEARCH.md § "Installation" + § "Package Legitimacy Audit" — pinned versions and the SUS-but-approved mysql2 note
|
|
- .planning/phases/07-mobile-test-harness/07-PATTERNS.md § "apps/pwa/package.json (modify)" — exact script + devDependency additions
|
|
</read_first>
|
|
<action>
|
|
Install via the pnpm workspace filter (NOT npx, NOT root): `pnpm --filter @familysync/pwa add -D @playwright/test` — pin to 1.60.0 (verified current in RESEARCH.md; do not float to `latest`). Then install both engines with system deps: `pnpm --filter @familysync/pwa exec playwright install --with-deps webkit chromium` (the `--with-deps` flag is mandatory — WebKit on Linux needs system libraries; this is the iphone profile's engine per D-04). Do NOT rely on the global `playwright-cli` Chromium — the harness brings its own browser store. Add three scripts to `apps/pwa/package.json` scripts block: `test:e2e` = `playwright test`, `test:e2e:ui` = `playwright test --ui`, `test:e2e:headed` = `playwright test --headed`. Add a root `package.json` workspace script `test:e2e` = `pnpm --filter @familysync/pwa test:e2e`. Confirm `@playwright/test` lands in `devDependencies` (not `dependencies`). mysql2 is already a project dep (used by apps/api) — do NOT add it here; global-setup (Plan 02) imports the existing one. No checkpoint is needed for the mysql2 SUS verdict per RESEARCH.md (already installed, official package).
|
|
</action>
|
|
<acceptance_criteria>
|
|
- `pnpm --filter @familysync/pwa exec playwright --version` prints a 1.60.x version
|
|
- `apps/pwa/package.json` lists `@playwright/test` under `devDependencies` and has a `test:e2e` script equal to `playwright test`
|
|
- root `package.json` has a `test:e2e` script delegating to `pnpm --filter @familysync/pwa test:e2e`
|
|
- WebKit and Chromium binaries are resolvable: `pnpm --filter @familysync/pwa exec playwright install --dry-run webkit chromium` reports both already installed (or installs cleanly)
|
|
</acceptance_criteria>
|
|
<verify>
|
|
<automated>pnpm --filter @familysync/pwa exec playwright --version</automated>
|
|
</verify>
|
|
<done>@playwright/test@1.60.x is a devDependency in apps/pwa, WebKit+Chromium engines installed, and `test:e2e` scripts exist in both apps/pwa and root package.json.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Author playwright.config.ts (two-profile matrix, SW block, env baseURL, vite webServer)</name>
|
|
<files>apps/pwa/playwright.config.ts</files>
|
|
<read_first>
|
|
- .planning/phases/07-mobile-test-harness/07-RESEARCH.md § "Code Examples — playwright.config.ts (complete)" + § "Pattern 1" — canonical config shape
|
|
- .planning/phases/07-mobile-test-harness/07-PATTERNS.md § "apps/pwa/playwright.config.ts" — config-file shape, analog `defineConfig` convention from vitest.config.ts
|
|
- .planning/phases/07-mobile-test-harness/07-UI-SPEC.md § "Device / Viewport Matrix" + § "Rule 7" + § "Rule 8" — descriptor strings, SW-block precondition, env-driven baseURL contract
|
|
- apps/pwa/vitest.config.ts — `defineConfig` wrapper convention to mirror
|
|
- apps/pwa/vite.config.ts — confirms dev server is :5173 and proxies /api, /health, /callback to :3000 (baseURL points at the vite origin; readiness hits proxied /health)
|
|
</read_first>
|
|
<action>
|
|
Create `apps/pwa/playwright.config.ts` importing `defineConfig, devices` from `@playwright/test`. Set `testDir: './e2e'`, `testMatch: '**/*.spec.ts'`, `fullyParallel: true`, `retries: process.env.CI ? 2 : 0`, `workers: process.env.CI ? 1 : undefined`, `reporter: process.env.CI ? 'github' : 'list'`, and `globalSetup: './e2e/global-setup.ts'` (Plan 02 creates that file — the reference is forward-declared and resolves at run time). In top-level `use`: `baseURL: process.env.PLAYWRIGHT_BASE_URL ?? 'http://localhost:5173'` (env-driven per D-08/Rule 8 — never a hardcoded host), `trace: 'on-first-retry'`, `video: 'on-first-retry'`, `screenshot: 'only-on-failure'`. Define exactly two `projects`: `{ name: 'iphone', use: { ...devices['iPhone 14'], serviceWorkers: 'block' } }` and `{ name: 'pixel', use: { ...devices['Pixel 7'], serviceWorkers: 'block' } }` — exact descriptor strings `'iPhone 14'` (WebKit) and `'Pixel 7'` (Chromium) per D-03/D-04; `serviceWorkers: 'block'` on BOTH per D-02/Pitfall 15. Add a `webServer` block managing vite ONLY (D-10): `command: 'pnpm --filter @familysync/pwa dev'`, `url:` same as baseURL, `reuseExistingServer: !process.env.CI`, `timeout: 120_000`. Do NOT add `storageState` anywhere (D-01/Pitfall 14 — auth comes from DEV_AUTH_BYPASS on the API, not a checked-in state file). Do NOT add `toHaveScreenshot` expectations or snapshot config (UI-SPEC Rule 6 — structural assertions only; Schedule-X drift). The webServer manages vite only — API + MariaDB + Redis stay caller-managed (D-09); do not try to start the API from webServer.
|
|
</action>
|
|
<acceptance_criteria>
|
|
- `pnpm --filter @familysync/pwa exec playwright test --list` lists exactly two projects named `iphone` and `pixel` (it will report 0 tests until specs land — that is expected; the project count is what matters here)
|
|
- the config file contains `serviceWorkers: 'block'` in both project `use` blocks and contains no `storageState` key
|
|
- the config references `PLAYWRIGHT_BASE_URL` for `baseURL` and `globalSetup: './e2e/global-setup.ts'`
|
|
- the config contains `reuseExistingServer: !process.env.CI` and `command: 'pnpm --filter @familysync/pwa dev'` in webServer
|
|
- grep finds no `toHaveScreenshot` and no `storageState` in the file
|
|
</acceptance_criteria>
|
|
<verify>
|
|
<automated>cd apps/pwa && npx playwright test --list 2>&1 | grep -E '\[iphone\]|\[pixel\]|projects' | head; grep -c "serviceWorkers: 'block'" playwright.config.ts</automated>
|
|
</verify>
|
|
<done>`playwright.config.ts` exists with two projects (iphone/WebKit, pixel/Chromium), `serviceWorkers: 'block'` on both, env-driven baseURL, globalSetup ref, vite-only webServer, no storageState, no screenshot assertions.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 3: Isolate Vitest glob + bring e2e/ into the typecheck gate</name>
|
|
<files>apps/pwa/vitest.config.ts, apps/pwa/tsconfig.json</files>
|
|
<read_first>
|
|
- apps/pwa/vitest.config.ts — current `test:` block (environment jsdom, globals, setupFiles, env TZ); no explicit include/exclude today
|
|
- .planning/phases/07-mobile-test-harness/07-PATTERNS.md § "apps/pwa/vitest.config.ts (modify)" — the one-line `exclude` diff
|
|
- .planning/phases/07-mobile-test-harness/07-RESEARCH.md § "Pitfall 1" + § "Anti-Patterns" (Vitest picks up *.spec.ts) — why exclude is mandatory
|
|
- apps/pwa/tsconfig.json — current `include`; confirm whether `e2e/**` is already covered or must be added so playwright.config.ts + e2e specs pass `tsc --noEmit`
|
|
- .planning/phases/07-mobile-test-harness/07-RESEARCH.md § "Project Constraints" — `tsc --noEmit` gate must cover e2e/
|
|
</read_first>
|
|
<action>
|
|
In `apps/pwa/vitest.config.ts`, add `exclude: ['e2e/**', 'node_modules/**']` inside the existing `test:` block. This stops Vitest's default `**/*.{test,spec}.{js,ts,tsx}` glob from picking up `e2e/*.spec.ts` (which import `@playwright/test` and would throw `devices is not defined`/import errors under jsdom — Pitfall 1). Do NOT remove or narrow the existing `environment`, `globals`, `setupFiles`, or `env` keys. Then ensure `apps/pwa/tsconfig.json` brings `playwright.config.ts` and `e2e/**/*.ts` into the typecheck program so they pass the project-wide `tsc --noEmit` gate: if the current `include` is `["src"]` or similar and excludes the new files, add `"playwright.config.ts"` and `"e2e"` to `include` (or widen the glob). Verify `tsc --noEmit` is green after the change — but note e2e/global-setup.ts and the spec files do not exist yet (Plan 02/03/04 create them), so at this point the only e2e file to typecheck is whatever exists; the config + tsconfig wiring is the deliverable here, full e2e typecheck is re-verified per spec plan.
|
|
</action>
|
|
<acceptance_criteria>
|
|
- `apps/pwa/vitest.config.ts` `test:` block contains `exclude: ['e2e/**', 'node_modules/**']`
|
|
- `pnpm --filter @familysync/pwa test` (vitest run) does NOT attempt to run any `e2e/*.spec.ts` file (no `@playwright/test` import errors); existing unit suite still passes
|
|
- `pnpm --filter @familysync/pwa exec tsc --noEmit` exits 0 with `playwright.config.ts` in scope
|
|
- `apps/pwa/tsconfig.json` include covers `playwright.config.ts` and `e2e`
|
|
</acceptance_criteria>
|
|
<verify>
|
|
<automated>cd apps/pwa && pnpm exec tsc --noEmit && pnpm exec vitest run 2>&1 | grep -vi 'e2e/.*spec' | tail -5</automated>
|
|
</verify>
|
|
<done>Vitest excludes `e2e/**`, the existing unit suite is green, and `tsc --noEmit` covers `playwright.config.ts` + `e2e/`.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<threat_model>
|
|
|
|
## Trust Boundaries
|
|
|
|
| Boundary | Description |
|
|
| -------------------- | -------------------------------------------------------------------------------------------------- |
|
|
| harness → dev API | Playwright drives the PWA which calls the API; the API runs with `DEV_AUTH_BYPASS=true` (dev only) |
|
|
| repo → CI/production | config + scripts checked into the repo; must not leak dev-only auth posture into production |
|
|
|
|
## STRIDE Threat Register
|
|
|
|
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
|
| --------- | ---------------------- | ------------------------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| T-07-01 | Elevation of Privilege | `DEV_AUTH_BYPASS=true` reaching production | mitigate | Config sets no auth-bypass itself — bypass is API-side and guarded by `NODE_ENV !== 'production'` (devBypass.ts). This plan documents in the README (Plan 02/04) that production compose MUST NOT set `DEV_AUTH_BYPASS`. The harness config only assumes the dev stack already has it. |
|
|
| T-07-02 | Information Disclosure | checked-in `storageState.json` with a real OIDC session | accept (designed out) | N/A by D-01 — `playwright.config.ts` deliberately omits `storageState`; auth comes from the dev bypass, never a session file. No session cookie is ever serialized into the repo. |
|
|
| T-07-03 | Tampering | `@playwright/test` package install | mitigate | Pinned to 1.60.0 (official Microsoft package, 38.6M wk downloads — RESEARCH.md Package Legitimacy Audit, verdict OK/Approved). No `[ASSUMED]`/`[SUS]`/`[SLOP]` packages introduced; mysql2 (SUS-but-approved) is already a project dep and is not added here. |
|
|
|
|
</threat_model>
|
|
|
|
<verification>
|
|
- `playwright test --list` reports exactly the two projects `iphone` and `pixel`.
|
|
- `vitest run` ignores `e2e/**` (no Playwright import errors); existing unit suite stays green.
|
|
- `tsc --noEmit` green in apps/pwa with `playwright.config.ts` in scope.
|
|
- Config contains `serviceWorkers: 'block'` (both profiles), env-driven baseURL, no `storageState`, no `toHaveScreenshot`.
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
|
|
- `@playwright/test@1.60.x` installed as a devDependency in apps/pwa with WebKit + Chromium engines available.
|
|
- `playwright.config.ts` defines the iPhone/WebKit + Pixel/Chromium matrix with `serviceWorkers: 'block'`, env baseURL, globalSetup ref, and vite-only webServer.
|
|
- Vitest no longer collides with `*.spec.ts`; typecheck gate covers e2e/.
|
|
- `test:e2e` scripts exposed at apps/pwa and root.
|
|
</success_criteria>
|
|
|
|
<output>
|
|
Create `.planning/phases/07-mobile-test-harness/07-01-SUMMARY.md` when done.
|
|
</output>
|