# CLI-08 Two-Client E2E Smoke

Plan 06-08 — the MVP merge gate. Playwright drives two browser contexts
through a complete login → move → chat → reconnect cycle against the
authoritative server.

[doc->REQ-CLI-08]

## Run modes

### (a) Local dev — boot server + Vite dev side-by-side

```bash
# Pre-reqs (one-time):
#   1. apps/server/.env with BETTER_AUTH_SECRET (`openssl rand -hex 32`),
#      DATABASE_URL=./rebno-dev.db, ALLOWED_ORIGINS=, STAGING_MODE=0.
#   2. Two seeded accounts (see "Seeding accounts" below).
#   3. Workspace deps installed: `pnpm install --frozen-lockfile`
#      and packages built: `pnpm --filter @rebno/protocol --filter @rebno/game-logic --filter @rebno/db build`.

UAT_ACCOUNT_A=alice UAT_PASSWORD_A=alicepass1234 \
UAT_ACCOUNT_B=bob   UAT_PASSWORD_B=bobpass1234   \
pnpm --filter @rebno/client test:e2e
```

Playwright auto-boots `apps/server` (`:2567`) AND the Vite dev server
(`:5173`). `baseURL` is `http://localhost:5173`; Vite proxies `/api`,
`/colyseus`, `/matchmake`, `/health` to `:2567` per
`apps/client/vite.config.ts`.

Reuses already-running webServers (`reuseExistingServer: !CI`), so a
hot dev loop is fast.

### (b) Deployed staging — CI staging-deploy post-deploy gate

```bash
STAGING_URL=https://staging.rebno.decidel.com \
STAGING_INVITE_TOKEN=<phase-5-d-04-token> \
UAT_ACCOUNT_A=<staging seeded username A> \
UAT_PASSWORD_A=<staging seeded password A> \
UAT_ACCOUNT_B=<staging seeded username B> \
UAT_PASSWORD_B=<staging seeded password B> \
pnpm --filter @rebno/client test:e2e
```

`STAGING_URL` non-empty → Playwright skips webServer boot and points
straight at deployed staging. Used by `.github/workflows/deploy-staging.yml`
post-deploy step (plan 06-08 task 3).

## Required env

| Var                     | Where               | Default        | Notes                                                            |
| ----------------------- | ------------------- | -------------- | ---------------------------------------------------------------- |
| `STAGING_URL`           | both                | unset → local  | Set on staging CI to `https://staging.rebno.decidel.com`         |
| `STAGING_INVITE_TOKEN`  | staging only        | unset          | Required when `STAGING_MODE=1` server-side (Phase 5 D-04)        |
| `UAT_ACCOUNT_A`         | both                | `alice`        | Seeded username                                                  |
| `UAT_PASSWORD_A`        | both                | `alicepass1234`| Seeded password                                                  |
| `UAT_ACCOUNT_B`         | both                | `bob`          | Seeded username                                                  |
| `UAT_PASSWORD_B`        | both                | `bobpass1234`  | Seeded password                                                  |

## Seeding accounts

The cli-08 test logs in as 2 seeded accounts. Two paths:

### Path 1 — Better-Auth direct sign-up (recommended for fresh DBs)

```bash
HTTPS_BASE=http://localhost:2567   # or https://staging.rebno.decidel.com

# alice
curl -X POST $HTTPS_BASE/api/auth/sign-up/email \
  -H 'content-type: application/json' \
  -H "x-staging-invite: $STAGING_INVITE_TOKEN" \
  -d '{"email":"alice@rebno.test","password":"alicepass1234","name":"alice","username":"alice"}'

# bob
curl -X POST $HTTPS_BASE/api/auth/sign-up/email \
  -H 'content-type: application/json' \
  -H "x-staging-invite: $STAGING_INVITE_TOKEN" \
  -d '{"email":"bob@rebno.test","password":"bobpass1234","name":"bob","username":"bob"}'
```

Re-running the seed against an existing account is idempotent: Better-Auth
returns 409 ("user already exists"), but the account stays seeded, so the
test still passes.

### Path 2 — Legacy migration script (Phase 4 SRV-10 path)

```bash
echo -e "alice\nalicepass1234\nbob\nbobpass1234" > /tmp/legacy-seed.txt
pnpm migrate:legacy-accounts /tmp/legacy-seed.txt --db ./rebno-dev.db
```

The legacy migrator inserts staging rows that auto-rehash to argon2id on
first login. Useful when the test env mirrors a real legacy import.

## Selectors used

| Selector                                          | Source                                |
| ------------------------------------------------- | ------------------------------------- |
| `#username`, `#password`, `button[type=submit]`   | `apps/client/public/forms/login.html` |
| `canvas[data-game-ready="true"]`                  | `GameScene.ts` (set on first state)   |
| `[data-nameplate="<username>"]`                   | `Nameplate.ts` (DOM mirror)           |
| `[data-x-coord]`                                  | `Nameplate.ts` (numeric attr)         |
| `[data-chat-input]`                               | `ChatHUD.ts` (input field)            |
| `.chat-line`                                      | `ChatHUD.ts` (rendered chat lines)    |
| `window.__rebno?.room?.connection?.close?.()`     | `GameScene.ts` (test-only handle)     |

## Failure artifacts

On test failure, Playwright preserves:
- `apps/client/playwright-report/` — HTML reporter (open `index.html`)
- `apps/client/test-results/` — per-test traces + videos + screenshots

CI uploads these via `actions/upload-artifact@v4` for 14 days.

## Known caveats

- **Windows local dev:** `apps/server/src/index.ts:387` has a
  Linux-only entry guard that breaks on Windows (`import.meta.url` vs
  `process.argv[1]` slash mismatch). Local dev requires a one-line tooling
  fix; CI runs on ubuntu-latest unaffected. See
  `.planning/phases/06-client-rebuild-mvp-gate-cli-08-hard-milestone/deferred-items.md`.
- **Static-mount on staging:** server does not yet `app.use(express.static)`
  for `apps/server/public/`. Local-dev e2e bypasses this via the Vite dev
  server proxy; staging-deploy e2e via `STAGING_URL` requires the
  static-mount middleware to land first. Tracked in deferred-items.md.
- **Phase 4 D-04 input rate limit (10/s, burst 15):** the test's WASD
  press is one keydown + one keyup over ~1s — well within budget. If
  the e2e flakes on the move assertion in CI, check `rate-limit.ts`
  budget (plan 06-08 SUMMARY documents whether a relaxation was needed).

## Gates

- `pnpm --filter @rebno/client typecheck` — must pass
- `pnpm --filter @rebno/client test:e2e` — local GREEN proof
- `gh workflow run deploy-staging.yml` — CI staging gate (after task 3)
- `pnpm trace:check` — `[int->REQ-CLI-08]` must register as satisfied
