Skip to content

test(web): Playwright e2e harness with auth-gate and dashboard specs#76

Merged
CoderCoco merged 3 commits into
mainfrom
claude/issue-74-e2e-test-harness
May 3, 2026
Merged

test(web): Playwright e2e harness with auth-gate and dashboard specs#76
CoderCoco merged 3 commits into
mainfrom
claude/issue-74-e2e-test-harness

Conversation

@CoderCoco
Copy link
Copy Markdown
Owner

Closes #74

Summary

Adds a Playwright e2e harness for @gsd/web that runs against the production build (vite build + vite preview) with every /api/* call stubbed at the network layer. The Nest server is never started — this is the stubs-only tier (#74); the full-stack tier with a real Nest server lands separately in #75.

  • New scripts: npm run app:test:e2e (root) → playwright test in @gsd/web.
  • New CI job (.github/workflows/e2e.yml) installs the Chromium browser with system deps, runs the suite on every PR, and uploads playwright-report/ (traces + videos) as an artifact on failure.
  • Co-located fixtures and stubs under app/packages/web/e2e/fixtures/; specs under e2e/specs/.
  • A custom authedPage Playwright fixture pre-seeds localStorage.apiToken so most specs start authenticated; auth-gate specs use the base test to exercise the 401-trigger path.
  • A catch-all page.route('**/api/**') returns 404 for any endpoint not explicitly stubbed so missing stubs surface as fast test failures rather than hangs.

Spec coverage delivered with this PR

Auth gate (auth-gate.spec.ts):

  • should show token modal when API returns 401
  • should load dashboard when valid token is already stored
  • should save token and show dashboard after reload

Dashboard (dashboard.spec.ts):

  • should render a game card for a stopped game / for a running game with IP
  • should render multiple game cards
  • should show empty-state message when no games are configured
  • should fire POST /api/start/:game when Start is clicked
  • should disable Start button for a running game
  • Sidebar nav specs for /logs, /discord, /settings

Per-surface specs (Costs panel, Stop confirmation, polling indicator, etc.) follow with their respective UI issues (#60#68) — the harness is now in place to plug them in.

Acceptance criteria from #74

  • npm run app:test:e2e runs the suite locally and in CI against the production build.
  • CI fails the PR when a spec breaks; traces + videos uploaded as artifacts on failure.
  • At least the auth-gate and dashboard Start specs land with this issue.
  • docs/docs/components/management-app.md gains a "Running e2e tests" section.
  • No real AWS / Discord calls — every external request is stubbed.
  • npm run app:test continues to pass; e2e suite runs as a separate command.
  • CLAUDE.md's "Code & Test Conventions" section now describes the two-tier strategy (test(web): integration / e2e test harness for the dashboard #74 stubs vs test: full-stack integration test suite (real Nest server + mocked AWS SDK) #75 real Nest) and when to add specs to each tier.

Implementation notes

  • Chose Playwright over Vitest browser mode (per the issue's open question) for the trace viewer, parallel projects, and first-class CI artifacts story. Vitest unit tests remain on Vitest — only browser-driven specs use Playwright.
  • The webServer command is npm run build && npm run preview; Playwright polls until http://localhost:4173 responds. reuseExistingServer is true locally so re-runs are fast if vite preview is already running.
  • Cleanup: added app/packages/web/test-results/, playwright-report/, and playwright/.cache/ to .gitignore so local artifacts don't get committed.

Test plan

  • CI: e2e workflow runs and all 12 specs pass (Chromium, Ubuntu).
  • Local: sudo npx playwright install-deps chromium once, then npm run app:test:e2e from repo root passes.
  • npm run app:test still passes (the unit-test suite is untouched).
  • Reviewer can open app/packages/web/playwright-report/index.html after a local run to inspect traces/videos.

CoderCoco and others added 2 commits May 3, 2026 01:31
Closes #74

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 3, 2026 05:43
Comment thread .github/workflows/e2e.yml Fixed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a Playwright-based e2e test harness for @gsd/web (tier-1 stubs-only) that runs against the production Vite build/preview, plus CI wiring and docs describing how to run the suite.

Changes:

  • Add root and workspace scripts to run Playwright e2e tests (npm run app:test:e2e@gsd/web).
  • Introduce Playwright config, fixtures/stubs, and initial specs (auth gate + dashboard + sidebar navigation).
  • Add CI workflow to run e2e on PRs and upload Playwright HTML report artifacts on failure; document e2e usage and the two-tier testing strategy.

Reviewed changes

Copilot reviewed 10 out of 12 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
package.json Adds root app:test:e2e script targeting the web workspace.
package-lock.json Adds Playwright dependencies and lockfile updates to support the new harness.
docs/docs/components/management-app.md Documents how to run the Playwright e2e suite locally/CI.
app/packages/web/playwright.config.ts Playwright runner configuration (testDir, webServer via Vite build/preview, reporters).
app/packages/web/package.json Adds test:e2e script and Playwright dev dependency.
app/packages/web/e2e/specs/dashboard.spec.ts Dashboard/Start button/sidebar navigation e2e specs.
app/packages/web/e2e/specs/auth-gate.spec.ts Auth-gate e2e specs covering 401 → modal and token persistence.
app/packages/web/e2e/fixtures/index.ts authedPage fixture + stubApis() network stubbing helper.
app/packages/web/e2e/fixtures/game-data.ts Shared fixture payloads for env/status/costs.
CLAUDE.md Documents the two-tier browser testing strategy and Playwright conventions.
.gitignore Ignores Playwright reports/results/cache directories.
.github/workflows/e2e.yml New CI job to run e2e tests and upload Playwright reports on failure.

Comment on lines +76 to +82
test('should navigate to the Settings page via sidebar', async ({ authedPage: page }) => {
await stubApis(page, { statuses: [] });
await page.goto('/');

await page.getByRole('link', { name: 'Settings' }).click();
await expect(page).toHaveURL('/settings');
});
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1cd9f87 — added an /api/config stub (handling both GET and POST) directly in stubApis() so the /settings route loads cleanly. Also added a WATCHDOG_CONFIG fixture and a config?: WatchdogConfig override on StubOptions for parity with the other stubs.


Generated by Claude Code

- Reverse stubApis() route registration: register catch-all 404 first,
  specific stubs after. Playwright matches handlers in reverse
  registration order, so the previous order made the catch-all
  intercept every /api/* request and the dashboard never received
  data — the cause of the failing 'should render a game card' specs.
  Apply the same fix to the inline route handlers in auth-gate.spec.
- Add /api/config stub to stubApis() so navigating to /settings does
  not trigger an unhandled rejection in WatchdogPanel (Copilot review).
- Add permissions: contents: read to .github/workflows/e2e.yml
  (CodeQL / GHAS finding).
- Exclude packages/web/e2e/** from React/react-hooks ESLint rules so
  the Playwright fixture pattern (await use(page)) doesn't trip
  rules-of-hooks; remove the unused 'expect' import and add the
  missing JSDoc for StubOptions.
@CoderCoco
Copy link
Copy Markdown
Owner Author

@copilot review this again please

Copy link
Copy Markdown
Contributor

Copilot AI commented May 3, 2026

@copilot review this again please

Reviewed — everything looks clean now. The fixes in 1cd9f87 address all the previously flagged issues:

  • Route precedence in auth-gate.spec.ts: catch-all is registered first so specific per-endpoint handlers (including the 401-trigger ones) take precedence over it — now consistent with stubApis().
  • /api/config stub in stubApis(): handles both GET and POST; WATCHDOG_CONFIG fixture and config?: WatchdogConfig on StubOptions added for per-spec overrides.
  • CI permissions: contents: read block is in place.
  • ESLint: e2e files excluded from all four React rule blocks.

All CI jobs pass (Lint, Test, E2E, Docs). No remaining issues.

@CoderCoco CoderCoco merged commit bd89b17 into main May 3, 2026
9 checks passed
@CoderCoco CoderCoco deleted the claude/issue-74-e2e-test-harness branch May 3, 2026 16:11
CoderCoco pushed a commit that referenced this pull request May 3, 2026
The Playwright harness from #76 hard-coded "Online"/"Offline" badges and
both Start+Stop buttons being present on every card — both assumptions
broke when #60 swapped to RUNNING/STOPPED labels and a single-state
primary action.

- ACTUAL_COSTS fixture + /api/costs/actual stub so the new KPI strip's
  sparkline fetch resolves without falling through the catch-all 404.
- Dashboard specs updated to look up cards by their <h3> game-name
  heading (avoids cross-matching the sparkline / sidebar text), assert
  STOPPED/RUNNING badges, and handle the single-CTA pattern by checking
  Start has count 0 while a game is running.
- New specs: search-filter narrows the grid, empty-state on no match,
  and KPI strip renders the four ops tiles with the live X/Y count.
- GameCard: drop the redundant body connect-string row — hostname now
  lives only in the header next to the copy button so getByText() finds
  a single match per card.
CoderCoco added a commit that referenced this pull request May 4, 2026
Closes #60

## Summary

- New `KpiStrip` at the top of `/` — four ops tiles (Servers running,
Spend today, Forecast MTD, Active alerts) each with a top color accent
rule and a 7-bar sparkline driven by `/api/status` +
`/api/costs/estimate` + `/api/costs/actual`.
- `GameCard` redesigned: gradient top-accent rule keyed to state,
Outfit-bold game name above DM Mono hostname (with copy button), status
badge with icon + pulsing/animated dot covering all 5 states (RUNNING /
STARTING / STOPPED / NOT_DEPLOYED / ERROR), 2×2 stats grid (Last run /
Players / $/hr / Task short-id), Start-or-Stop gradient primary +
Files/Logs secondary actions.
- `DashboardPage` adds a client-side search input (filters by game name
or hostname) and switches to a responsive 3 → 2 → 1 column grid.
- e2e specs from #76 realigned with the new design — STOPPED/RUNNING
badges, single-CTA pattern (`Start.toHaveCount(0)` while running) — plus
new specs covering search filter, empty-state on no matches, and KPI
tile rendering.
- `CLAUDE.md` documents the `issue-flow` plugin as the canonical issue →
PR driver, complementing the existing `/pr` command.

## Verification — acceptance criteria mapped to landing

| AC | Where it lives |
|---|---|
| KPI strip renders 4 tiles with live data + sparkline |
`app/packages/web/src/components/KpiStrip.tsx` |
| GameCard uses shadcn Card / Button / Badge from #58 | `GameCard.tsx`
(Card root, Button variants `start`/`stop`/`secondary`/`ghost`, state
Badge) |
| Status badge shows icon + text on all 5 states | `STATE_LABELS` +
`StateIcon` in `GameCard.tsx` |
| Search filter narrows grid in real time | `DashboardPage.tsx`
`useMemo` filter on game name + hostname |
| Start, Stop, Files, Logs reachable from new card | `GameCard.tsx`
action row; Logs links to `/logs` (the route placeholder lands fully in
#63) |
| `npm run app:test` passes; existing tests updated, not deleted | 258
tests pass; no prior GameCard tests existed |

## Implementation notes

- The redesigned card swaps the primary CTA based on state instead of
disabling the inactive one — cleaner UX, but it required updating the
e2e spec from `Start.toBeDisabled()` to `Start.toHaveCount(0)` for
running games.
- KPI sparkline data is currently a single shared `/api/costs/actual`
series across all four tiles (cost itself for the cost tiles; same
series as a coarse activity proxy for Servers running; flat zeros for
Active alerts when count is 0). Per-tile historical series would need
new endpoints.
- Forecast MTD uses average-daily-spend × calendar-days-in-month —
simple extrapolation, no Cost Explorer Forecast API.
- The `Logs` per-card button is `<Link to="/logs">` (asChild). The
`/logs` page is a placeholder until #63 lands — the link exists so the
action is "reachable" per the AC.

## Test plan

- [ ] Local: `npm run app:dev`, browse to `/`, verify the four KPI tiles
render with live values and a 7-bar sparkline beneath each.
- [ ] Type a partial game name or hostname into the search input — grid
narrows in real time; clear it — grid restores.
- [ ] Start a stopped game from a card — primary CTA swaps to Stop after
the 3s refresh; click Files — modal opens; click Logs — navigates to
`/logs`.
- [ ] CI: `npm run app:test` (vitest, 258 tests) and `npm run
app:test:e2e` (Playwright via `.github/workflows/e2e.yml`) both green.


---
_Generated by [Claude
Code](https://claude.ai/code/session_01TiPfpb2vvkRMMXqc9oX5d2)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test(web): integration / e2e test harness for the dashboard

5 participants