Files
galaxy-game/ui/docs/testing.md
T
Ilia Denisov 69fa6b30e1 tools/local-dev: docker-compose stack for UI development
Adds tools/local-dev/ with postgres + redis + mailpit + backend +
gateway plus a Make wrapper, so `make -C tools/local-dev up` brings
the full authenticated stack online and `pnpm -C ui/frontend dev`
talks to it directly. The committed `.env.development` already
points at the stack and pins the matching gateway response public
key from the dev keypair under tools/local-dev/keys/.

The backend ships a new opt-in env, BACKEND_AUTH_DEV_FIXED_CODE
(`tools/local-dev/.env` defaults it to 123456). When set,
ConfirmEmailCode accepts that literal in addition to the real
bcrypt-verified code; SendEmailCode still queues a real email so
Mailpit captures the issued code at http://localhost:8025/, and
both paths coexist. The override is rejected as non-six-digit by
config validation and emits a loud warning at backend startup.

The local-dev Dockerfiles mirror backend/Dockerfile and
gateway/Dockerfile but switch the runtime stage to alpine so
docker-compose healthchecks can wget /healthz; the gateway
Dockerfile additionally copies ui/core/ into the build context
because gateway/go.mod's `replace galaxy/core => ../ui/core` is
required to compile the gateway main.

Smoke tested:
- `make -C tools/local-dev up` boots all five services to healthy.
- send-email-code + confirm-email-code with code=123456 returns a
  device_session_id; a real code in Mailpit also redeems
  successfully.
- `pnpm test` 14/14, `pnpm exec playwright test` 44/44.
- `go test ./backend/internal/config/...` green.

Docs: tools/local-dev/README.md, tools/local-dev/keys/README.md,
new "Local development stack" section in ui/docs/testing.md, and a
short pointer in ui/README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 09:42:29 +02:00

7.7 KiB

UI Testing Tiers

UI client test toolchain. Project-wide testing layers (service / inter-service / system) live in ../../docs/TESTING.md; this doc only covers the UI-specific tiers added in Phase 2 of ../PLAN.md.

Tier 1 — per-PR

Triggered by .gitea/workflows/ui-test.yaml on every push and pull request that touches ui/**, backend/**, gateway/**, game/**, pkg/**, go.work, or go.work.sum. Linux runner only.

The actions/checkout@v4 step uses submodules: recursive, so the runner pulls every git submodule the suite depends on (today only pkg/geoip/test-data, the MaxMind-DB fixtures used by pkg/geoip).

Runs:

  • go test over the monorepo Go modules, excluding two areas:

    • integration/ — needs Docker + testcontainers and is the project's make -C integration integration gate.
    • client/ — the deprecated Fyne client (see ../PLAN.md §74) is frozen; its tests are not run in CI.

    The pkg/<name>/ modules are listed one by one in the workflow because they are independent go.work modules and ./pkg/... does not recurse into separate modules. The exact command lives in .gitea/workflows/ui-test.yaml.

  • pnpm test (Vitest + @testing-library/svelte + @testing-library/jest-dom) — component / unit tests under ui/frontend/tests/**/*.test.ts.

  • pnpm exec playwright test — end-to-end smoke against pnpm run dev on port 5173. Four projects:

    • chromium-desktop (Desktop Chrome)
    • webkit-desktop (Desktop Safari)
    • chromium-mobile-iphone-13 (iPhone 13 viewport, Chromium engine)
    • chromium-mobile-pixel-5 (Pixel 5 viewport, Chromium engine)

Playwright traces and screenshots are retained on failure and uploaded as Gitea Actions artefacts (playwright-report and playwright-traces, 14-day retention).

Tier 2 — release

Triggered by .gitea/workflows/ui-release.yaml on tag push (v*). Currently mirrors the Tier 1 step set; the dedicated release-only checks land in later phases:

  • Visual regression baseline check — Phase 33. Snapshots live in ui/frontend/tests/__snapshots__/ until the project shifts to Argos or another visual-diff service.
  • iOS smoke (Capacitor + Appium) — Phase 32. Runs on a macos-13 runner once the Capacitor mobile wrapper exists.

Both blocks are present as commented sections in .gitea/workflows/ui-release.yaml with the phase number that re-enables them.

Local execution

From ui/frontend/:

pnpm test                                           # Vitest
pnpm exec playwright install                        # one-time
pnpm exec playwright test                           # all projects
pnpm exec playwright test --project=chromium-desktop
pnpm exec playwright show-report                    # open last HTML report

From the repository root, the same scope CI uses (backend serially because most packages spawn their own Postgres testcontainer and parallel bootstraps starve each other on constrained runners):

