style(13-03): apply Prettier formatting across repo
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.
This commit is contained in:
@@ -7,9 +7,11 @@
|
||||
---
|
||||
|
||||
<user_constraints>
|
||||
|
||||
## User Constraints (from CONTEXT.md)
|
||||
|
||||
### Locked Decisions
|
||||
|
||||
- **D-01:** Auth via `DEV_AUTH_BYPASS=true` on the host-side dev stack — **never** a checked-in `storage-state.json` with an expiring session cookie (Pitfall 14). No Authelia/OIDC mocking. Dev-bypass resolves to Dev User id 1.
|
||||
- **D-02:** Playwright context uses `serviceWorkers: 'block'` so the PWA's `injectManifest` SW (`sw.js`, `registerType: 'autoUpdate'`) cannot intercept requests / return stale cached responses (Pitfall 15). Verify the trace shows no SW-sourced responses.
|
||||
- **D-03:** Run a **two-profile matrix: iPhone + Pixel** — covers both household ecosystems (Apple + Android/Fastmail). The iPhone profile satisfies the hard non-technical-Apple-member UX constraint; Pixel covers Chrome-viewport defects.
|
||||
@@ -22,26 +24,30 @@
|
||||
- **D-10:** Optionally use Playwright `webServer` for **vite only** with `reuseExistingServer: !process.env.CI` (reuse the operator's `pnpm dev` locally, start vite fresh in CI). The API + MariaDB + Redis always stay compose-managed — `webServer` cannot own a multi-container stack.
|
||||
|
||||
### Claude's Discretion
|
||||
|
||||
- **Assertion approach:** lead with structural/role-based locator assertions + explicit tap-target measurements; add `toHaveScreenshot` only if non-flaky cross-environment snapshots are achievable. Schedule-X drift risk weighted heavily.
|
||||
- **Stack lifecycle (D-08–D-10):** planner may refine exact env-var name and webServer wiring.
|
||||
- Spec file location/structure, trace/artifact capture on failure, and npm-script + Makefile wiring — planner's discretion (follow existing conventions).
|
||||
|
||||
### Deferred Ideas (OUT OF SCOPE)
|
||||
|
||||
- Gitea CI pipeline itself (Phase 8 owns it).
|
||||
- Real production-service-worker behavior.
|
||||
- iOS-Safari standalone-PWA behavior (Home-Screen install, standalone OIDC redirect, iOS push).
|
||||
- Live event-create against Fastmail (dev-bypass user 1 has no CalDAV credential).
|
||||
</user_constraints>
|
||||
</user_constraints>
|
||||
|
||||
---
|
||||
|
||||
<phase_requirements>
|
||||
|
||||
## Phase Requirements
|
||||
|
||||
| ID | Description | Research Support |
|
||||
|----|-------------|-----------------|
|
||||
| TEST-01 | The assistant can drive the PWA in a mobile-emulated viewport (device profile + mobile UA + touch) for automated UI/layout verification. | `devices['iPhone 14']` + `devices['Pixel 7']` confirmed in Playwright `@playwright/test` 1.60.0; `projects:` config pattern documented via Context7. |
|
||||
| TEST-02 | Automated runs reach the authenticated PWA via the existing `DEV_AUTH_BYPASS` on the host-side dev stack (no manual login, no Authelia/OIDC mocking). Harness specs consumed by Phase 8 CI as UI-regression step. | `DEV_AUTH_BYPASS` already wired in API; global-setup pattern for readiness gate + DB seeding documented; `baseURL` env-var pattern confirmed. |
|
||||
| ID | Description | Research Support |
|
||||
| ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| TEST-01 | The assistant can drive the PWA in a mobile-emulated viewport (device profile + mobile UA + touch) for automated UI/layout verification. | `devices['iPhone 14']` + `devices['Pixel 7']` confirmed in Playwright `@playwright/test` 1.60.0; `projects:` config pattern documented via Context7. |
|
||||
| TEST-02 | Automated runs reach the authenticated PWA via the existing `DEV_AUTH_BYPASS` on the host-side dev stack (no manual login, no Authelia/OIDC mocking). Harness specs consumed by Phase 8 CI as UI-regression step. | `DEV_AUTH_BYPASS` already wired in API; global-setup pattern for readiness gate + DB seeding documented; `baseURL` env-var pattern confirmed. |
|
||||
|
||||
</phase_requirements>
|
||||
|
||||
---
|
||||
@@ -60,17 +66,17 @@ The primary research question — assertion strategy — is answered: **use stru
|
||||
|
||||
## Architectural Responsibility Map
|
||||
|
||||
| Capability | Primary Tier | Secondary Tier | Rationale |
|
||||
|------------|-------------|----------------|-----------|
|
||||
| Mobile viewport + UA emulation | Test Harness (`@playwright/test`) | — | Playwright `devices[...]` spread into project `use:` options; no app-layer change needed |
|
||||
| Auth bypass | API (existing `devBypass.ts`) | Test Harness (sets `DEV_AUTH_BYPASS=true`) | Bypass is already implemented; harness only ensures env var is set before API starts |
|
||||
| DB seeding | Test Harness (`globalSetup`) | Dev MariaDB (port 3306) | Direct mysql2 connection from global-setup; no API endpoint for seed data |
|
||||
| Stack readiness gate | Test Harness (`globalSetup`) | — | `GET /health` poll via `fetch` with retry loop before any spec runs |
|
||||
| Service worker suppression | Test Harness (context option) | — | `serviceWorkers: 'block'` in `playwright.config.ts` context options; stops Workbox intercept |
|
||||
| Structural assertions (tap target, overflow, visibility, a11y) | Test Harness (spec files) | — | `boundingBox()`, `page.evaluate(scrollWidth)`, `isVisible()`, role-based locators |
|
||||
| API error-state simulation | Test Harness (`page.route()`) | — | Fulfill `/api/events*` with status 500 for error-state tests; no backend change needed |
|
||||
| Vite dev server lifecycle | Test Harness (`webServer`) or Operator | — | `webServer` starts vite if not running; `reuseExistingServer: !process.env.CI` avoids double-start locally |
|
||||
| CI portability | Test Harness (env-driven config) | — | `PLAYWRIGHT_BASE_URL` + `DB_HOST` env vars; no hardcoded `localhost` in spec files |
|
||||
| Capability | Primary Tier | Secondary Tier | Rationale |
|
||||
| -------------------------------------------------------------- | -------------------------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------- |
|
||||
| Mobile viewport + UA emulation | Test Harness (`@playwright/test`) | — | Playwright `devices[...]` spread into project `use:` options; no app-layer change needed |
|
||||
| Auth bypass | API (existing `devBypass.ts`) | Test Harness (sets `DEV_AUTH_BYPASS=true`) | Bypass is already implemented; harness only ensures env var is set before API starts |
|
||||
| DB seeding | Test Harness (`globalSetup`) | Dev MariaDB (port 3306) | Direct mysql2 connection from global-setup; no API endpoint for seed data |
|
||||
| Stack readiness gate | Test Harness (`globalSetup`) | — | `GET /health` poll via `fetch` with retry loop before any spec runs |
|
||||
| Service worker suppression | Test Harness (context option) | — | `serviceWorkers: 'block'` in `playwright.config.ts` context options; stops Workbox intercept |
|
||||
| Structural assertions (tap target, overflow, visibility, a11y) | Test Harness (spec files) | — | `boundingBox()`, `page.evaluate(scrollWidth)`, `isVisible()`, role-based locators |
|
||||
| API error-state simulation | Test Harness (`page.route()`) | — | Fulfill `/api/events*` with status 500 for error-state tests; no backend change needed |
|
||||
| Vite dev server lifecycle | Test Harness (`webServer`) or Operator | — | `webServer` starts vite if not running; `reuseExistingServer: !process.env.CI` avoids double-start locally |
|
||||
| CI portability | Test Harness (env-driven config) | — | `PLAYWRIGHT_BASE_URL` + `DB_HOST` env vars; no hardcoded `localhost` in spec files |
|
||||
|
||||
---
|
||||
|
||||
@@ -78,25 +84,26 @@ The primary research question — assertion strategy — is answered: **use stru
|
||||
|
||||
### Core (new additions for this phase)
|
||||
|
||||
| Library | Version | Purpose | Why Standard |
|
||||
|---------|---------|---------|--------------|
|
||||
| `@playwright/test` | 1.60.0 | Mobile-emulated E2E test runner + assertions | Official Playwright test runner; includes device descriptors, `projects:`, `globalSetup`, `page.route()`, `boundingBox()`, `toHaveScreenshot` (omitted this phase) — the only credible option for WebKit-on-Linux emulation [VERIFIED: npm registry] |
|
||||
| Library | Version | Purpose | Why Standard |
|
||||
| ------------------ | ------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `@playwright/test` | 1.60.0 | Mobile-emulated E2E test runner + assertions | Official Playwright test runner; includes device descriptors, `projects:`, `globalSetup`, `page.route()`, `boundingBox()`, `toHaveScreenshot` (omitted this phase) — the only credible option for WebKit-on-Linux emulation [VERIFIED: npm registry] |
|
||||
|
||||
### Supporting (already in project, used in harness)
|
||||
|
||||
| Library | Version | Purpose | When to Use |
|
||||
|---------|---------|---------|-------------|
|
||||
| Library | Version | Purpose | When to Use |
|
||||
| -------- | ------------------------------ | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `mysql2` | 3.22.5 (already a project dep) | DB connection in global-setup seed script | Direct mysql2 `createConnection` (not Drizzle — global-setup runs outside the API; Drizzle schema not needed for raw INSERT/TRUNCATE) [VERIFIED: npm registry, `SUS` flag — see audit] |
|
||||
|
||||
### Alternatives Considered
|
||||
|
||||
| Instead of | Could Use | Tradeoff |
|
||||
|------------|-----------|----------|
|
||||
| `mysql2` in global-setup | Drizzle ORM | Drizzle is fine but adds unnecessary indirection for 3 TRUNCATE + INSERT statements; raw mysql2 is simpler and already project-resident |
|
||||
| `serviceWorkers: 'block'` | Manual SW unregister in test | `block` is one line of config; unregister requires per-test async setup and is easy to forget |
|
||||
| `page.route()` for error states | Mocking API server | route interception is in-process and doesn't require a separate mock server; the canonical Playwright approach |
|
||||
| Instead of | Could Use | Tradeoff |
|
||||
| ------------------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `mysql2` in global-setup | Drizzle ORM | Drizzle is fine but adds unnecessary indirection for 3 TRUNCATE + INSERT statements; raw mysql2 is simpler and already project-resident |
|
||||
| `serviceWorkers: 'block'` | Manual SW unregister in test | `block` is one line of config; unregister requires per-test async setup and is easy to forget |
|
||||
| `page.route()` for error states | Mocking API server | route interception is in-process and doesn't require a separate mock server; the canonical Playwright approach |
|
||||
|
||||
**Installation:**
|
||||
|
||||
```bash
|
||||
pnpm --filter @familysync/pwa add -D @playwright/test
|
||||
# Install browser engines (both projects: WebKit + Chromium)
|
||||
@@ -104,6 +111,7 @@ pnpm --filter @familysync/pwa exec playwright install --with-deps webkit chromiu
|
||||
```
|
||||
|
||||
**Version verification:**
|
||||
|
||||
```bash
|
||||
npm view @playwright/test version # → 1.60.0 (verified 2026-06-10)
|
||||
npm view mysql2 version # → 3.22.5 (verified 2026-06-10)
|
||||
@@ -113,10 +121,10 @@ npm view mysql2 version # → 3.22.5 (verified 2026-06-10)
|
||||
|
||||
## Package Legitimacy Audit
|
||||
|
||||
| Package | Registry | Age | Downloads | Source Repo | Verdict | Disposition |
|
||||
|---------|----------|-----|-----------|-------------|---------|-------------|
|
||||
| `@playwright/test` | npm | Published 2026-05-11 (latest 1.60.0) | 38.6M/wk | github.com/microsoft/playwright | OK | Approved |
|
||||
| `mysql2` | npm | Published 2026-06-06 (latest 3.22.5) | 11.4M/wk | github.com/sidorares/node-mysql2 | SUS (too-new flag for latest patch) | Approved — already a project dependency; legitimate package, flag is recency of latest patch, not package itself |
|
||||
| Package | Registry | Age | Downloads | Source Repo | Verdict | Disposition |
|
||||
| ------------------ | -------- | ------------------------------------ | --------- | -------------------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
||||
| `@playwright/test` | npm | Published 2026-05-11 (latest 1.60.0) | 38.6M/wk | github.com/microsoft/playwright | OK | Approved |
|
||||
| `mysql2` | npm | Published 2026-06-06 (latest 3.22.5) | 11.4M/wk | github.com/sidorares/node-mysql2 | SUS (too-new flag for latest patch) | Approved — already a project dependency; legitimate package, flag is recency of latest patch, not package itself |
|
||||
|
||||
**Packages removed due to SLOP verdict:** none
|
||||
|
||||
@@ -196,7 +204,7 @@ apps/pwa/
|
||||
```typescript
|
||||
// apps/pwa/playwright.config.ts
|
||||
// Source: Context7 /microsoft/playwright.dev — emulation.mdx + test-global-setup-teardown.mdx
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './e2e',
|
||||
@@ -239,10 +247,11 @@ export default defineConfig({
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120_000,
|
||||
},
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
**Device descriptor confirmed properties:**
|
||||
|
||||
- `devices['iPhone 14']`: viewport `390×844`, userAgent `Mobile Safari`, `hasTouch: true`, `isMobile: true`, `defaultBrowserType: 'webkit'` [VERIFIED: playwright deviceDescriptorsSource.json]
|
||||
- `devices['Pixel 7']`: viewport `412×915`, userAgent `Chrome/Android`, `hasTouch: true`, `isMobile: true`, `defaultBrowserType: 'chromium'` [VERIFIED: playwright deviceDescriptorsSource.json]
|
||||
|
||||
@@ -251,20 +260,20 @@ export default defineConfig({
|
||||
```typescript
|
||||
// apps/pwa/e2e/global-setup.ts
|
||||
// Source: Context7 /microsoft/playwright.dev — test-global-setup-teardown.mdx
|
||||
import mysql from 'mysql2/promise'
|
||||
import mysql from 'mysql2/promise';
|
||||
|
||||
export default async function globalSetup() {
|
||||
// Step 1: Wait for /health — D-08 readiness gate
|
||||
const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? 'http://localhost:5173'
|
||||
const deadline = Date.now() + 60_000
|
||||
const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? 'http://localhost:5173';
|
||||
const deadline = Date.now() + 60_000;
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
const res = await fetch(`${baseURL}/health`)
|
||||
if (res.ok) break
|
||||
const res = await fetch(`${baseURL}/health`);
|
||||
if (res.ok) break;
|
||||
} catch {
|
||||
// ECONNREFUSED — not ready yet
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 1_000))
|
||||
await new Promise((r) => setTimeout(r, 1_000));
|
||||
}
|
||||
// will throw if never resolved — test run fails fast with a clear message
|
||||
|
||||
@@ -275,59 +284,63 @@ export default async function globalSetup() {
|
||||
user: process.env.DB_USER ?? 'familysync',
|
||||
password: process.env.DB_PASSWORD ?? '',
|
||||
database: process.env.DB_NAME ?? 'familysync',
|
||||
})
|
||||
});
|
||||
|
||||
try {
|
||||
// Disable FK checks for TRUNCATE ordering
|
||||
await conn.execute('SET FOREIGN_KEY_CHECKS=0')
|
||||
await conn.execute('TRUNCATE TABLE list_items')
|
||||
await conn.execute('TRUNCATE TABLE list_shares')
|
||||
await conn.execute('TRUNCATE TABLE lists')
|
||||
await conn.execute('TRUNCATE TABLE calendar_events')
|
||||
await conn.execute('SET FOREIGN_KEY_CHECKS=1')
|
||||
await conn.execute('SET FOREIGN_KEY_CHECKS=0');
|
||||
await conn.execute('TRUNCATE TABLE list_items');
|
||||
await conn.execute('TRUNCATE TABLE list_shares');
|
||||
await conn.execute('TRUNCATE TABLE lists');
|
||||
await conn.execute('TRUNCATE TABLE calendar_events');
|
||||
await conn.execute('SET FOREIGN_KEY_CHECKS=1');
|
||||
|
||||
// Seed: one calendar event on shared calendar id=10 (timed, not all-day)
|
||||
// Minimal VCALENDAR string — enough for the API to expand and the UI to show it
|
||||
const futureStart = new Date(Date.now() + 24 * 60 * 60 * 1000) // tomorrow
|
||||
const futureStartUtc = futureStart.toISOString().replace('T', 'T').replace(/\.\d+Z$/, 'Z')
|
||||
const uid = 'e2e-seed-event-001'
|
||||
const futureStart = new Date(Date.now() + 24 * 60 * 60 * 1000); // tomorrow
|
||||
const futureStartUtc = futureStart
|
||||
.toISOString()
|
||||
.replace('T', 'T')
|
||||
.replace(/\.\d+Z$/, 'Z');
|
||||
const uid = 'e2e-seed-event-001';
|
||||
const rawVevent = [
|
||||
'BEGIN:VCALENDAR',
|
||||
'VERSION:2.0',
|
||||
'BEGIN:VEVENT',
|
||||
`UID:${uid}`,
|
||||
`DTSTART:${futureStart.toISOString().replace(/[-:]/g, '').replace(/\.\d+Z$/, 'Z')}`,
|
||||
`DTSTART:${futureStart
|
||||
.toISOString()
|
||||
.replace(/[-:]/g, '')
|
||||
.replace(/\.\d+Z$/, 'Z')}`,
|
||||
`SUMMARY:Seeded Test Event`,
|
||||
'END:VEVENT',
|
||||
'END:VCALENDAR',
|
||||
].join('\r\n')
|
||||
].join('\r\n');
|
||||
await conn.execute(
|
||||
`INSERT INTO calendar_events
|
||||
(calendar_id, uid, etag, raw_vevent, title, dtstart_utc, all_day, has_rrule)
|
||||
VALUES (10, ?, 'e2e-etag-001', ?, 'Seeded Test Event', ?, false, false)`,
|
||||
[uid, rawVevent, futureStartUtc],
|
||||
)
|
||||
);
|
||||
|
||||
// Seed: one list with two items for user 1
|
||||
const [listResult] = await conn.execute(
|
||||
const [listResult] = (await conn.execute(
|
||||
`INSERT INTO lists (owner_id, name, is_shared) VALUES (1, 'E2E Grocery List', true)`,
|
||||
) as any[]
|
||||
const listId = listResult.insertId
|
||||
await conn.execute(
|
||||
`INSERT INTO list_shares (list_id, user_id) VALUES (?, 1)`,
|
||||
[listId],
|
||||
)
|
||||
)) as any[];
|
||||
const listId = listResult.insertId;
|
||||
await conn.execute(`INSERT INTO list_shares (list_id, user_id) VALUES (?, 1)`, [listId]);
|
||||
await conn.execute(
|
||||
`INSERT INTO list_items (list_id, text, checked, rank) VALUES (?, 'Milk', false, 'a0'), (?, 'Eggs', false, 'a1')`,
|
||||
[listId, listId],
|
||||
)
|
||||
);
|
||||
} finally {
|
||||
await conn.end()
|
||||
await conn.end();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Notes for planner:**
|
||||
|
||||
- The `calendar_events.dtstart_utc` type is `TIMESTAMP`, which MariaDB stores in UTC. Pass an ISO UTC string.
|
||||
- `calendar_id=10` is the confirmed shared calendar from project memory `dev-data-user1-no-calendars`. The seed assumes this row pre-exists (it does on the dev stack); the planner may add an `INSERT IGNORE INTO calendars ...` guard for CI resilience.
|
||||
- `list_shares` ensures user 1 can see the list in `/api/lists`.
|
||||
@@ -337,21 +350,21 @@ export default async function globalSetup() {
|
||||
|
||||
```typescript
|
||||
// Source: Context7 /microsoft/playwright.dev — api/class-locator.mdx + UI-SPEC.md Rule 1
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('BottomTabBar tabs meet 44px touch target', async ({ page }) => {
|
||||
await page.goto('/calendar')
|
||||
|
||||
const calTab = page.getByRole('link', { name: 'Calendar' })
|
||||
const listsTab = page.getByRole('link', { name: 'Lists' })
|
||||
|
||||
await page.goto('/calendar');
|
||||
|
||||
const calTab = page.getByRole('link', { name: 'Calendar' });
|
||||
const listsTab = page.getByRole('link', { name: 'Lists' });
|
||||
|
||||
for (const el of [calTab, listsTab]) {
|
||||
const box = await el.boundingBox()
|
||||
expect(box).not.toBeNull()
|
||||
expect(box!.width).toBeGreaterThanOrEqual(44)
|
||||
expect(box!.height).toBeGreaterThanOrEqual(44)
|
||||
const box = await el.boundingBox();
|
||||
expect(box).not.toBeNull();
|
||||
expect(box!.width).toBeGreaterThanOrEqual(44);
|
||||
expect(box!.height).toBeGreaterThanOrEqual(44);
|
||||
}
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
**API confirmation:** `locator.boundingBox()` returns `{ x, y, width, height }` in CSS pixels (logical pixels at `scale: 'css'`). Returns `null` if element not visible. [CITED: context7 /microsoft/playwright.dev — api/class-locator.mdx]
|
||||
@@ -363,8 +376,8 @@ test('BottomTabBar tabs meet 44px touch target', async ({ page }) => {
|
||||
const overflow = await page.evaluate(() => ({
|
||||
scrollWidth: document.documentElement.scrollWidth,
|
||||
clientWidth: document.documentElement.clientWidth,
|
||||
}))
|
||||
expect(overflow.scrollWidth).toBeLessThanOrEqual(overflow.clientWidth)
|
||||
}));
|
||||
expect(overflow.scrollWidth).toBeLessThanOrEqual(overflow.clientWidth);
|
||||
```
|
||||
|
||||
This is `page.evaluate()` — standard Playwright API, no special library needed. [CITED: context7 /microsoft/playwright.dev]
|
||||
@@ -376,10 +389,10 @@ This is `page.evaluate()` — standard Playwright API, no special library needed
|
||||
// Use BEFORE page.goto() — route must be registered before navigation
|
||||
await page.route('/api/events*', (route) =>
|
||||
route.fulfill({ status: 500, body: JSON.stringify({ error: 'simulated' }) }),
|
||||
)
|
||||
await page.goto('/calendar')
|
||||
await expect(page.getByRole('heading', { name: "Couldn't load events" })).toBeVisible()
|
||||
await expect(page.getByRole('button', { name: 'Retry' })).toBeVisible()
|
||||
);
|
||||
await page.goto('/calendar');
|
||||
await expect(page.getByRole('heading', { name: "Couldn't load events" })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Retry' })).toBeVisible();
|
||||
```
|
||||
|
||||
**Important:** Call `await page.unroute('/api/events*')` or use `page.route` with `{ times: 1 }` to prevent the mock from leaking to subsequent tests. [CITED: context7 /microsoft/playwright.dev — network.mdx]
|
||||
@@ -415,13 +428,13 @@ webServer: {
|
||||
|
||||
## Don't Hand-Roll
|
||||
|
||||
| Problem | Don't Build | Use Instead | Why |
|
||||
|---------|-------------|-------------|-----|
|
||||
| Mobile viewport + touch + UA string | Custom browser launch flags | `devices['iPhone 14']` spread | `devices` includes DPR, hasTouch, isMobile, UA — reproducing this by hand misses fields and drifts with Playwright updates |
|
||||
| Service worker suppression | Per-test SW unregister evaluate | `serviceWorkers: 'block'` context option | One config line; unregister requires async setup in every test and is easy to omit |
|
||||
| Stack readiness polling | `sleep 10 && run tests` | `globalSetup` health-poll loop | Sleep is non-deterministic; a poll with timeout gives fast-pass and hard-fail |
|
||||
| API error states | Separate mock API server | `page.route()` inline fulfill | route() is in-process, zero infrastructure, the Playwright-canonical approach |
|
||||
| DB seeding from the API | POST requests to API endpoints | Direct mysql2 INSERT in globalSetup | Dev-bypass user 1 cannot create calendar events via API (422, no CalDAV credential); direct DB insert bypasses that constraint and is faster |
|
||||
| Problem | Don't Build | Use Instead | Why |
|
||||
| ----------------------------------- | ------------------------------- | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Mobile viewport + touch + UA string | Custom browser launch flags | `devices['iPhone 14']` spread | `devices` includes DPR, hasTouch, isMobile, UA — reproducing this by hand misses fields and drifts with Playwright updates |
|
||||
| Service worker suppression | Per-test SW unregister evaluate | `serviceWorkers: 'block'` context option | One config line; unregister requires async setup in every test and is easy to omit |
|
||||
| Stack readiness polling | `sleep 10 && run tests` | `globalSetup` health-poll loop | Sleep is non-deterministic; a poll with timeout gives fast-pass and hard-fail |
|
||||
| API error states | Separate mock API server | `page.route()` inline fulfill | route() is in-process, zero infrastructure, the Playwright-canonical approach |
|
||||
| DB seeding from the API | POST requests to API endpoints | Direct mysql2 INSERT in globalSetup | Dev-bypass user 1 cannot create calendar events via API (422, no CalDAV credential); direct DB insert bypasses that constraint and is faster |
|
||||
|
||||
**Key insight:** Playwright's device descriptors, `serviceWorkers` context option, and `page.route()` network interception are designed precisely for this use case. The only custom code needed is the globalSetup health poll and DB seed script.
|
||||
|
||||
@@ -435,13 +448,13 @@ webServer: {
|
||||
|
||||
The UI-SPEC.md defines five concrete quality rules, all of which map directly to Playwright structural APIs with zero rendering-pipeline variance:
|
||||
|
||||
| Rule | Playwright API | Variance Risk |
|
||||
|------|---------------|--------------|
|
||||
| Touch target ≥ 44px | `locator.boundingBox()` → measure width/height | None — CSS pixel dimensions are layout-engine output, consistent across OS font settings |
|
||||
| No horizontal overflow | `page.evaluate(() => scrollWidth/clientWidth)` | None — DOM measurement, not pixel comparison |
|
||||
| Elements in viewport on load | `locator.isVisible()` + `boundingBox().y + height ≤ viewportHeight` | None — geometry check |
|
||||
| Accessible names present | `page.getByRole(role, { name })` — if locatable, name exists | None — ARIA tree query |
|
||||
| Empty/error states render | `getByText()`, `getByRole()` visibility | None — presence check |
|
||||
| Rule | Playwright API | Variance Risk |
|
||||
| ---------------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| Touch target ≥ 44px | `locator.boundingBox()` → measure width/height | None — CSS pixel dimensions are layout-engine output, consistent across OS font settings |
|
||||
| No horizontal overflow | `page.evaluate(() => scrollWidth/clientWidth)` | None — DOM measurement, not pixel comparison |
|
||||
| Elements in viewport on load | `locator.isVisible()` + `boundingBox().y + height ≤ viewportHeight` | None — geometry check |
|
||||
| Accessible names present | `page.getByRole(role, { name })` — if locatable, name exists | None — ARIA tree query |
|
||||
| Empty/error states render | `getByText()`, `getByRole()` visibility | None — presence check |
|
||||
|
||||
### Why `toHaveScreenshot` is excluded
|
||||
|
||||
@@ -528,7 +541,7 @@ The UI-SPEC.md defines five concrete quality rules, all of which map directly to
|
||||
```typescript
|
||||
// apps/pwa/playwright.config.ts
|
||||
// Source: Context7 /microsoft/playwright.dev
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './e2e',
|
||||
@@ -569,14 +582,14 @@ export default defineConfig({
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120_000,
|
||||
},
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
### vitest.config.ts patch (add exclude)
|
||||
|
||||
```typescript
|
||||
// apps/pwa/vitest.config.ts — add exclude to prevent Vitest from picking up e2e specs
|
||||
import { defineConfig } from 'vitest/config'
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
@@ -584,9 +597,9 @@ export default defineConfig({
|
||||
globals: true,
|
||||
setupFiles: ['./src/test-setup.ts'],
|
||||
env: { TZ: 'UTC' },
|
||||
exclude: ['e2e/**', 'node_modules/**'], // ← ADD THIS
|
||||
exclude: ['e2e/**', 'node_modules/**'], // ← ADD THIS
|
||||
},
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
### package.json scripts additions
|
||||
@@ -602,6 +615,7 @@ export default defineConfig({
|
||||
```
|
||||
|
||||
**Root workspace script (for pnpm filter):**
|
||||
|
||||
```bash
|
||||
pnpm --filter @familysync/pwa test:e2e
|
||||
```
|
||||
@@ -611,63 +625,64 @@ pnpm --filter @familysync/pwa test:e2e
|
||||
```typescript
|
||||
// apps/pwa/e2e/layout.spec.ts
|
||||
// Source: UI-SPEC.md Rules 1-4 + Context7 /microsoft/playwright.dev
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('BottomTabBar presence and tap targets', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/calendar')
|
||||
})
|
||||
await page.goto('/calendar');
|
||||
});
|
||||
|
||||
test('BottomTabBar is present at mobile width', async ({ page }) => {
|
||||
await expect(page.getByRole('navigation', { name: 'Main navigation' })).toBeVisible()
|
||||
})
|
||||
await expect(page.getByRole('navigation', { name: 'Main navigation' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('Calendar tab meets 44px touch target', async ({ page }) => {
|
||||
const tab = page.getByRole('link', { name: 'Calendar' })
|
||||
const box = await tab.boundingBox()
|
||||
expect(box).not.toBeNull()
|
||||
expect(box!.width).toBeGreaterThanOrEqual(44)
|
||||
expect(box!.height).toBeGreaterThanOrEqual(44)
|
||||
})
|
||||
const tab = page.getByRole('link', { name: 'Calendar' });
|
||||
const box = await tab.boundingBox();
|
||||
expect(box).not.toBeNull();
|
||||
expect(box!.width).toBeGreaterThanOrEqual(44);
|
||||
expect(box!.height).toBeGreaterThanOrEqual(44);
|
||||
});
|
||||
|
||||
test('no horizontal overflow on /calendar', async ({ page }) => {
|
||||
const overflow = await page.evaluate(() => ({
|
||||
scrollWidth: document.documentElement.scrollWidth,
|
||||
clientWidth: document.documentElement.clientWidth,
|
||||
}))
|
||||
expect(overflow.scrollWidth).toBeLessThanOrEqual(overflow.clientWidth)
|
||||
})
|
||||
})
|
||||
}));
|
||||
expect(overflow.scrollWidth).toBeLessThanOrEqual(overflow.clientWidth);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Error state — /calendar', () => {
|
||||
test('shows error heading and Retry button when API returns 500', async ({ page }) => {
|
||||
await page.route('/api/events*', (route) =>
|
||||
route.fulfill({ status: 500, body: JSON.stringify({ error: 'simulated' }) }),
|
||||
)
|
||||
await page.goto('/calendar')
|
||||
await expect(page.getByRole('heading', { name: "Couldn't load events" })).toBeVisible()
|
||||
await expect(page.getByRole('button', { name: 'Retry' })).toBeVisible()
|
||||
);
|
||||
await page.goto('/calendar');
|
||||
await expect(page.getByRole('heading', { name: "Couldn't load events" })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Retry' })).toBeVisible();
|
||||
// error state must also pass overflow rule
|
||||
const overflow = await page.evaluate(() => ({
|
||||
scrollWidth: document.documentElement.scrollWidth,
|
||||
clientWidth: document.documentElement.clientWidth,
|
||||
}))
|
||||
expect(overflow.scrollWidth).toBeLessThanOrEqual(overflow.clientWidth)
|
||||
})
|
||||
})
|
||||
}));
|
||||
expect(overflow.scrollWidth).toBeLessThanOrEqual(overflow.clientWidth);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## State of the Art
|
||||
|
||||
| Old Approach | Current Approach | When Changed | Impact |
|
||||
|--------------|-----------------|--------------|--------|
|
||||
| `playwright-cli` global tool (Chromium desktop only) | `@playwright/test` with `devices[...]` projects (WebKit + Chromium, mobile viewport) | This phase | Playwright-cli remains for interactive assistant smoke tests; `@playwright/test` is for automated regression |
|
||||
| No E2E tests — `playwright-cli` used ad-hoc | Structured `e2e/` spec files with globalSetup + device matrix | This phase | Mobile layout defects caught automatically instead of by operator on real devices |
|
||||
| `toHaveScreenshot` visual regression | Structural assertions (boundingBox, overflow eval, role/name locators) | Deliberate decision — UI-SPEC §Rule 6 | Lower maintenance, zero rendering-pipeline variance, sufficient quality coverage for this app |
|
||||
| Old Approach | Current Approach | When Changed | Impact |
|
||||
| ---------------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
||||
| `playwright-cli` global tool (Chromium desktop only) | `@playwright/test` with `devices[...]` projects (WebKit + Chromium, mobile viewport) | This phase | Playwright-cli remains for interactive assistant smoke tests; `@playwright/test` is for automated regression |
|
||||
| No E2E tests — `playwright-cli` used ad-hoc | Structured `e2e/` spec files with globalSetup + device matrix | This phase | Mobile layout defects caught automatically instead of by operator on real devices |
|
||||
| `toHaveScreenshot` visual regression | Structural assertions (boundingBox, overflow eval, role/name locators) | Deliberate decision — UI-SPEC §Rule 6 | Lower maintenance, zero rendering-pipeline variance, sufficient quality coverage for this app |
|
||||
|
||||
**Deprecated/outdated patterns for this codebase:**
|
||||
|
||||
- `storageState.json` for Playwright auth: never appropriate here; `DEV_AUTH_BYPASS` is the correct pattern. [CITED: PITFALLS.md §Pitfall 14]
|
||||
- `serviceWorkers: 'allow'` (default): would allow Workbox cache-first to intercept API calls. [CITED: PITFALLS.md §Pitfall 15]
|
||||
|
||||
@@ -675,12 +690,12 @@ test.describe('Error state — /calendar', () => {
|
||||
|
||||
## Assumptions Log
|
||||
|
||||
| # | Claim | Section | Risk if Wrong |
|
||||
|---|-------|---------|---------------|
|
||||
| A1 | `fractional-indexing` rank strings `'a0'`, `'a1'` are valid initial ranks for seed items | Pattern 2 (global-setup) | Seed succeeds but list items sort incorrectly; items may not appear in expected order in UI |
|
||||
| A2 | Playwright `webServer` starts before `globalSetup` is called | Pitfall 3 | globalSetup health poll would time out if Vite isn't started yet; ordering must be confirmed against docs |
|
||||
| A3 | `mysql2` `SUS` verdict is a false positive due to recent patch release | Package Audit | Not a concern — package is already in the project; would only matter if upgrading to the latest patch caused issues |
|
||||
| A4 | Linux WebKit font rendering differs from macOS enough to cause `toHaveScreenshot` failures | Primary Research Q | If wrong, screenshots could be added with `maxDiffPixelRatio: 0.03`; structural assertions remain the lower-risk choice |
|
||||
| # | Claim | Section | Risk if Wrong |
|
||||
| --- | ------------------------------------------------------------------------------------------ | ------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| A1 | `fractional-indexing` rank strings `'a0'`, `'a1'` are valid initial ranks for seed items | Pattern 2 (global-setup) | Seed succeeds but list items sort incorrectly; items may not appear in expected order in UI |
|
||||
| A2 | Playwright `webServer` starts before `globalSetup` is called | Pitfall 3 | globalSetup health poll would time out if Vite isn't started yet; ordering must be confirmed against docs |
|
||||
| A3 | `mysql2` `SUS` verdict is a false positive due to recent patch release | Package Audit | Not a concern — package is already in the project; would only matter if upgrading to the latest patch caused issues |
|
||||
| A4 | Linux WebKit font rendering differs from macOS enough to cause `toHaveScreenshot` failures | Primary Research Q | If wrong, screenshots could be added with `maxDiffPixelRatio: 0.03`; structural assertions remain the lower-risk choice |
|
||||
|
||||
---
|
||||
|
||||
@@ -705,19 +720,21 @@ test.describe('Error state — /calendar', () => {
|
||||
|
||||
## Environment Availability
|
||||
|
||||
| Dependency | Required By | Available | Version | Fallback |
|
||||
|------------|------------|-----------|---------|----------|
|
||||
| Node.js 22 LTS | global-setup (fetch native, mysql2) | ✓ (assumed) | 22.x | — |
|
||||
| Dev MariaDB :3306 (port-bound) | global-setup DB seed | ✓ when dev stack is up via `docker-compose.dev.yml` | MariaDB 11 | Seed step skipped gracefully — tests run with empty DB (empty-state assertions still valid) |
|
||||
| Vite dev server :5173 | All spec files | ✓ via `webServer` or operator's `pnpm dev` | Vite 8.0.16 | — |
|
||||
| API :3000 with DEV_AUTH_BYPASS=true | All spec files (via Vite proxy) | ✓ when dev stack is up | Node 22 + Hono | — |
|
||||
| WebKit browser binary | iPhone project | ✗ (not yet installed) | — | Must run `playwright install --with-deps webkit` |
|
||||
| Chromium browser binary | Pixel project | ✓ (used by playwright-cli skill) | Chromium (via playwright-cli) | May need re-install via `@playwright/test`'s own browser store |
|
||||
| Dependency | Required By | Available | Version | Fallback |
|
||||
| ----------------------------------- | ----------------------------------- | --------------------------------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| Node.js 22 LTS | global-setup (fetch native, mysql2) | ✓ (assumed) | 22.x | — |
|
||||
| Dev MariaDB :3306 (port-bound) | global-setup DB seed | ✓ when dev stack is up via `docker-compose.dev.yml` | MariaDB 11 | Seed step skipped gracefully — tests run with empty DB (empty-state assertions still valid) |
|
||||
| Vite dev server :5173 | All spec files | ✓ via `webServer` or operator's `pnpm dev` | Vite 8.0.16 | — |
|
||||
| API :3000 with DEV_AUTH_BYPASS=true | All spec files (via Vite proxy) | ✓ when dev stack is up | Node 22 + Hono | — |
|
||||
| WebKit browser binary | iPhone project | ✗ (not yet installed) | — | Must run `playwright install --with-deps webkit` |
|
||||
| Chromium browser binary | Pixel project | ✓ (used by playwright-cli skill) | Chromium (via playwright-cli) | May need re-install via `@playwright/test`'s own browser store |
|
||||
|
||||
**Missing dependencies with no fallback:**
|
||||
|
||||
- WebKit browser binary — required for the `iphone` project. Must be installed via `playwright install --with-deps webkit` as part of Phase 7 Wave 0.
|
||||
|
||||
**Missing dependencies with fallback:**
|
||||
|
||||
- Dev MariaDB port binding — if compose isn't up, the seed is skipped; specs run with empty DB, exercising empty-state assertions only (partial coverage, but not a hard failure).
|
||||
|
||||
---
|
||||
@@ -726,23 +743,23 @@ test.describe('Error state — /calendar', () => {
|
||||
|
||||
### Test Framework
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Framework | `@playwright/test` 1.60.0 |
|
||||
| Config file | `apps/pwa/playwright.config.ts` (Wave 0 — new file) |
|
||||
| Property | Value |
|
||||
| ------------------------------- | -------------------------------------------------------------------- |
|
||||
| Framework | `@playwright/test` 1.60.0 |
|
||||
| Config file | `apps/pwa/playwright.config.ts` (Wave 0 — new file) |
|
||||
| Quick run command (one profile) | `pnpm --filter @familysync/pwa exec playwright test --project=pixel` |
|
||||
| Full suite command | `pnpm --filter @familysync/pwa exec playwright test` |
|
||||
| Headed (local debug) | `pnpm --filter @familysync/pwa exec playwright test --headed` |
|
||||
| Full suite command | `pnpm --filter @familysync/pwa exec playwright test` |
|
||||
| Headed (local debug) | `pnpm --filter @familysync/pwa exec playwright test --headed` |
|
||||
|
||||
### Phase Requirements → Test Map
|
||||
|
||||
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|
||||
|--------|----------|-----------|------------------|-------------|
|
||||
| TEST-01 | PWA loads in mobile-emulated viewport (390px / 412px), touch-capable, mobile UA | E2E (Playwright) | `pnpm --filter @familysync/pwa exec playwright test` | ❌ Wave 0 — create `e2e/layout.spec.ts` |
|
||||
| TEST-01 | Tap targets ≥ 44px on BottomTabBar, FAB, Retry, settings button | E2E (Playwright) | same | ❌ Wave 0 — `e2e/layout.spec.ts` |
|
||||
| TEST-01 | No horizontal overflow on `/calendar`, `/lists` | E2E (Playwright) | same | ❌ Wave 0 — `e2e/layout.spec.ts` |
|
||||
| TEST-02 | Reaches authenticated PWA via `DEV_AUTH_BYPASS` (no manual login) | E2E (Playwright) | same | ❌ Wave 0 — `e2e/global-setup.ts` enforces auth precondition |
|
||||
| TEST-02 | Harness runs headlessly, CI-portable (env-driven baseURL, readiness gate) | E2E (Playwright) | `CI=true pnpm --filter @familysync/pwa exec playwright test` | ❌ Wave 0 — `playwright.config.ts` |
|
||||
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|
||||
| ------- | ------------------------------------------------------------------------------- | ---------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| TEST-01 | PWA loads in mobile-emulated viewport (390px / 412px), touch-capable, mobile UA | E2E (Playwright) | `pnpm --filter @familysync/pwa exec playwright test` | ❌ Wave 0 — create `e2e/layout.spec.ts` |
|
||||
| TEST-01 | Tap targets ≥ 44px on BottomTabBar, FAB, Retry, settings button | E2E (Playwright) | same | ❌ Wave 0 — `e2e/layout.spec.ts` |
|
||||
| TEST-01 | No horizontal overflow on `/calendar`, `/lists` | E2E (Playwright) | same | ❌ Wave 0 — `e2e/layout.spec.ts` |
|
||||
| TEST-02 | Reaches authenticated PWA via `DEV_AUTH_BYPASS` (no manual login) | E2E (Playwright) | same | ❌ Wave 0 — `e2e/global-setup.ts` enforces auth precondition |
|
||||
| TEST-02 | Harness runs headlessly, CI-portable (env-driven baseURL, readiness gate) | E2E (Playwright) | `CI=true pnpm --filter @familysync/pwa exec playwright test` | ❌ Wave 0 — `playwright.config.ts` |
|
||||
|
||||
### Harness Self-Validation (the harness must prove it works)
|
||||
|
||||
@@ -781,35 +798,35 @@ This phase's deliverable IS the test infrastructure. The harness is validated wh
|
||||
|
||||
### Applicable ASVS Categories
|
||||
|
||||
| ASVS Category | Applies | Standard Control |
|
||||
|---------------|---------|-----------------|
|
||||
| V2 Authentication | Yes (test auth path) | `DEV_AUTH_BYPASS=true` — never real credentials in test env; bypass is dev-only (guarded by `NODE_ENV !== 'production'`) |
|
||||
| V3 Session Management | No | DEV_AUTH_BYPASS bypasses session cookies entirely |
|
||||
| V4 Access Control | No | Harness tests as user 1; no privilege escalation in scope |
|
||||
| V5 Input Validation | No | Harness is read-only; no form submission in scope |
|
||||
| V6 Cryptography | No | No crypto operations in test harness |
|
||||
| ASVS Category | Applies | Standard Control |
|
||||
| --------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| V2 Authentication | Yes (test auth path) | `DEV_AUTH_BYPASS=true` — never real credentials in test env; bypass is dev-only (guarded by `NODE_ENV !== 'production'`) |
|
||||
| V3 Session Management | No | DEV_AUTH_BYPASS bypasses session cookies entirely |
|
||||
| V4 Access Control | No | Harness tests as user 1; no privilege escalation in scope |
|
||||
| V5 Input Validation | No | Harness is read-only; no form submission in scope |
|
||||
| V6 Cryptography | No | No crypto operations in test harness |
|
||||
|
||||
### Known Threat Patterns for this stack
|
||||
|
||||
| Pattern | STRIDE | Standard Mitigation |
|
||||
|---------|--------|---------------------|
|
||||
| `DEV_AUTH_BYPASS=true` active in production | Elevation of Privilege | API guards on `NODE_ENV !== 'production'` — production compose MUST NOT set this variable. The harness README must document this. |
|
||||
| `storage-state.json` with real OIDC session committed to repo | Information Disclosure | Not applicable — `storageState` is never used in this harness (D-01). |
|
||||
| DB seed credentials in test script | Information Disclosure | Use env vars for DB credentials in globalSetup (`DB_HOST`, `DB_PASSWORD`); no hardcoded credentials. |
|
||||
| Pattern | STRIDE | Standard Mitigation |
|
||||
| ------------------------------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `DEV_AUTH_BYPASS=true` active in production | Elevation of Privilege | API guards on `NODE_ENV !== 'production'` — production compose MUST NOT set this variable. The harness README must document this. |
|
||||
| `storage-state.json` with real OIDC session committed to repo | Information Disclosure | Not applicable — `storageState` is never used in this harness (D-01). |
|
||||
| DB seed credentials in test script | Information Disclosure | Use env vars for DB credentials in globalSetup (`DB_HOST`, `DB_PASSWORD`); no hardcoded credentials. |
|
||||
|
||||
---
|
||||
|
||||
## Project Constraints (from CLAUDE.md)
|
||||
|
||||
| Directive | Impact on Phase 7 |
|
||||
|-----------|-------------------|
|
||||
| MariaDB only — no PostgreSQL | seed script uses `mysql2`; no pg driver |
|
||||
| pnpm workspace | all installs via `pnpm --filter @familysync/pwa add`; scripts via `pnpm --filter @familysync/pwa exec playwright` |
|
||||
| No Makefile (root Makefile does not exist) | scripts exposed via `package.json` `scripts` in `apps/pwa` and root workspace; no Makefile to update |
|
||||
| playwright-cli is global Chromium only | `@playwright/test` brings its own browser store; no conflict with playwright-cli; the two tools coexist |
|
||||
| playwright-cli skill exception for iOS-Safari-standalone | real device checks (Home Screen install, iOS push) remain human gates — NOT in scope for this harness |
|
||||
| Vitest for unit tests | `*.spec.ts` glob collision must be resolved via `vitest.config.ts` exclude |
|
||||
| `tsc --noEmit` gate (both apps) | `playwright.config.ts` and `e2e/*.ts` files must pass typecheck; add to root `typecheck` script or ensure `apps/pwa/tsconfig.json` includes `e2e/` |
|
||||
| Directive | Impact on Phase 7 |
|
||||
| -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| MariaDB only — no PostgreSQL | seed script uses `mysql2`; no pg driver |
|
||||
| pnpm workspace | all installs via `pnpm --filter @familysync/pwa add`; scripts via `pnpm --filter @familysync/pwa exec playwright` |
|
||||
| No Makefile (root Makefile does not exist) | scripts exposed via `package.json` `scripts` in `apps/pwa` and root workspace; no Makefile to update |
|
||||
| playwright-cli is global Chromium only | `@playwright/test` brings its own browser store; no conflict with playwright-cli; the two tools coexist |
|
||||
| playwright-cli skill exception for iOS-Safari-standalone | real device checks (Home Screen install, iOS push) remain human gates — NOT in scope for this harness |
|
||||
| Vitest for unit tests | `*.spec.ts` glob collision must be resolved via `vitest.config.ts` exclude |
|
||||
| `tsc --noEmit` gate (both apps) | `playwright.config.ts` and `e2e/*.ts` files must pass typecheck; add to root `typecheck` script or ensure `apps/pwa/tsconfig.json` includes `e2e/` |
|
||||
|
||||
---
|
||||
|
||||
@@ -844,6 +861,7 @@ This phase's deliverable IS the test infrastructure. The harness is validated wh
|
||||
## Metadata
|
||||
|
||||
**Confidence breakdown:**
|
||||
|
||||
- Standard stack: HIGH — `@playwright/test` 1.60.0 verified via npm; device descriptors verified via source file; mysql2 confirmed existing dep
|
||||
- Architecture: HIGH — patterns derived from existing project conventions (vitest config, DB client env vars, devBypass) + Context7 Playwright docs
|
||||
- Pitfalls: HIGH — Pitfalls 1/2/5/6 derived from reading actual project files; Pitfall 3/4 from reasoning about CI ordering
|
||||
|
||||
Reference in New Issue
Block a user