Closes three items from the outstanding e2e/CI gap list: - Timing budgets: timings.measure() only ever recorded durations for the (still-unwired) Pushgateway export — nothing failed CI when a step got slow. Add e2e/timing-budgets.ts (expectWithinBudget + shared BUDGET_MS buckets) and wire it into every measure() call site across the suite. Mail-wait steps are deliberately left unbudgeted (external mail-trap delivery latency, not a code performance signal). - actions/upload-artifact@v4 doesn't work against this Gitea/act runner's artifact backend — pin to v3 for the e2e failure-report upload. - Shared act_runner npm cache has corrupted platform-native tarballs before (@next/swc-linux-x64-musl) and reds viewer-unit/admin-unit/e2e with no product bug involved. All three npm ci steps now retry once after `npm cache clean --force` on first failure. Verified: full local suite green against DEV (37 passed, 6 skipped, no budget assertion failures) before wiring into CI.
5.2 KiB
PunimTag e2e (Playwright + @levkin/playkit)
Smoke tests against the public DEV URL so we catch LAN-redirect bugs
(e.g. NEXTAUTH_URL=http://10.0.10.121:3001 → Kolby #57).
Quick start
cd e2e
cp .env.example .env # edit credentials
npm ci
npx playwright install chromium
npm test
Private Gitea installs need auth once: set a local git insteadOf with a token
before npm ci, then unset it. CI does this in .gitea/workflows/ci.yml.
Never commit lockfile URLs that embed tokens.
Environment
| Variable | Required | Purpose |
|---|---|---|
PLAYKIT_BASE_URL |
yes (default in env-defaults.json) |
Public viewer host |
PLAYKIT_API_BASE_URL |
no | LAN API (see env-defaults.json) |
E2E_ADMIN_EMAIL |
for auth specs | Prefer e2e@levkine.ca — not admin@admin.com |
E2E_ADMIN_PASSWORD |
for auth specs | Dedicated e2e password (Vaultwarden / Infisical) |
E2E_API_USERNAME / E2E_API_PASSWORD |
optional | FastAPI bearer login, admin role (separate DB from NextAuth; mirrors e2e@levkine.ca) |
E2E_API_VIEWER_USERNAME / E2E_API_VIEWER_PASSWORD |
optional | FastAPI bearer login, viewer role — role-permission gate specs only |
E2E_VIEWER_EMAIL / E2E_VIEWER_PASSWORD |
optional | NextAuth auth-DB viewer, hasWriteAccess=false (third, independent store) — viewer.write-gates.spec.ts only |
PLAYKIT_MAIL_PROVIDER |
for mail specs | mailpit (homelab) |
MAILPIT_BASE_URL |
for mail specs | http://10.0.10.45:8025 |
MAILPIT_USER / MAILPIT_PASSWORD |
for mail specs | Mailpit basic auth |
Secrets: Infisical LevkinOps → dev → /playkit/punimtag + Gitea Actions.
See ansible docs/hardening/SECRETS.md.
Playkit pin: @levkin/playkit@v0.3.1 (git tag). Zod schema option, storageState
helpers, playkitFailureArtifacts, Mailpit, and network interception
(interceptNetworkCall, startNetworkErrorMonitor — see playkit docs/NETWORK.md)
are all in this pin — see network-errors.spec.ts for the silent-4xx/5xx monitors on
gallery/search/upload/manage-users. Timing → Pushgateway is supported by the kit but
not enabled in this CI job yet.
What we assert
- Login page loads on the public hostname
- Sign-out stays on that hostname (never
10.x) - FastAPI
/health→{ "status": "ok" } - Forgot-password / reset-password email in Mailpit; links use public host
- Register → unverified block → verify email via Mailpit → login
- Idle logout (
?e2e_idle_ms=) stays on public host - Upload: submit a tiny fixture via
#file-upload(uses storageState) - Manage Users open/close then sign-out (Kolby #57 overlay path)
- API
/api/v1/auth/meand/api/v1/photosreturn 401 without token - API catalog: people/tags lists, login 422/401, users/roles 401, missing photo 404|401
- Viewer
/api/auth/sessionreturns email when storageState is loaded - FastAPI role-permission write gates: viewer 403 vs admin 200 on users, role-permissions, bulk-delete
- Public gallery search (
/api/search): tag filter, person filter, and combined-filter narrowing return correct subsets; UI tag-filter interaction updates URL + result count - Logged-in-only filters: Favorites checkbox toggles
favoritesOnlyURL param; People filter UI selection updates URL + result count (uses storageState) - Silent-failure network monitor (
startNetworkErrorMonitor): gallery home, search+filter, upload, and Manage Users overlay all stay free of background 4xx/5xx while the UI loads/interacts interceptNetworkCallspies on the real upload POST (status +{ message, photos }shape) and the filtered/api/search?tags=GET (status + Zod-validated shape) instead of brittlewaitForResponse/text scraping- Zod-validated API responses: FastAPI login
TokenResponse+/auth/meUserResponse(api.fastapi-login.spec.ts), gallery searchSearchResponse(gallery.search-filters.spec.ts) - NextAuth (browser-session) write gates: viewer (
hasWriteAccess=false) gets 403, admin gets past the gate, onPOST /api/faces/{id}/identify(viewer.write-gates.spec.ts) — distinct from the FastAPI role-permission gates in #12 - PROD smoke (
prod.smoke.spec.ts, opt-in viaPROD_BASE_URL):/api/health+ login page load on the public PROD host — dormant until the PROD LXC exists (see ROADMAP) - Manage Users real CRUD + causal cross-session effect (
admin.manage-users-actions.spec.ts): admin creates/edits/deletes a user through the actual panel (not just open/close), and deactivating a user via the UI immediately revokes that user's already open NextAuth session (jwt callback re-checksisActive, Kolby #57 fix) — every run creates a disposablee2e-manage-test-*account and deletes it in afinally, never touching the shared punimtagdev/MirrorMatch DB's real accounts - Timing budgets (
timing-budgets.ts): everytimings.measure()call site is followed byexpectWithinBudget()against a sharedBUDGET_MSbucket — a step getting order-of-magnitude slower reds the test, not just a Pushgateway sample nobody's watching
See repo root ROADMAP.md for gaps and next steps.
Defaults for base URLs live in env-defaults.json (imported by env-defaults.ts,
fixtures.ts, playwright.config.ts, and the Gitea e2e job).