Files
2026-05-27 23:29:53 +01:00

5.1 KiB

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<void>, waitForReady(opts?): Promise<void>, 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<void> — waits until the page sees idleMs of zero in-flight requests
  • screenshotOnFailure(testInfo, page): Promise<void> — captures a screenshot named after the failing test
  • dismissDialog(page, action?): Promise<void> — registers a one-shot dialog handler that auto-accepts (or auto-dismisses if action === "dismiss")
  • mockGeolocation(context, coords): Promise<void> — installs a static geolocation override
  • mockClipboard(page): Promise<void> — 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 <feature>.test.ts next to <feature>.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/