Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,70 @@
## ADDED Requirements
### Requirement: Storybook test-runner config factory
The `@suaveplan/testing-dom` package SHALL export `createStorybookTestRunnerConfig(opts?)` returning a `@storybook/test-runner` config object that wires a `postVisit` hook capturing a screenshot for every story variant. The threshold (`maxDiffPixelRatio: 0.005`), animation policy (`animations: 'disabled'`), and viewport (`1280×720`) MUST match the e2e visual path defined in `testing-e2e/spec.md`.
#### Scenario: Threshold matches e2e path
- **WHEN** `createStorybookTestRunnerConfig()` is invoked with no overrides
- **THEN** the resulting config's screenshot threshold equals the value emitted by `configureVisualRegression()` in `@suaveplan/testing-e2e`
#### Scenario: postVisit hook installed
- **WHEN** the test-runner executes a story
- **THEN** the `postVisit` hook captures a screenshot named after the story id
### Requirement: `pinStoryEnvironment` determinism harness
The package SHALL export `pinStoryEnvironment(page)` which locks viewport to `1280×720`, awaits `document.fonts.ready`, sets `prefers-reduced-motion: reduce`, and applies the story's declared `colorScheme` (`light` or `dark`) via `page.emulateMedia`. Failure to reach font readiness within `timeoutMs` MUST throw `StorybookVisualError` with `code === 'TESTING_DOM.STORYBOOK_VISUAL.RUNNER_LOAD_FAILED'`.
#### Scenario: Determinism levers applied
- **WHEN** `pinStoryEnvironment(page)` resolves
- **THEN** `prefers-reduced-motion` is `'reduce'` AND fonts are loaded AND viewport is `1280×720`
#### Scenario: Color scheme honoured
- **WHEN** a story declares `parameters.colorScheme === 'dark'`
- **THEN** `pinStoryEnvironment(page)` calls `page.emulateMedia({ colorScheme: 'dark' })` before capture
### Requirement: Theme and locale decorator pinning
`ThemeDecorator` and `LocaleDecorator` consumed by `*.stories.tsx` files SHALL apply a deterministic theme and locale per story file (no per-render randomness, no `Date.now()`-derived choices). Stories that need to vary theme or locale MUST do so via explicit `parameters` rather than runtime state.
#### Scenario: Decorator output stable
- **WHEN** the same story is rendered twice with identical `parameters`
- **THEN** the resulting DOM and visual screenshot are bit-identical
### Requirement: Per-variant baseline coverage
Every `*.stories.tsx` file registered with the Storybook test-runner SHALL produce a baseline screenshot per declared variant (named export). Stories with no baseline on the active platform MUST throw `StorybookVisualError` with `code === 'TESTING_DOM.STORYBOOK_VISUAL.BASELINE_MISSING'` unless `--update-snapshots` is in effect.
#### Scenario: Variant baseline missing
- **WHEN** a story variant has no baseline on the active platform AND `--update-snapshots` is NOT set
- **THEN** the run throws `StorybookVisualError` with `code === 'TESTING_DOM.STORYBOOK_VISUAL.BASELINE_MISSING'`
#### Scenario: Variant pixel diff exceeded
- **WHEN** a variant comparison exceeds the configured `maxDiffPixelRatio`
- **THEN** the run throws `StorybookVisualError` with `code === 'TESTING_DOM.STORYBOOK_VISUAL.PIXEL_DIFF_EXCEEDED'` whose `context` contains `{ storyId, baselinePath, actualPath, diffPath, pixelRatio }`
### Requirement: Structured error codes
All errors thrown from the Storybook visual helpers SHALL be subclasses of `TestingDomError` carrying `{ code, cause?, context? }` and SHALL match the regex `^TESTING_DOM\.STORYBOOK_VISUAL\.[A-Z_]+$`. Defined codes are `TESTING_DOM.STORYBOOK_VISUAL.PIXEL_DIFF_EXCEEDED`, `TESTING_DOM.STORYBOOK_VISUAL.BASELINE_MISSING`, `TESTING_DOM.STORYBOOK_VISUAL.RUNNER_LOAD_FAILED`.
#### Scenario: Coded error
- **WHEN** any Storybook visual helper throws
- **THEN** the thrown value `instanceof TestingDomError` is true AND `err.code` matches the regex
### Requirement: Coverage gate at 100%
Istanbul statement, branch, function, and line coverage across `packages/testing/testing-dom/src/storybook-visual/` (excluding `*.bench.ts`, `*.schemas.ts`, barrel `index.ts`) MUST be 100% on every dimension. The pre-publish gate (`bunx turbo test:coverage --filter=@suaveplan/testing-dom`) hard-fails below 100%.
#### Scenario: Coverage gate enforces 100%
- **WHEN** `bunx turbo test:coverage --filter=@suaveplan/testing-dom` runs
- **THEN** statements, branches, functions, and lines coverage all equal 100%

View File

