Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .agents/docs/architecture/testing-strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,8 @@ coverage is therefore a smoke subset, not the full suite.

`webServer` boots `apps/api`, `apps/gateway` and `apps/web` by running each app's own `pnpm dev:test`
with `NODE_ENV=test`, which is what gives the API an in-memory Mongo replica set instead of an
external database. `workers: 1` on CI; `fullyParallel` otherwise. The default assertion timeout is
external database. `workers: 1` on CI and 2 elsewhere, with `fullyParallel` on (`testing/AGENTS.md`
§Running has why, and the override). The default assertion timeout is
raised to 15s because a 2-core runner doing real DB work misses the 5s default.

## Known warts
Expand Down
2 changes: 2 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ PLAYGROUND_DEV_SERVER_PORT=3750
GATEWAY_DEV_SERVER_PORT=3500
# The port to use for the Vite (full web app) development server
WEB_DEV_SERVER_PORT=3000
# The number of end-to-end tests to run in parallel (default: 2, or 1 when CI is set)
E2E_WORKERS=
# Set an arbitrary delay (in milliseconds) for all responses (useful for testing suspense)
API_RESPONSE_DELAY=
# If set to 'true' and NODE_ENV === 'development', then login is automated
Expand Down
8 changes: 8 additions & 0 deletions testing/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,14 @@ preview is an iframe on the other loopback name, so the spec drives it through a
is served from the root of the API, and its Redoc page requests `/spec.json`, which the web origin's
`/api` proxy does not route.

**The suite runs on two workers, and one on CI.** Playwright's own default of half the cores logs in
faster than the API's login throttle allows (50 a minute), so tests partway through the suite fail
with `Login failed … (429)`, and each failure restarts a worker that logs in again. Set `E2E_WORKERS`
in the root `.env` to change the count; `src/support/env.ts` reads it and throws on anything but a
positive integer. A shell export loses to `.env` wherever `.env` carries the key, blank included: the
`test:*` scripts load it through `env-cmd`, whose values win, and `.env.template` carries the key
blank. For one run, pass `--workers=<n>` after the script instead.

Projects: `setup` (with `teardown` attached), `chromium` (every spec), and `firefox`, which greps
`/@smoke/`. **Cross-browser coverage exists only for tests whose title contains `@smoke`** — add the
tag to a critical flow, leave it off everything else.
Expand Down
6 changes: 4 additions & 2 deletions testing/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as path from 'node:path';

import { defineConfig, devices } from '@playwright/test';

import { apiPort, baseURL, gatewayPort, playgroundPort, webPort } from './src/support/env';
import { apiPort, baseURL, gatewayPort, playgroundPort, webPort, workerCount } from './src/support/env';

const webServerDefaults = {
gracefulShutdown: { signal: 'SIGINT', timeout: 1000 },
Expand Down Expand Up @@ -65,5 +65,7 @@ export default defineConfig({
url: `http://localhost:${playgroundPort}`
}
],
workers: process.env.CI ? 1 : undefined
// Playwright's own default is half the cores. That many workers log in faster than the API's login
// throttle allows partway through the suite, and leave a developer's machine little for anything else.
workers: workerCount ?? (process.env.CI ? 1 : 2)
});
15 changes: 15 additions & 0 deletions testing/src/support/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ function requiredNumber(name: string): number {
return value;
}

function optionalPositiveInteger(name: string): number | undefined {
const raw = process.env[name];
if (!raw) {
return undefined;
}
const value = parseNumber(raw);
if (!Number.isInteger(value) || value < 1) {
throw new Error(`Expected ${name} to be a positive integer, got ${raw}`);
}
return value;
}

// Read straight from the same `.env` apps/web builds `config.meta.contactEmail` from: a developer's
// value differs from the `.env.template` one CI generates, so a literal here only passes locally.
export const contactEmail = required('CONTACT_EMAIL');
Expand All @@ -25,6 +37,9 @@ export const gatewayPort = requiredNumber('GATEWAY_DEV_SERVER_PORT');
export const playgroundPort = requiredNumber('PLAYGROUND_DEV_SERVER_PORT');
export const webPort = requiredNumber('WEB_DEV_SERVER_PORT');

/** How many tests run at once, where `.env` says; `playwright.config.ts` holds the default. */
export const workerCount = optionalPositiveInteger('E2E_WORKERS');

/** How often, in milliseconds, the API pulls completed assignments from the gateway. */
export const gatewayRefreshInterval = requiredNumber('GATEWAY_REFRESH_INTERVAL');

Expand Down
Loading