Addresses every finding from the four-agent review of commit aa493b9.
Dashboard — honor redirect_to on subtab loadApp() (closes the silent
historical-slug redirect UX gap):
- queues/+page.svelte
- queues/[name]/+page.svelte
- files/+page.svelte
- dead-letters/+page.svelte
After a rename, the URL bar now reflects the canonical slug instead
of silently rendering the renamed app's data under the stale URL.
Mirrors the established pattern at apps/[slug]/+page.svelte:619-623.
manager-core:
- queues_api.rs IntoResponse now uses the JSON envelope shape
`{"error": "..."}` consistent with every sibling admin api file.
- triggers_api::delete_trigger reordered: cap check fires BEFORE the
trigger load, closing the 404-vs-403 existence side channel an
unauthorized caller could otherwise probe.
- InMemoryAppRepo mocks in topics_api + triggers_api now implement
get_by_slug + get_by_slug_or_history (previously
`unimplemented!()`), unblocking handler-level slug-input tests.
- Added 4 slug-acceptance tests to topics_api and 2 to triggers_api
(slug-resolves, unknown-slug-404, historical-slug-resolves,
create-via-slug). Also added delete-without-cap-is-forbidden test
pinning the new cap-first order.
e2e:
- navigation/tabs.spec.ts split per-tab so a regression on one tab
no longer masks regressions on the others.
- Negative assertion widened: captures every /api/v1/admin/apps/*
response and fails on any 4xx/5xx — not just the literal "Cannot
parse" string. Catches a broader regression shape.
- networkidle replaced with `expect(<main>).toBeVisible()` —
networkidle is officially discouraged for SPAs and was at risk of
timing out behind the queues auto-refresh.
- Cleanup registration moved BEFORE the create-app API call so a
flaky create still gets swept up.
- Queue drilldown route /queues/[name] now covered.
- Stable `data-testid="queues-empty-state"` replaces fragile
UI-copy substring match for the positive assertion.
- Header comment now spells out what this spec does and doesn't
catch.
docs:
- serverless_cloud_blueprint.md: slug-history described as
"200 OK + redirect_to" JSON envelope rather than "301 redirect"
— matches what apps_api actually implements (SPA can't honor a
mid-tree HTTP redirect).
Unit-test gap (acknowledged): queues_api, files_api, secrets_api,
dead_letters_api have zero in-process tests. Adding them properly
needs a shared mock-repo helper crate — the standalone trait surface
(QueueRepo + TriggerRepo + ScriptRepository + AuthzRepo + repo-
specific) is ~30 methods per file. Documented inline in queues_api.rs
near the resolver. Integration coverage via crates/picloud/tests/ and
the new e2e spec cover the same paths end-to-end.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dashboard E2E tests
Browser-driven tests for the PiCloud dashboard, powered by Playwright.
Prerequisites
The tests drive a real dashboard against a real backend. Bring up both before running:
# 1. Postgres
docker compose up -d postgres
# 2. Backend (port 18080 matches dashboard/vite.config.ts dev proxy)
PICLOUD_BIND=127.0.0.1:18080 \
PICLOUD_ADMIN_USERNAME=admin \
PICLOUD_ADMIN_PASSWORD=admin \
DATABASE_URL=postgres://picloud:picloud@127.0.0.1:15432/picloud \
cargo run -p picloud
# 3. Browser binaries (one-time, ~200 MB)
cd dashboard && npm run test:e2e:install
The Vite dev server is started automatically by Playwright's webServer
config — you do not need to run npm run dev yourself.
Running
cd dashboard
npm run test:e2e # headless, full suite
npm run test:e2e:ui # interactive UI runner
npx playwright test smoke # run a single spec
npx playwright show-report
Env vars
| Var | Default | Notes |
|---|---|---|
E2E_BASE_URL |
http://localhost:5173 |
Origin tests navigate against (dashboard is mounted at /admin). |
E2E_API_BASE |
http://127.0.0.1:18080 |
Backend used by globalSetup health probe + admin login. |
E2E_DASHBOARD_ORIGIN |
http://localhost:5173 |
Used to seed localStorage during globalSetup. |
E2E_ADMIN_USERNAME |
admin |
Bootstrap admin to log in as. |
E2E_ADMIN_PASSWORD |
admin |
Match PICLOUD_ADMIN_PASSWORD above. |
PICLOUD_DASHBOARD_PORT |
5173 |
Dev server port — picked up by both Vite and Playwright. |
How isolation works
Tests share one backend + one Postgres. To avoid cross-test interference:
- A shared bootstrap admin session is captured once in
tests/e2e/.auth/admin.json(gitignored) and reused by every test viastorageState. - Each test creates resources with a unique slug / username produced by
fixtures/ids.ts(e2e-<prefix>-w<worker>-<random>). - Each test registers cleanup via
fixtures/cleanup.tsand tears down inafterEach. Cleanup is best-effort: a missing resource doesn't fail the suite, so a test can pre-delete and still register the entry.
Layout
tests/e2e/
global-setup.ts # health probe + admin login + storageState seed
smoke.spec.ts # A.5 smoke
fixtures/
auth.ts # UI login/logout helpers (for login-flow specs)
api.ts # bearer-token-backed APIRequestContext
ids.ts # unique slug/username generators (test-fixture)
cleanup.ts # afterEach resource teardown