Files
at-container-registry/test/e2e/README.md
T
Evan JarrettandClaude Opus 5 5aefa85048 test/e2e: cover the admin job wiring ab4a4eb changed
jobs_test.go covers the job framework thoroughly, but nothing covers the
wiring: whether the kickoff handler renders the progress fragment into the
right hx-target, and whether the loop actually outlives the request it was
started from. Both are what ab4a4eb changed, and both are invisible to Go
tests — a typo in an hx-target or a fragment that renders blank passes every
assertion we have.

The load-bearing check drives crew import rather than the tier remap. A
one-member remap completes in under a second, so closing the tab "mid-run"
proves nothing; import does a PDS write plus a network PLC lookup per entry,
which leaves a real window to close the browser and watch the job keep going.
It is caught mid-flight at a progress tick with no admin page open.

Seeded members are created on the local-only dev hold and removed in a
finally block. README records the environment traps found while building
this: 127.0.0.1 vs localhost, in-memory sessions dying on every hold rebuild,
UA/IP pinning that makes curl log you out, and the forward-only appview
migrations that require a per-batch DB reset.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 16:34:25 -05:00

86 lines
4.1 KiB
Markdown

# Browser-driven batch validation
Checks for the `val/*` validation stack — the branch-per-batch series used to
sign off the range between the deployed commit and `main`.
These complement, and do not replace, `go test` and the in-process integration
harness (`internal/testharness`, `test/integration`, `make integration-test`).
Pick by what needs proving:
| Prove | Use |
|---|---|
| A function's logic, a query's shape, a guard's behaviour | Go unit test |
| A push/pull/delete round trip across appview + hold + S3 | `make integration-test` |
| A fragment renders into the right target, a job outlives its request, a 500 dressed as an empty state | these scripts |
## Running
```bash
npm i -D @playwright/test && npx playwright install chromium
node test/e2e/login.mjs # interactive, once per hold rebuild
node test/e2e/batch00-admin-jobs.mjs # then the batch checks
```
Env overrides: `ATCR_HOLD_URL`, `ATCR_APPVIEW_URL`, `ATCR_E2E_PROFILE`,
`ATCR_E2E_SEED`.
## Things that will cost you an afternoon
**Use `127.0.0.1`, never `localhost`.** The appview canonicalises to
`http://127.0.0.1:5000` and answers `localhost` with a 307. Any snippet written
against `localhost:5000` measures the redirect, not the endpoint.
**Admin sessions are in-memory and die on every hold rebuild.** They live in
`ui.sessions` (`pkg/hold/admin/admin.go`), not the `admin_sessions` table, which
is vestigial for this path. Air rebuilds the hold whenever tracked source
changes — including a batch checkout — so budget one interactive login per
switch. There is no test-mode bypass; `server.test_mode` only affects OAuth
redirect URLs.
**Never drive the admin panel with curl.** Sessions are pinned to User-Agent and
client IP prefix, and a mismatch does not merely reject the request — it calls
`deleteSession` and logs you out. Drive everything through `ctx.request`, which
inherits the browser's cookie jar and UA.
**Closing every Playwright page disposes `ctx.request`.** It fails with "Request
context disposed". Keep one `about:blank` page open when the test needs the
browser out of the way.
**Crew delete is a `<button hx-post>`, not a `<form>`.** Scraping for forms
finds nothing, deletes nothing, and cheerfully reports a clean tab while every
seeded member is still live. Assert against page text after a reload, not
against the scrape that just ran.
**Crew rows hydrate per-row via `hx-trigger="load"`.** The tab needs a real
settle window (~6s here) before anything is scrapeable.
**A seeded fixture makes the second run lie.** Crew import skips DIDs that
already exist, so a re-run finishes instantly and the detachment check silently
passes without ever exercising a running job. Purge before re-running.
**Preconditions are easy to miss.** The tier reconciliation card only renders
for crew on a tier absent from quota config (`handleCrewList`), so it is invisible
on a healthy hold. Crew add/update do not validate the tier against config,
which is how these tests manufacture the condition without restarting the hold.
## Per-batch stack switching
Use `val-switch.sh`. The appview DB migrates **forward only**, so older batch
code hits a schema from the future: batch 00 selects `tags.id` (dropped by
0032) and cannot write `manifests.manifest_key` (added NOT NULL by 0033/0034,
which kills every backfill insert). The script destroys and re-migrates the
appview volume so the DB matches the branch.
It deliberately does **not** touch `atcrio_atcr-hold`, which holds the hold's
did:web signing key and the CAR store — captain, crew, layer, stats and scan
records. Losing it means a new hold identity and every pushed layer gone.
`docker-compose.yml` is pinned to `main` throughout. `a7c7db6` (batch 01) is
what makes the appview share the hold's netns so `did:web:localhost%3A8080`
resolves, and every compose-based batch needs it — including batch 00, which
lands before it. The file is dev-only, so pinning it is a fixture decision
rather than a change to what is under validation. Never commit it from a batch
branch; the script unstages it for you, because `git checkout main -- <path>`
stages what it restores.