## 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%