go test -count=1 -p 1 ./backend/...
go test -count=1 \
  ./gateway/... ./game/... \
  ./pkg/calc/... ./pkg/connector/... ./pkg/cronutil/... \
  ./pkg/error/... ./pkg/geoip/... ./pkg/model/... \
  ./pkg/postgres/... ./pkg/redisconn/... ./pkg/schema/... \
  ./pkg/storage/... ./pkg/transcoder/... ./pkg/util/...

Local development stack

For UI work that needs a real authenticated stack (verifying the FlatBuffers wire end-to-end, exercising a real lobby flow, hitting real Mailpit), bring up tools/local-dev/:

make -C tools/local-dev up        # postgres + redis + mailpit + backend + gateway
pnpm -C ui/frontend dev           # Vite on the host, talks to the stack

ui/frontend/.env.development already targets the stack (http://localhost:8080) and pins the matching response-signing public key from tools/local-dev/keys/. Per-developer overrides go into .env.development.local (gitignored).

The stack honours BACKEND_AUTH_DEV_FIXED_CODE (default 123456 in tools/local-dev/.env) so the login form takes that literal in addition to the real Mailpit code; see ../../tools/local-dev/README.md for the full runbook (regenerating the dev keypair, switching the mode off, troubleshooting common boot issues).

The local-dev stack is independent from the local-ci stack below; they bind different ports and can run side by side.

Local CI verification

tools/local-ci/ ships a self-contained Gitea + Actions runner via docker-compose so workflow changes are exercised end-to-end on a real runner before pushing to a remote Gitea instance. On Apple Silicon the runner and every spawned workflow container are arm64-native (no QEMU). Full runbook lives in ../../tools/local-ci/README.md; the cheat sheet below covers the operations needed when working a phase that touches CI.

Bring up / push / tear down

make -C tools/local-ci up      # idempotent: gitea + runner + admin user + repo
make -C tools/local-ci push    # add `local-gitea` remote (first call) and push HEAD
make -C tools/local-ci status  # docker compose ps
make -C tools/local-ci logs    # tail container logs
make -C tools/local-ci down    # stop, keep state
make -C tools/local-ci clean   # stop and wipe volumes for a fresh start

Default credentials baked in: galaxy:galaxy-dev (admin user, also the owner of the galaxy/galaxy repo). Web UI on http://localhost:3000; runs at http://localhost:3000/galaxy/galaxy/actions.

Inspect a run from the shell

The Gitea Actions API is on http://localhost:3000/api/v1 with basic auth. Useful for verifying a workflow change without opening the browser:

# Latest workflow runs — `status` is a human-readable string here:
# "running" / "success" / "failure" / "cancelled".
curl -s -u galaxy:galaxy-dev \
  'http://localhost:3000/api/v1/repos/galaxy/galaxy/actions/tasks?limit=5' \
  | python3 -m json.tool

# Tight one-liner for the latest run only:
curl -s -u galaxy:galaxy-dev \
  'http://localhost:3000/api/v1/repos/galaxy/galaxy/actions/tasks?limit=1' \
  | python3 -c 'import json, sys; r=json.load(sys.stdin)["workflow_runs"][0]; print(r["run_number"], r["status"], r["display_title"])'

Step-by-step workflow output is stored zstd-compressed under /data/gitea/actions_log/galaxy/galaxy/<run_padded>/<job_index>.log.zst inside the gitea container:

docker compose -f tools/local-ci/docker-compose.yml exec -T gitea sh -c '
  apk add --quiet zstd
  zstdcat /data/gitea/actions_log/galaxy/galaxy/01/1.log.zst
' | less

<run_padded> is the run number, zero-padded to two digits (01, 02, …); <job_index> is the 1-based index of the job inside that run (only 1 for the current single-job workflows).

Typical phase workflow

When a phase changes anything under .gitea/workflows/ or surfaces new tests in CI:

  1. Local sanity first — run the affected commands directly (pnpm test, pnpm exec playwright test, the targeted go test ./... slice).
  2. Commit and make -C tools/local-ci push.
  3. Poll the API for the latest run; once it leaves running, inspect status. On failure pull the log via the snippet above.
  4. Fix and repeat. The runner is always-on; each push triggers a fresh run (test cache is cleared by -count=1 so a green run is honest).

Quick syntax-only dry-run with act

For a sub-second check that the workflow YAML is well-formed and action references resolve, without pulling images and without running anything:

act -W .gitea/workflows/ui-test.yaml -n push

act doesn't honour Gitea-specific behaviours (artifact storage, secrets, run triggers). Use it for syntax checks; fall back to the local Gitea above for honest end-to-end verification.