## Context `@suaveplan/testing-e2e` is the third Layer 0 testing-infrastructure package on genesis. It wraps Playwright with conventions, page objects, and a config factory. Layer 0 dependency profile: external catalog deps only. No `@suaveplan/*` workspace deps in runtime closure. `@suaveplan/testing` permitted as `devDependency` for the package's own tests. ## Goals / Non-Goals **Goals:** - Thin, conventional Playwright wrapper. No forking of Playwright internals. - `createPlaywrightConfig()` absorbs WSL2 + CI defaults so every consumer gets stable runs without copy-paste. - `PageObject` base class for the typed page-object pattern. - Local `TestingE2eError` hierarchy with stable `TESTING_E2E..` codes. - ≥ 95% coverage on every dimension; co-located unit tests + cross-cutting `tests/` per the genesis test-placement hard rule. **Non-Goals:** - A full Playwright re-implementation. We re-export, we don't rebuild. - Browser launching automation. Playwright already handles that; we expose its config. - React-specific helpers. Those live in `@suaveplan/testing-dom`. - Real-browser E2E tests of the package itself — the unit tests for our helpers run under vitest with happy-dom; real Playwright E2E of the package would be circular and impractical. ## Decisions ### D1: Local `TestingE2eError` hierarchy Mirrors the shape from `@suaveplan/testing` and `@suaveplan/testing-dom`. Subclasses: `NavigationError`, `WaitTimeoutError`, `ConfigError`, `FixtureError`, `AbortedError`. Codes follow `TESTING_E2E..` pattern. ### D2: `createPlaywrightConfig(options)` Factory accepting `{ baseURL, projects, reporter, ...overrides }` and returning a `PlaywrightTestConfig`: - `fullyParallel: false` by default (WSL2 stability) - `forbidOnly: !!process.env.CI` - `retries: process.env.CI ? 2 : 0` - `workers: process.env.CI ? 1 : undefined` - `use.trace: "on-first-retry"` - `use.screenshot: "only-on-failure"` - `use.video: "retain-on-failure"` - Caller can override every default via `options`. ### D3: `PageObject` base class ```ts export abstract class PageObject { constructor(protected readonly page: Page) {} abstract readonly path: string; async navigate(): Promise { ... } async waitForReady(opts?: { signal? }): Promise { ... } findByRole(role, options?): Locator { ... } findByLabel(text): Locator { ... } findByText(text): Locator { ... } } ``` Extension by composition: subclasses add typed locators and domain methods. ### D4: Test fixtures via Playwright's `test.extend` `extendedTest` exposes: - `apiContext`: a `request` instance auto-created per test - `freshPage`: a new browser context + page (isolated from any auth fixtures) - `defaultTimeout`: configurable per-suite default ### D5: Helpers - `waitForNetworkIdle(page, opts?)` — wait until N consecutive ticks with no in-flight requests - `screenshotOnFailure(testInfo, page)` — manual fallback for non-fixture-driven captures - `dismissDialog(page)` — registers a one-shot dialog handler - `mockGeolocation(page, coords)` — install a geolocation override via context.grantPermissions + setGeolocation - `mockClipboard(page)` — install a programmatic clipboard via `page.evaluate` ### D6: OTEL gated behind env flag Same pattern as testing-dom: counters/histograms behind `process.env.TESTING_E2E_OTEL_ENABLED === "1"`. ### D7: Tests run under vitest (not Playwright) Counter-intuitive but correct: testing-e2e is a LIBRARY exposing Playwright helpers. Its OWN unit tests run under vitest because vitest is faster and the helpers themselves don't need a real browser. We test the typed shapes, error throws, config defaults, etc. Real Playwright tests live in app packages that consume testing-e2e. ## Risks / Trade-offs - **Playwright version churn** — major Playwright bumps occasionally change `PlaywrightTestConfig` shapes. Mitigation: pin via catalog; bump deliberately. - **Page-object generality** — a one-size base class can't anticipate every consumer's pattern. Mitigation: keep `PageObject` minimal; document the intended subclassing pattern. - **Helpers tested under vitest, not Playwright** — covers shape but not real-browser behavior. Mitigation: mark helpers as "best effort" in docs; recommend integration tests in consumer apps. - **`devDependency on @suaveplan/testing`** — same as testing-dom; documented exception.