# End-to-end test harness The e2e harness stands up the full production-shaped stack (server + agent + rest-server) in Docker Compose and drives it through Playwright. CI runs it on every PR; operators can run it locally too. ## Files ``` e2e/ ├── compose.e2e.yml compose stack: server + rest-server + agent ├── Dockerfile.agent Linux container for the agent (alpine + restic) ├── agent-entrypoint.sh decides between announce / token-enrol / run └── playwright/ ├── package.json ├── playwright.config.ts └── tests/ ├── lib/server.ts bootstrap, login, accept, poll helpers └── smoke.spec.ts happy-path: enrol → backup → succeeded ``` ## Local run Prerequisites: Docker + Docker Compose, and `npx` for Playwright. ```sh # 1. Build + bring up the stack (server, rest-server, source data). docker compose -f e2e/compose.e2e.yml up --build -d server rest-server source-fixture # 2. Wait for the server, then scrape the bootstrap token from the log. until curl -fsS http://127.0.0.1:8080/api/version >/dev/null; do sleep 1; done RM_BOOTSTRAP_TOKEN=$(docker compose -f e2e/compose.e2e.yml logs server \ | grep -Eo '[a-zA-Z0-9_-]{40,}' | head -1) export RM_BOOTSTRAP_TOKEN # 3. Start the agent (it announces against the running server). docker compose -f e2e/compose.e2e.yml up -d agent # 4. Install + run Playwright. cd e2e/playwright npm install npx playwright install --with-deps chromium npx playwright test ``` When the test passes you'll see: ``` Running 2 tests using 1 worker ✓ smoke: enrol-via-announce → backup › happy path completes in under a minute (47s) ✓ smoke: scrape /metrics › metrics endpoint exposes the host gauge (180ms) 2 passed (47.5s) ``` Tear-down: ```sh docker compose -f e2e/compose.e2e.yml down -v ``` `-v` removes the named volumes too — important between runs because the rest-server volume holds an initialised repo and the agent-config volume holds a stale bearer. ## What the test exercises 1. **Bootstrap.** Posts the admin-creation request to `/api/bootstrap` with the token scraped from the server log. 2. **Login (UI).** Drives the login form via Playwright; verifies the dashboard loads with a session cookie set. 3. **Pending host appears.** Polls the dashboard for the inline accept form generated by the announcing agent; reads the pending-id out of its action URL. 4. **Accept.** POSTs `/api/pending-hosts/{id}/accept` with the rest-server URL + repo password. The server mints a Host row + bearer + AEAD-encrypted creds and pushes the bearer down the still-open pending WebSocket. 5. **Online + auto-init.** Polls `/api/hosts` until the new host is `status=online`. Auto-init runs as part of this — the first dispatched job after creds save is `restic init`. 6. **Run backup.** Submits the host detail page's `Run now` form; expects `HX-Redirect` to the live job page. 7. **Verify.** Polls `/api/hosts` until the host's `last_backup_status` flips to `succeeded`. 8. **Metrics.** Scrapes `/metrics` and asserts the server-gauge + build-info lines are present (the compose stack opens the endpoint via `RM_METRICS_TRUSTED_CIDR=0.0.0.0/0`). ## CI workflow [`.gitea/workflows/e2e.yml`](../.gitea/workflows/e2e.yml) runs the suite on every PR into `main`. On failure it dumps the last 200 lines of each container log as a workflow annotation and uploads the Playwright HTML report as an artefact. ## When tests fail - **Pending host never appears.** Agent container probably couldn't reach the server. Check `docker compose logs agent` for connection errors and `docker compose logs server` for any 4xx on `/api/agents/announce`. - **Backup hangs in `running`.** The agent shells out to `restic`; check the live job log at `http://127.0.0.1:8080/jobs/` (still up after a failed test as long as you didn't `down -v`). - **`RM_BOOTSTRAP_TOKEN not set`.** The server log scrape matched the wrong line or the token regex is too tight. The server prints the token on a line starting with ` ` (four spaces) inside a banner; widen the regex if your server log format changes. ## Adding new tests The harness is intentionally flat — one `*.spec.ts` per scenario. Reuse the helpers in `lib/server.ts` and avoid duplicating bootstrap / login boilerplate. Heavy fixtures (custom users, OIDC IdP) belong in their own compose override file rather than complicating `compose.e2e.yml`.