Files
punimtag/e2e
ilia 317a135daa
CI / skip-ci-check (pull_request) Successful in 4s
CI / docker-ci (pull_request) Successful in 7s
CI / secret-scan (pull_request) Successful in 14s
CI / admin-unit (pull_request) Successful in 1m21s
CI / viewer-unit (pull_request) Successful in 1m48s
CI / e2e (pull_request) Successful in 2m38s
fix: causal cross-session test hung on Active-only filter, not a real bug
statusBadgeText(email) after deactivating a user hung for the full test
timeout: ManageUsersContent.handleEditUser() refetches the list with the
current statusFilter ('active' by default) after every save, so the
just-deactivated row silently drops out of view — the locator then waits
forever for a row that filter will never show again (no actionTimeout is
configured, so only the outer test timeout eventually kills it).

Add ManageUsersPanel.showAllUsers() and switch to it before reading the
badge. Also gave the test more headroom (60s -> 120s) since it drives two
full UI logins plus create/edit/verify against a shared-CI DEV LXC.

Verified: full local suite green (37 passed, 6 skipped) against DEV.
2026-07-14 23:19:40 -04:00
..

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.canot 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 LevkinOpsdev/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

  1. Login page loads on the public hostname
  2. Sign-out stays on that hostname (never 10.x)
  3. FastAPI /health{ "status": "ok" }
  4. Forgot-password / reset-password email in Mailpit; links use public host
  5. Register → unverified block → verify email via Mailpit → login
  6. Idle logout (?e2e_idle_ms=) stays on public host
  7. Upload: submit a tiny fixture via #file-upload (uses storageState)
  8. Manage Users open/close then sign-out (Kolby #57 overlay path)
  9. API /api/v1/auth/me and /api/v1/photos return 401 without token
  10. API catalog: people/tags lists, login 422/401, users/roles 401, missing photo 404|401
  11. Viewer /api/auth/session returns email when storageState is loaded
  12. FastAPI role-permission write gates: viewer 403 vs admin 200 on users, role-permissions, bulk-delete
  13. Public gallery search (/api/search): tag filter, person filter, and combined-filter narrowing return correct subsets; UI tag-filter interaction updates URL + result count
  14. Logged-in-only filters: Favorites checkbox toggles favoritesOnly URL param; People filter UI selection updates URL + result count (uses storageState)
  15. 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
  16. interceptNetworkCall spies on the real upload POST (status + { message, photos } shape) and the filtered /api/search?tags= GET (status + Zod-validated shape) instead of brittle waitForResponse/text scraping
  17. Zod-validated API responses: FastAPI login TokenResponse + /auth/me UserResponse (api.fastapi-login.spec.ts), gallery search SearchResponse (gallery.search-filters.spec.ts)
  18. NextAuth (browser-session) write gates: viewer (hasWriteAccess=false) gets 403, admin gets past the gate, on POST /api/faces/{id}/identify (viewer.write-gates.spec.ts) — distinct from the FastAPI role-permission gates in #12
  19. PROD smoke (prod.smoke.spec.ts, opt-in via PROD_BASE_URL): /api/health + login page load on the public PROD host — dormant until the PROD LXC exists (see ROADMAP)
  20. 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-checks isActive, Kolby #57 fix) — every run creates a disposable e2e-manage-test-* account and deletes it in a finally, never touching the shared punimtagdev/MirrorMatch DB's real accounts

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).