@@ -0,0 +1,84 @@
## ADDED Requirements
### Requirement: `configureVisualRegression` factory
The `@suaveplan/testing-e2e` package SHALL export `configureVisualRegression(config?)` returning a `PlaywrightTestConfig` fragment with `expect.toHaveScreenshot.maxDiffPixelRatio` defaulted to `0.005`, `expect.toHaveScreenshot.animations` defaulted to `'disabled'`, `expect.toHaveScreenshot.caret` defaulted to `'hide'`, and `use.viewport` defaulted to `{ width: 1280, height: 720 }`. Caller-supplied values MUST take precedence over defaults.
#### Scenario: Defaults emitted
- **WHEN** `configureVisualRegression()` is called with no arguments
- **THEN** the returned fragment contains `expect.toHaveScreenshot.maxDiffPixelRatio === 0.005` AND `expect.toHaveScreenshot.animations === 'disabled'` AND `use.viewport.width === 1280`
#### Scenario: Caller override wins
- **WHEN** `configureVisualRegression({ expect: { toHaveScreenshot: { maxDiffPixelRatio: 0.001 } } })` is called
- **THEN** the returned fragment has `expect.toHaveScreenshot.maxDiffPixelRatio === 0.001`
### Requirement: `prepareForVisual` determinism harness
The package SHALL export `prepareForVisual(page)` which MUST: disable animations via `page.emulateMedia({ reducedMotion: 'reduce' })`, wait for `page.waitForLoadState('networkidle')`, await `document.fonts.ready` inside the page context, and assert the active viewport matches the configured visual viewport. Failure to reach font readiness within `timeoutMs` MUST throw `VisualRegressionError` with `code === 'TESTING_E2E.VISUAL.FONTS_NOT_READY'`.
#### Scenario: Determinism levers applied
- **WHEN** `prepareForVisual(page)` resolves successfully
- **THEN** `prefers-reduced-motion` is `'reduce'` AND `document.fonts.status === 'loaded'` AND the page has reached `networkidle`
#### Scenario: Font timeout
- **WHEN** `document.fonts.ready` does not resolve within `timeoutMs`
- **THEN** the call throws `VisualRegressionError` with `code === 'TESTING_E2E.VISUAL.FONTS_NOT_READY'`
#### Scenario: Viewport mismatch
- **WHEN** the page viewport differs from the configured visual viewport at capture time
- **THEN** the call throws `VisualRegressionError` with `code === 'TESTING_E2E.VISUAL.VIEWPORT_MISMATCH'`
### Requirement: Per-OS baseline storage
Visual baselines SHALL be stored under `apps/<app>/e2e/__snapshots__/<platform>/<spec>/<test>.png` where `<platform>` is one of `linux`, `darwin`, `win32`. A run on a platform with no baseline directory MUST NOT pass implicitly — it MUST throw `VisualRegressionError` with `code === 'TESTING_E2E.VISUAL.BASELINE_MISSING'` unless `--update-snapshots` is in effect.
#### Scenario: Missing baseline fails
- **WHEN** an e2e spec calls `expect(page).toHaveScreenshot()` on a platform with no baseline directory and `--update-snapshots` is NOT set
- **THEN** the call throws `VisualRegressionError` with `code === 'TESTING_E2E.VISUAL.BASELINE_MISSING'`
#### Scenario: Pixel diff exceeded
- **WHEN** a comparison exceeds the configured `maxDiffPixelRatio`
- **THEN** the call throws `VisualRegressionError` with `code === 'TESTING_E2E.VISUAL.PIXEL_DIFF_EXCEEDED'` whose `context` contains `{ baselinePath, actualPath, diffPath, pixelRatio }`
### Requirement: Baseline-update workflow discipline
Updates to committed baselines SHALL only occur via runs invoked with `--update-snapshots` AND MUST land in a dedicated commit prefixed `chore(visual): refresh baselines` separate from any feature or fix commit.
#### Scenario: Baseline refresh commit isolated
- **WHEN** baselines are updated
- **THEN** the resulting commit contains only `__snapshots__/` changes AND its message starts with `chore(visual): refresh baselines`
### Requirement: Per-spec visual invariant
Every page-level Playwright spec under `apps/<app>/e2e/` that asserts rendered DOM SHALL also call `expect(page).toHaveScreenshot()` after `prepareForVisual(page)` resolves. Specs that exclusively test API endpoints (no DOM render) are exempt.
#### Scenario: Page-level spec includes screenshot
- **WHEN** a spec navigates to a route, runs `prepareForVisual(page)`, and asserts rendered DOM
- **THEN** the spec also calls `expect(page).toHaveScreenshot()` before completing
### Requirement: Structured error codes
All errors thrown from the visual helpers SHALL be subclasses of `TestingE2eError` carrying `{ code, cause?, context? }` and SHALL match the regex `^TESTING_E2E\.VISUAL\.[A-Z_]+$`. Defined codes are `TESTING_E2E.VISUAL.PIXEL_DIFF_EXCEEDED`, `TESTING_E2E.VISUAL.BASELINE_MISSING`, `TESTING_E2E.VISUAL.FONTS_NOT_READY`, `TESTING_E2E.VISUAL.VIEWPORT_MISMATCH`.
#### Scenario: Coded error
- **WHEN** any visual helper throws
- **THEN** the thrown value `instanceof TestingE2eError` is true AND `err.code` matches the regex
### Requirement: Coverage gate at 100%
Istanbul statement, branch, function, and line coverage across `packages/testing/testing-e2e/src/visual/` (excluding `*.bench.ts`, `*.schemas.ts`, barrel `index.ts`) MUST be 100% on every dimension. The pre-publish gate hard-fails below 100%.
#### Scenario: Coverage gate enforces 100%
- **WHEN** `bunx turbo test:coverage --filter=@suaveplan/testing-e2e` runs
- **THEN** statements, branches, functions, and lines coverage all equal 100%