## ADDED Requirements ### Requirement: Layer 0 dep-graph isolation The `@suaveplan/testing-e2e` `dependencies` and `peerDependencies` MUST NOT contain any `@suaveplan/*` workspace package. `devDependencies` MAY include `@suaveplan/testing` for the package's own tests. #### Scenario: Runtime closure clean - **WHEN** `bunx turbo ls --filter=@suaveplan/testing-e2e...` is run - **THEN** no `@suaveplan/*` package appears in the runtime closure ### Requirement: Playwright primitive re-exports The package MUST re-export `test`, `expect`, `Page`, `Browser`, `BrowserContext`, `Locator`, `request` from `@playwright/test` so consumers import a single entry point. #### Scenario: Single-import consumer - **WHEN** a consumer imports `{ test, expect, Page }` from `@suaveplan/testing-e2e` - **THEN** the symbols are the Playwright originals (identity comparison passes) ### Requirement: `createPlaywrightConfig` produces stable defaults The factory SHALL emit a `PlaywrightTestConfig` with `fullyParallel: false`, `use.trace: "on-first-retry"`, `use.screenshot: "only-on-failure"`, `use.video: "retain-on-failure"`. CI mode (detected via `process.env.CI`) MUST set `forbidOnly: true`, `retries: 2`, `workers: 1`. Caller-supplied overrides MUST take precedence. #### Scenario: Default config - **WHEN** `createPlaywrightConfig({ baseURL: "http://localhost:3000" })` is called - **THEN** the returned config has `fullyParallel === false` AND `use.trace === "on-first-retry"` #### Scenario: CI overrides - **WHEN** the factory runs with `process.env.CI === "1"` and no override - **THEN** the config has `forbidOnly === true`, `retries === 2`, `workers === 1` #### Scenario: Caller override wins - **WHEN** `createPlaywrightConfig({ retries: 5 })` is called with `process.env.CI === "1"` - **THEN** the config has `retries === 5` ### Requirement: `PageObject` base class `PageObject` MUST be an abstract class accepting a Playwright `Page` in its constructor. It MUST expose `path: string` (abstract), `navigate(): Promise`, `waitForReady(opts?): Promise`, and convenience locator helpers (`findByRole`, `findByLabel`, `findByText`). #### Scenario: Subclass + navigate - **WHEN** a subclass extends `PageObject`, sets `path = "/users"`, and calls `await instance.navigate()` - **THEN** `page.goto("/users")` is called once #### Scenario: waitForReady aborts on signal - **WHEN** `waitForReady({ signal })` is called with a pre-aborted signal - **THEN** the promise rejects with `AbortedError` whose `code === "TESTING_E2E.ABORTED"` ### Requirement: Helpers expose stable signatures The following helpers MUST be exported with the signatures below and SHALL throw `TestingE2eError` subclasses on failure (never plain `Error`): - `waitForNetworkIdle(page, options?): Promise` — waits until the page sees `idleMs` of zero in-flight requests - `screenshotOnFailure(testInfo, page): Promise` — captures a screenshot named after the failing test - `dismissDialog(page, action?): Promise` — registers a one-shot dialog handler that auto-accepts (or auto-dismisses if `action === "dismiss"`) - `mockGeolocation(context, coords): Promise` — installs a static geolocation override - `mockClipboard(page): Promise` — installs a programmatic clipboard #### Scenario: waitForNetworkIdle resolves - **WHEN** the predicate observes zero in-flight requests for the configured `idleMs` window - **THEN** the promise resolves #### Scenario: waitForNetworkIdle times out - **WHEN** the network never quiets within `timeoutMs` - **THEN** the promise rejects with `WaitTimeoutError` whose `code === "TESTING_E2E.WAIT.TIMEOUT"` ### Requirement: Structured errors with stable codes All errors thrown from `@suaveplan/testing-e2e` MUST be subclasses of `TestingE2eError extends Error` carrying `{ code, cause?, context? }`. Codes match `^TESTING_E2E\.[A-Z]+\.[A-Z_]+$`. #### Scenario: Coded error - **WHEN** any helper throws - **THEN** the thrown value `instanceof TestingE2eError` is true AND `err.code` matches the regex ### Requirement: Coverage and docs gates Istanbul statement + branch + function + line coverage across `packages/universal/testing-e2e/src/` (excluding `*.bench.ts`, `*.schemas.ts`, barrel `index.ts` files) MUST be ≥ 95% on every dimension. Every implementation file MUST have a sibling `.md` (≥ 200 words, simple + advanced examples). #### Scenario: CI coverage gate - **WHEN** `bunx turbo test:coverage --filter=@suaveplan/testing-e2e` runs - **THEN** statement / branch / function / line coverage all ≥ 95% ### Requirement: Test placement follows the genesis hard rule Per-feature unit tests SHALL be co-located in `src/` (named `.test.ts` next to `.ts`). Cross-cutting tests (dep-graph guard, tree-shake guard, no-disallowed grep guard) MUST live in `tests/` sibling to `src/`. #### Scenario: Per-feature co-location - **WHEN** `src/page-objects/page-object.ts` exists - **THEN** its unit tests live at `src/page-objects/page-object.test.ts` #### Scenario: Cross-cutting in tests/ - **WHEN** a guard exercises package-level concerns (dep graph, tree-shake, lint) - **THEN** it lives under `tests/`