Files
galaxy-game/ui/docs/testing.md
T
Ilia Denisov a9087691a3
Tests · Go / test (push) Successful in 2m6s
Tests · Go / test (pull_request) Successful in 3m1s
Tests · Integration / integration (pull_request) Successful in 1m42s
chore(ci): tidy CI/dev infra — drop local-ci, lift migration rule, scope by galaxy.stack label
Five connected cleanups across the dev/CI infrastructure:

1. Drop tools/local-ci/. The standalone Gitea + act_runner stack was
   the legacy "offline workflow validator"; the per-stage CI gate now
   runs on gitea.lan and the directory was only retained as a
   fallback. Removing it leaves no operational dependency: backend,
   gateway, and game code have no references; documentation that
   pointed at it (CLAUDE.md, docs/ARCHITECTURE.md, ui/docs/testing.md,
   tools/dev-deploy/README.md, tools/local-dev/README.md) is updated
   in this same change. Historical "Verified on local-ci run N"
   markers in ui/PLAN.md are preserved unchanged.

2. Lift the pre-production single-migration rule. The rule forced
   every schema delta into 00001_init.sql and required a manual
   make clean-data wipe on every backward-incompatible change in
   tools/dev-deploy/. Future schema deltas now land as additive
   sequence-numbered files (00002_*.sql, …) that goose applies
   automatically on backend startup; 00001_init.sql becomes an
   immutable baseline. Authoring conventions live in
   backend/internal/postgres/migrations/README.md. The chain may be
   squashed back into a fresh 00001 as a deliberate one-time
   operation before the first production deployment.

3. Document the deployment cadence. The dev environment is
   single-tenant: pushes to feature/* run the test workflows
   (go-unit, ui-test, integration) only; dev-deploy.yaml fires on
   push to development. A workflow_dispatch override on
   dev-deploy.yaml lets a developer preview a feature branch on the
   shared dev environment before merge; the next merge into
   development overwrites the manual deploy idempotently.

4. Scope compose-managed resources by an explicit
   galaxy.stack=<local-dev|dev-deploy> label. Both compose files
   stamp the label on every service, network, and named volume.
   Makefiles in tools/local-dev/ and tools/dev-deploy/ filter their
   engine-cleanup operations by (stack-label AND engine OCI title)
   so they never touch unrelated workloads on the same daemon.
   dev-deploy.yaml gains a pre-`compose up` step that reaps stale
   exited/dead containers under the dev-deploy stack label.

5. Backend now stamps the same galaxy.stack=<value> label on every
   engine container it spawns, sourced from a new BACKEND_STACK_LABEL
   env var (empty → label not applied; legacy-safe). Both compose
   files set it to their stack name (local-dev / dev-deploy). The
   contract is recorded in docs/ARCHITECTURE.md under
   "Container labels". A package-level test in
   backend/internal/runtime exercises both the label-present and
   label-absent paths.

No tests intentionally regressed: go test ./backend/internal/{config,
runtime,dockerclient} is green, both compose files validate cleanly,
and the backend, gateway, and game modules all build.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 23:32:42 +02:00

6.9 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).

Synthetic reports for visual testing (DEV)

For visual checks of the map, inspectors and order-overlay against rich game states, the lobby exposes a DEV-only "Load synthetic report" affordance (import.meta.env.DEV). The flow is:

  1. Convert a legacy text report (tools/local-dev/reports/{dg,gplus}/) to JSON with the Go CLI:

    go run ./tools/local-dev/legacy-report/cmd/legacy-report-to-json \
        --in  tools/local-dev/reports/dg/KNNTS039.REP \
        --out /tmp/dg39.json
    

    See ../../tools/local-dev/legacy-report/README.md for what the parser populates today and how to extend it when a new UI phase decodes a new Report field.

  2. Run the UI dev server (pnpm -C ui/frontend dev), open the lobby, and use the "Load JSON…" file picker in the Synthetic test reports (DEV) section. The page navigates to /games/synthetic-<uuid>/map with the report wired into the in-game shell.

In synthetic mode:

  • The map view, inspectors and races view render against the loaded report exactly as they would for a real game.
  • Composing orders works locally (overlay applies through applyOrderOverlay as usual), but nothing is sent to the gatewayOrderDraftStore.scheduleSync short-circuits because the synthetic id is not a UUID and the layout deliberately does not bind a GalaxyClient for this game.
  • The order draft is persisted into the platform Cache under the same order-drafts namespace as real games, keyed by the synthetic id, so navigating back into the same synthetic session restores the draft. The cache is cleared with __galaxyDebug.clearOrderDraft(gameId) (DEV debug surface).
  • A page reload loses the in-memory report registry; opening the same synthetic id afterwards redirects to /lobby. Re-load the JSON to reseed.

The synthetic-report parity rule (see ../PLAN.md § Assumptions and Defaults) requires every UI phase that extends decodeReport to also extend the legacy parser in lockstep, or to record in the parser's README.md that the new field cannot be derived from legacy text. This keeps the synthetic-mode coverage in step with the contract as the UI grows.

CI verification

Workflow changes are exercised on the primary CI host (gitea.lan). Push the branch (git push gitea …), then open the run in the Gitea UI to inspect the status and logs. See CLAUDE.md (## Per-stage CI gate) for the per-stage workflow.

For a sub-second syntax check of a workflow YAML without pulling images or 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 only for syntax checks.