mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-21 10:03:29 +00:00
c8601a2031
* 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.
63 lines
2.0 KiB
TypeScript
63 lines
2.0 KiB
TypeScript
import { test as base, type BrowserContext } from '@playwright/test';
|
|
import { readFileSync } from 'node:fs';
|
|
import { dirname, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
|
|
/**
|
|
* Shared test fixtures.
|
|
*
|
|
* Specs import `test` from here (not `@playwright/test`) so every
|
|
* BrowserContext is pre-authenticated with the admin's API token and
|
|
* every test gets the resolved suite fixture (baseURL, workspace slug,
|
|
* derived admin username).
|
|
*
|
|
* We use a Bearer token rather than a session cookie because the Pad
|
|
* server binds sessions to the originating User-Agent (see
|
|
* internal/server/middleware_auth.go). A session minted in node.js and
|
|
* replayed from a headless Chromium context would be silently rejected;
|
|
* tokens have no such binding.
|
|
*/
|
|
|
|
export interface SuiteFixture {
|
|
baseURL: string;
|
|
workspaceSlug: string;
|
|
adminEmail: string;
|
|
adminUsername: string;
|
|
apiToken: string;
|
|
}
|
|
|
|
function loadSuiteFixture(): SuiteFixture {
|
|
const path = resolve(HERE, '.auth', 'fixture.json');
|
|
return JSON.parse(readFileSync(path, 'utf8')) as SuiteFixture;
|
|
}
|
|
|
|
async function applyTokenAuth(context: BrowserContext, fixture: SuiteFixture) {
|
|
await context.setExtraHTTPHeaders({
|
|
Authorization: `Bearer ${fixture.apiToken}`
|
|
});
|
|
}
|
|
|
|
// Standalone helper for the occasional spec that wants to build its
|
|
// own isolated context (e.g. to assert unauthed behaviour).
|
|
export function suiteFixture(): SuiteFixture {
|
|
return loadSuiteFixture();
|
|
}
|
|
|
|
// Extended test — use `import { test } from './fixtures'` in specs.
|
|
// The `context` fixture override applies the Bearer header once per
|
|
// context so downstream `page` navigation is authenticated.
|
|
export const test = base.extend<{ fixture: SuiteFixture }>({
|
|
context: async ({ context }, run) => {
|
|
const fixture = loadSuiteFixture();
|
|
await applyTokenAuth(context, fixture);
|
|
await run(context);
|
|
},
|
|
fixture: async ({}, run) => {
|
|
await run(loadSuiteFixture());
|
|
}
|
|
});
|
|
|
|
export { expect } from '@playwright/test';
|