mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-20 09:33:28 +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.
105 lines
3.7 KiB
TypeScript
105 lines
3.7 KiB
TypeScript
import { defineConfig, devices } from '@playwright/test';
|
|
import { dirname, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
// Anchor all test paths to the config file's directory so runs are
|
|
// invariant under the caller's cwd (Playwright's default resolves
|
|
// relative paths against cwd, which differs between `npx playwright
|
|
// test` and CI invocations).
|
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
|
|
/**
|
|
* Playwright config for Pad end-to-end smoke tests.
|
|
*
|
|
* The suite exercises the real Pad binary (built from this repo) serving
|
|
* its embedded web UI. That's the same shape a user gets from
|
|
* `pad server start`, so regressions that only show up once the web
|
|
* assets are embedded are caught here rather than in svelte-check.
|
|
*
|
|
* See e2e/global-setup.ts for how admin bootstrap + workspace seeding
|
|
* happen before any test runs.
|
|
*/
|
|
|
|
const E2E_PORT = Number(process.env.PAD_E2E_PORT ?? 17800);
|
|
const E2E_HOST = process.env.PAD_E2E_HOST ?? '127.0.0.1';
|
|
const BASE_URL = `http://${E2E_HOST}:${E2E_PORT}`;
|
|
|
|
// Build a private data dir for this run so the test instance never
|
|
// touches the developer's real ~/.pad or another CI run's artifacts.
|
|
const DATA_DIR = process.env.PAD_E2E_DATA_DIR ?? resolve(HERE, '..', '.pad-e2e');
|
|
|
|
// Pad binary relative to the repo root. `make build` / `make build-go`
|
|
// writes ./pad in the repo root; CI builds it explicitly before the
|
|
// e2e job runs.
|
|
const PAD_BINARY = process.env.PAD_BINARY ?? resolve(HERE, '..', 'pad');
|
|
|
|
|
|
export default defineConfig({
|
|
testDir: './e2e',
|
|
timeout: 30_000,
|
|
expect: { timeout: 5_000 },
|
|
|
|
// Playwright's reporter list kept minimal; CI uses the list reporter
|
|
// and uploads the HTML report as an artifact on failure.
|
|
reporter: process.env.CI ? [['list'], ['html', { open: 'never' }]] : 'list',
|
|
|
|
// Run tests in parallel but cap workers on CI to keep the suite under
|
|
// the 2-minute target quoted in TASK-689.
|
|
fullyParallel: true,
|
|
workers: process.env.CI ? 2 : undefined,
|
|
forbidOnly: !!process.env.CI,
|
|
retries: process.env.CI ? 1 : 0,
|
|
|
|
use: {
|
|
baseURL: BASE_URL,
|
|
trace: 'retain-on-failure',
|
|
video: 'retain-on-failure',
|
|
screenshot: 'only-on-failure'
|
|
// NOTE: auth is applied per-test via the fixture in e2e/fixtures.ts
|
|
// rather than config.use.storageState. The config approach proved
|
|
// flaky: Playwright resolves `storageState` against disk at
|
|
// project-setup time (before globalSetup runs), so the file written
|
|
// by globalSetup wasn't picked up. Passing cookies through an
|
|
// explicit `test.extend` fixture sidesteps that ordering entirely.
|
|
},
|
|
|
|
globalSetup: './e2e/global-setup.ts',
|
|
|
|
projects: [
|
|
{
|
|
name: 'desktop-chromium',
|
|
use: { ...devices['Desktop Chrome'] }
|
|
},
|
|
{
|
|
// Mobile viewport — triggers the mobile BottomSheet branch
|
|
// (CONVE-639: isMobile ≤ 639.98px on WorkspaceSwitcher).
|
|
// Pixel 7 ships with a Chromium defaultBrowserType, so we keep
|
|
// one installed browser for both projects and avoid downloading
|
|
// WebKit in CI.
|
|
name: 'mobile-chromium',
|
|
use: { ...devices['Pixel 7'] }
|
|
}
|
|
],
|
|
|
|
webServer: {
|
|
// Wipe the data dir BEFORE the server starts, so migrations run
|
|
// against an empty SQLite file every run. Doing this in globalSetup
|
|
// would race with the already-running server — it has the DB file
|
|
// open by then. The wrapper script is Node-based so it works on
|
|
// Windows (cmd/PowerShell), not just POSIX shells.
|
|
command: `node ${resolve(HERE, 'e2e', 'run-pad.mjs')}`,
|
|
url: `${BASE_URL}/api/v1/health`,
|
|
timeout: 30_000,
|
|
reuseExistingServer: !process.env.CI,
|
|
stdout: 'pipe',
|
|
stderr: 'pipe',
|
|
env: {
|
|
PAD_BINARY,
|
|
PAD_HOST: E2E_HOST,
|
|
PAD_PORT: String(E2E_PORT),
|
|
PAD_DATA_DIR: DATA_DIR,
|
|
PAD_LOG_LEVEL: 'warn'
|
|
}
|
|
}
|
|
});
|