pm4ai

Testing

Testing conventions and strategies

Test execution strategy + Playwright e2e tuning.

MUST

  • Run only failing tests first; verify 2-3×; stop. Why: full suites are slow + noisy.
  • Pre-test: bun fix passes, kill stale procs (pkill -9 -f "next"), clear results (rm -rf test-results). Why: clean baseline.
  • Cheapest faithful harness first — unit → focused integration → headless script → service smoke → full UI/e2e. Why: reproduce a bug at the lowest tier that shows it; don’t loop a 40s e2e on a logic bug.
  • Paid-API tests (Anthropic, etc.) in dedicated files (*.cost.test.ts, smoke-*), cheapest model + shortest prompt, run LAST after free checks (lint → unit → build). Why: pass the zero-cost gate before anything billable.
  • After a failed paid-API cycle, record actual cost (tokens × model price) before the next attempt. Why: silent retries compound the bill without tracking lesson-rate.

NEVER

  • Never run the full suite unless explicitly asked. Cost: slow, noisy, masks the real failure.
  • Never scale to full suites blindly before verifying. Cost: wasted wall-clock.
  • Never run paid-API tests in a debug loop. Cost: real money per iteration.

Playwright E2E

ScopeTimeoutKill
Single test5s10s
Single file8s/test30s
Full suite10s/test180s
SymptomFix
Hangs on fill()/click()Check element visible/enabled
networkidle hangsUse waitForSelector() instead
Element not foundCheck testid on element vs parent
Flaky counts--workers=1

Playwright resolves a tsconfig extends by probing two paths, never by node resolution

  • Extend the REPO ROOT tsconfig from every sub-package in a repo that runs Playwright — "extends": "../../tsconfig.json" — and let the root keep the bare lintmax/tsconfig. Why: the runner tries the path relative to the config's own directory and then <configdir>/node_modules/<spec>, so the bare specifier resolves at the ROOT, where node_modules sits beside it, and nowhere deeper — while the root-relative chain applies the same base options (tsc --showConfig matches, the root adding only its own types) and names the gate once instead of once per package.
  • Sweep EVERY tsconfig a run loads, not the one beside playwright.config.ts. Why: the runner loads a tsconfig for each file it pulls in, so a shared e2e helper package and a vendored UI package fail exactly as the app does, and a fix confined to the app clears the first error and leaves the next waiting.
  • Read Failed to resolve "extends" path as an UNREACHABLE dependency, never a malformed tsconfig. Why: the message names the tsconfig, so the instinct is to rewrite it or reinstall, while the package is installed and sits one directory above where the loader looks. Adding the .json extension changes nothing, and neither does a walk-up when the specifier is an exports subpath alias — which the loader does not read.
  • Treat this error on an untouched repo as a TOOLCHAIN change and bisect the runner before editing any config. Why: an earlier version returned the unresolved path without checking it exists and its loader then skipped the missing file in silence, so the base config was never applied and nothing said so; running the previous version against the identical tree separates a newly-strict error from a broken config in one command, and it surfaces the finding that matters — the shared options were silently absent all along.
  • Keep the resolvable spelling even after the runner stops erroring. Why: upstream reverted to not throwing and declined tsc-compatible resolution for now, so the bare specifier returns to being silently IGNORED rather than resolved — green with the shared strictness dropped, which is the failure this whole class is made of.
  • Verify with the runner itself — bunx playwright test --list from the package directory — and treat reaching a RUNTIME guard as the pass. Why: tsc and every editor resolve the specifier correctly through node resolution, so neither can confirm or deny this; only the runner's own loader can.

On this page