E2E tests against staging UI #
Playwright + TypeScript browser tests that exercise the /account
dashboard against the staging atmos-relay. Complements
cmd/staging-probe (HTTP-level signup flow) by covering UI rendering
and browser-only behavior.
What's tested #
dashboard.spec.ts— signed-in demo member sees their handle + domain on/account
More flows land in Phase 2 (sign-out, add-domain form, /enroll/auth start, /api/v1/me).
How auth works #
The tests use /admin/dev/mint-session (internal ref) to issue a
short-lived JWT for did:plc:demo-demo-staging.example — the
persistent demo member seeded on staging by /admin/dev/seed-demo.
The endpoint refuses any DID that doesn't start with did:plc:demo-,
so the test stack is structurally incapable of impersonating real
members even if the admin token leaks.
The JWT is dropped onto the Playwright BrowserContext as the
atmos_account_session cookie (Path=/account, matching the prod
cookie attrs).
Running locally #
Staging is reachable only from the operator's private network (e.g. a
Tailscale tailnet or VPN). Substitute your own staging URL for
{STAGING_BASE_URL} below.
cd tests/e2e
npm ci
npx playwright install --with-deps chromium
export STAGING_BASE_URL=https://{STAGING_BASE_URL}
export STAGING_ADMIN_TOKEN=$(sops -d ../../infra/secrets/staging.yaml | grep admin_token | awk '{print $2}')
npm test
For interactive debugging:
npm run test:headed # open a real browser window
npm run test:debug # Playwright inspector
npm run report # open the HTML report after a run
Running in CI #
.gitea/workflows/staging-e2e.yml triggers:
workflow_runafter a successful Staging Probe run (gates expensive E2E behind the cheaper HTTP-level probe)workflow_dispatch— manual button in the Gitea Actions UI
The workflow runs inside mcr.microsoft.com/playwright:v1.50.0-noble
on the docker-build runner (the only combination known to work on
Gitea — see memory gitea-playwright-ci.md). Tailscale is set up
inline with the same --tun=userspace-networking +
--outbound-http-proxy-listen pattern as staging-probe.yml.
Adding tests #
- Keep tests independent: no shared state between spec files. The demo member is read-only common infrastructure; never mutate it.
- For write-path tests (Phase 3+), seed a fresh member per test via
/admin/dev/seed-demowith a unique domain, then/admin/dev/delete-memberin afterEach. - Use Playwright's web-first assertions
(
await expect(locator).toBeVisible()) — they auto-retry within the timeout. Don't manually poll withawait locator.isVisible(). - Prefer role-based locators (
page.getByRole('heading', ...)) over CSS selectors — they're resilient to template refactors.
Why staging-only, no localhost mode #
Mint-session is staging-only by three independent gates (env=staging, hostname check, marker file). The test stack therefore can't authenticate against a local dev relay; running locally still hits the real staging microvm. This is intentional — the value of E2E is verifying the deployed system, not a dev approximation of it.