mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-10 15:05:40 +00:00
c8492db29f
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
111 lines
4.0 KiB
TypeScript
111 lines
4.0 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',
|
|
// Every E2E test shares one loopback IP, so the auth limiter
|
|
// (5 logins/min/IP) trips as soon as a spec logs in a couple of
|
|
// browser clients (BUG-2089). Disable rate limiting for the E2E
|
|
// server only — never in prod/self-host. run-pad.mjs spawns the
|
|
// binary with inherited env, so this reaches the pad process.
|
|
PAD_DISABLE_RATE_LIMITS: '1'
|
|
}
|
|
}
|
|
});
|