Skip to main content

E2E And Staging

This page describes the production-grade validation path for Wargrid.

Per-App E2E Layout

Each deployable app owns its own end-to-end entrypoint. This keeps local feedback fast and lets CI fan out work in parallel.

AppCommandNotes
APIbun run --cwd apps/api test:e2eCovers typed REST contracts and fixture reset routes.
Multiplayerbun run --cwd apps/multiplayer test:e2eCovers room join, messaging, and presence events.
Sitebun run --cwd apps/site test:e2eCovers the marketing shell and legal/navigation entry points.
Playbun run --cwd apps/play test:e2eCovers auth, deck, social, lobby, and grid preview flows.
Adminbun run --cwd apps/admin test:e2eCovers admin auth, moderation, telemetry, and queue views.
Studiobun run --cwd apps/studio test:e2eCovers balancing and board inspection screens.
Docsbun run --cwd apps/docs test:e2eCovers the published documentation surface.
Blogbun run --cwd apps/blog test:e2eCovers the Astro blog shell.

Browser app suites launch Playwright through tools/e2e/playwright/run-playwright-cli.mjs. This wrapper runs the Playwright CLI under Node. The self-hosted Linux runner needed this because direct playwright test -c ... calls inside Bun scripts hit .esm.preflight module-resolution failures.

Run the whole matrix with:

bun run test:e2e

Fixture Isolation

apps/play and apps/admin share fixture-backed API state during E2E runs. The tests isolate their writes with the x-wargrid-test-namespace header and reset routes under /api/testing/reset-fixtures.

When you add a new mutable player or admin flow:

  1. move the mutable state into a testable runtime module
  2. keep the HTTP layer thin
  3. preserve namespace-aware reset behavior

CI Gate Order

Pull requests and main both use the same quality gates before any deployment:

  1. bun install --frozen-lockfile
  2. bun run vendor-docs:sync
  3. bun run check
  4. per-app test:e2e

bun run check currently expands to:

bun run typecheck
bun run lint
bun run test
bun run storybook:build
bun run docs:build
bun run blog:build

PR To Staging Flow

Wargrid uses a default cooperative staging rollout:

  1. open a pull request from a branch in the main repository
  2. let validate and the per-app e2e matrix finish
  3. let deploy-staging deploy the full stack to staging
  4. verify the change on the staging hosts
  5. merge the pull request
  6. let the production workflow deploy from main

The staging deployment runs from .github/workflows/ci.yml. Production runs from .github/workflows/deploy-wargrid.yml.

Staging Topology

Staging lives on the main server and mirrors production as closely as possible.

SurfaceProductionStaging
Sitewargrid.appstaging.wargrid.app
Playplay.wargrid.appstaging-play.wargrid.app
Adminadmin.wargrid.appstaging-admin.wargrid.app
APIapi.wargrid.appstaging-api.wargrid.app
Docsdocs.wargrid.appstaging-docs.wargrid.app
Blogwargrid.blogstaging-blog.wargrid.app
Multiplayerdedicated nodestaging-multiplayer.wargrid.app

Before a staging rollout, the deployment script clones the production Postgres database into wargrid_staging. Then it deploys API, site, play, admin, docs, blog, and multiplayer in sequence. After the API container is up, it runs bun run --cwd packages/db db:push inside that container.

Password Protection

Staging is protected with nginx basic auth inside the Kamal-managed containers.

  • Secret name: STAGING_BASIC_AUTH_PASSWORD
  • Runtime switch: BASIC_AUTH_ENABLED=true
  • Password file generation happens in infra/docker/entrypoint.sh
  • Default username: wargrid

Keep the password in GitHub Actions secrets and local environment only. Do not commit the value.

The staging workflow writes .kamal/secrets-common and .kamal/secrets.staging on the GitHub runner immediately before invoking Kamal. This is required because Kamal destinations load secrets from those dotenv files during deploy.

The current CI and deploy flow runs on the self-hosted runner wargrid-deploy-94 with labels self-hosted, Linux, X64, and wargrid-deploy. The app-specific E2E matrix remains split by app so additional runners can execute it in parallel later without a workflow rewrite.

Browser E2E and the production deploy workflow install Node 22 with actions/setup-node@v4 before running Playwright. Keep that step in place unless the shared launcher and the runner behavior are reworked together.

Staging API Routing

The staging API is currently served directly at:

  • directly at https://staging-api.wargrid.app

The staging play and admin apps point their PUBLIC_API_URL at that host. If same-origin /api proxying is added later, update the Kamal config, this document, and the AI notes in the same change.

Post-Deploy Smoke

After the Kamal rollout, CI calls infra/scripts/verify-http-surface.mjs staging. The smoke verifies:

  • staging.wargrid.app
  • staging-api.wargrid.app/up
  • staging-api.wargrid.app/api/matchmaking/queues
  • staging-play.wargrid.app
  • staging-admin.wargrid.app
  • staging-docs.wargrid.app
  • staging-blog.wargrid.app

This catches deploy-time problems that pure local Playwright runs can miss, such as bad production start commands or preview host allow-lists.

Local Docs Host Binding

apps/docs uses different host bindings for local and container use:

  • local start, preview, and serve:prod bind to 127.0.0.1
  • Kamal uses serve:prod:container, which binds to 0.0.0.0

Keep this split intact. It prevents local browsers from opening invalid http://0.0.0.0:4201/ URLs while preserving container reachability on the server.

Required Secrets

The deployment workflows expect these GitHub Actions secrets:

  • KAMAL_SSH_PRIVATE_KEY
  • WARGRID_DATABASE_PASSWORD
  • BETTER_AUTH_SECRET
  • SMTP_PASS
  • STAGING_BASIC_AUTH_PASSWORD

Manual Verification Checklist

After staging is live:

  1. open the staging host and pass basic auth
  2. sign in with a player account
  3. sign in with an admin account
  4. verify the changed surface and any affected public hosts
  5. check that API-backed state persists and migrations applied cleanly
  6. merge only after staging behaves like production