Files
pad/web/e2e/dashboard.spec.ts
T
xarmian c8601a2031 test(e2e): Playwright smoke test infrastructure + 2 dashboard tests (TASK-689) (#225)
* test(e2e): Playwright smoke test infrastructure + 2 dashboard tests (TASK-689)

Option A of TASK-689: land the test infrastructure and a minimal smoke
test on both mobile and desktop viewports. Broader flow coverage (board
view drag, item detail, comments, mobile hamburger, BottomSheet
regression guard) is tracked as TASK-733.

Infrastructure
--------------
- web/playwright.config.ts: two projects (desktop-chromium, mobile-
  chromium via Pixel 7), reporter list+html, trace/video/screenshot
  retained on failure, webServer that wipes + recreates the data dir
  then runs the pad binary. Paths anchored to the config file's
  directory so runs are cwd-invariant.
- web/e2e/global-setup.ts: bootstraps admin via POST /auth/bootstrap,
  logs in, creates the e2e workspace, mints a user-scoped API token,
  and persists the token + resolved admin username to fixture.json.
- web/e2e/fixtures.ts: extends base test so every BrowserContext
  automatically gets Authorization: Bearer <token>. Uses a token
  rather than a session cookie because sessions are User-Agent bound
  in middleware_auth.go and a node-minted session would be rejected
  by a Chromium UA.

Tests
-----
- web/e2e/dashboard.spec.ts: a logged-in user lands on the seeded
  workspace, no login form is rendered, and the workspace name
  appears on the page. Runs in both project viewports.

CI
--
- New `e2e` job in .github/workflows/ci.yml: builds web UI + binary,
  installs Playwright chromium with OS deps, runs the suite, and
  uploads the HTML report as an artifact on failure. Timeout capped
  at 10 minutes (suite itself runs in ~4s today).

Local run (in mcr.microsoft.com/playwright:v1.59.1-noble): 2 passed
in 4.1s.

Parent: PLAN-644.
Follow-up: TASK-733 for broader flow coverage (Option B in the
original ship plan).

* fix(e2e): persist server-returned workspace slug instead of the constant (TASK-689)

Addresses Codex P2 on PR #225. When Playwright's `reuseExistingServer:
true` (local dev), a re-run of globalSetup hits `POST /api/v1/workspaces`
against a DB that already has `e2e`. The server uniquifies the slug
(`e2e` → `e2e-2` → …) and returns the uniquified value, but the old
code wrote `WORKSPACE_SLUG` (the constant) to fixture.json. Tests
then navigated to /e2e-admin/e2e — which might still exist from a
previous run with stale state — instead of /e2e-admin/e2e-2, missing
regressions in freshly-seeded content.

Fix: read `slug` back from the workspace-create response and use that
when writing fixture.json. Local re-runs now always point at the
workspace this run actually created.

Parent: PLAN-644.

* fix(e2e): cross-platform webServer bootstrap via Node wrapper (TASK-689)

Addresses Codex P2 on PR #225: `rm -rf && mkdir -p && pad server start`
in webServer.command is POSIX-only. Windows contributors on cmd.exe or
PowerShell can't run `npm run test:e2e` at all — the e2e suite becomes
Linux/macOS-only, defeating the "CI parity" goal.

Fix: extract the wipe-and-exec logic into web/e2e/run-pad.mjs. Node's
fs.rmSync / mkdirSync / child_process.spawn are uniform across
platforms, and the wrapper forwards SIGTERM/SIGINT so Playwright's
teardown still cleanly kills the child on suite exit.

Local re-run in mcr.microsoft.com/playwright:v1.59.1-noble: 2 passed.

Parent: PLAN-644.
2026-04-22 22:35:08 -04:00

29 lines
1.1 KiB
TypeScript

import { expect, test } from './fixtures';
import { WORKSPACE_NAME } from './global-setup';
/**
* Dashboard smoke test.
*
* Confirms that:
* - A logged-in user lands on a workspace route without redirecting
* to login (session from the auth fixture is honored).
* - The seeded workspace's name shows up on the page.
*
* If this test goes red, something fundamental in the auth → route →
* render path is broken; any deeper flow tests would be meaningless
* until it's green again.
*
* Runs in both project viewports; the dashboard should render in both.
*/
test('authed user sees the seeded workspace dashboard', async ({ page, fixture }) => {
await page.goto(`/${fixture.adminUsername}/${fixture.workspaceSlug}`);
// We shouldn't have been bounced to an inline "Sign in" form.
await expect(page.getByRole('button', { name: /^sign in$/i })).toHaveCount(0);
// Workspace name should be visible somewhere — top bar, title, or
// dashboard header all render it.
await expect(page.getByText(WORKSPACE_NAME, { exact: false }).first()).toBeVisible();
});