CI's first run showed the Edit button's aria-label exact-match locator
timing out against the live table even though the same row resolved
fine for badge-text reads moments earlier — switch to positional
lookup (Edit is always the first action button, Deactivate/Delete the
last) instead of relying on exact accessible-name matching.
admin.manage-users.spec.ts only opened/closed the panel; the actual
create/edit/delete flow through the UI was untested. Adds a page object
(ManageUsersPanel) and two specs: full CRUD through the panel, and a
causal cross-session check that deactivating a user via the admin UI
immediately revokes that user's already-open session (NextAuth jwt
callback re-checks isActive on every request, per issue #57's fix).
Every run creates and tears down its own disposable e2e-manage-test-*
account since punimtagdev shares its Postgres DB with MirrorMatch.
- upload.smoke.spec.ts / gallery.search-filters.spec.ts: replace
waitForResponse/text-scrape with typed interceptNetworkCall spies (status
+ JSON shape) on the real upload POST and the filtered /api/search GET.
- api.fastapi-login.spec.ts: Zod-validate the FastAPI TokenResponse and
/auth/me UserResponse instead of loose casts. gallery.search-filters.spec.ts
gets the same treatment for the Prisma-backed SearchResponse.
- prod.smoke.spec.ts: opt-in (PROD_BASE_URL) health + login-page check on a
real PROD host; skips as a no-op until the PROD LXC exists (none does yet
per `pct list` — see ROADMAP).
- viewer.write-gates.spec.ts: NextAuth (browser-session) hasWriteAccess gate
on POST /api/faces/{id}/identify — viewer 403s, admin passes through.
Provisioned a third, independent auth-DB user (e2e-viewer@levkine.ca,
hasWriteAccess=false) for this via ansible's provision-punimtag-e2e-user.py
(see that repo for the vault/Infisical/Gitea/Vaultwarden side).
Bump @levkin/playkit to v0.3.1 (network interception helpers) and land the
work it unblocked: startNetworkErrorMonitor on gallery/search/upload/manage-
users, Favorites + People filter UI e2e (storageState), and a fix for the
lingering `res.data: unknown` / Mailpit-vs-Mailtrap type errors in the mail
and catalog specs. Also adds Vitest coverage + a CI job for admin-frontend,
which had no test infra before this.
- Wire E2E_API_USERNAME/PASSWORD (admin) + E2E_API_VIEWER_USERNAME/PASSWORD
(viewer) into the Gitea Actions e2e job so FastAPI-authed specs run in CI.
- Add api.role-permissions.spec.ts: viewer 403 vs admin 200/200/200 on
/api/v1/users, /api/v1/role-permissions, and /api/v1/photos/bulk-delete
(safe no-op via a non-existent photo id — proves the gate, not deletion).
- Add gallery.search-filters.spec.ts: tag_id and person_id filters on the
public /api/search route return correct subsets, combined filters narrow
results, and the /search UI tag-filter interaction updates the URL/count.
- Update ROADMAP/README/.env.example for the new env vars and coverage.
Wire viewer-frontend Vitest into Gitea Actions, centralize DEV base URLs
in env-defaults.json, expand playkit API catalog specs, and land auth/mail
flows plus verify-email public-host redirect and idle-logout e2e hooks.
Add an e2e/ Playwright harness (via @levkin/playkit) that smoke-tests
the public DEV host: login page loads, sign-out never redirects to a
LAN host, FastAPI /health responds, and forgot-password mail lands in
Mailtrap with a public reset link (Kolby #56/#57 regressions).
Wire it into Gitea Actions as a new `e2e` job (self-hosted runner,
Chromium via Playwright), gated behind the existing skip-ci-check.
Add npm root scripts (install:e2e, test:e2e) and gitignore e2e build
artifacts (node_modules, reports, .env).