The E2E harness runs the real pad binary with the real rate limiter, and
every Playwright test shares one loopback IP (127.0.0.1). The auth limiter
(5 logins/min/IP, burst 5) trips as soon as a spec logs in a couple of
browser clients — collab-persistence.spec.ts logs in two per test — so
browserLogin fails with "in-page login failed with status 429". This was
deterministic, not flaky: it failed on TASK-2058's own PR and its push to
main, and on every downstream PR since.
Add a test-only env knob PAD_DISABLE_RATE_LIMITS: when truthy, New() leaves
Server.rateLimiters nil, which RateLimit() already treats as a pass-through
(Stop() and the MCP path are already nil-safe). Wire it into the Playwright
webServer.env; run-pad.mjs spawns the binary with inherited env so it
reaches the pad process. Limiters stay fully active in prod/self-host — the
knob is an explicit opt-in only the E2E server sets.
Verified: collab-persistence.spec.ts passes locally with the fix; the
existing limiter tests still pass (limiters on when the env is unset); new
TestRateLimit_DisabledByEnv pins the bypass.
Claude-Session: https://claude.ai/code/session_015yuBJQYfDj95cgX3DaD8SF
* 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.