205 lines
6.3 KiB
Markdown
205 lines
6.3 KiB
Markdown
# OpenSpec Change: enterprise-browser-storybook-test-utils
|
|
|
|
- **Change ID**: enterprise-browser-storybook-test-utils
|
|
- **Package**: `@suaveplan/storybook-test-utils`
|
|
- **Category**: browser
|
|
- **Target path**: `browser/dev/storybook-test-utils`
|
|
- **Status**: IN PROGRESS
|
|
- **Date**: 2026-04-24
|
|
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/storybook-test-utils.md`
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
Deliver `@suaveplan/storybook-test-utils` on the genesis branch at enterprise quality. The package provides Playwright helpers for driving Storybook preview UIs across every Suaveplan React package. Genesis: splits the single `storybook-helpers.ts` into six focused modules, ships a `STORYBOOK_PORTS` registry, adds typed story-index discovery, console/network/accessibility assertion helpers, a reusable Playwright `test` fixture, moves `@playwright/test` to `peerDependencies`, writes the currently-zero test coverage (≥95%), and authors all missing `.md` docs.
|
|
|
|
---
|
|
|
|
## Current-state audit (genesis)
|
|
|
|
Fresh build — does not exist on genesis yet.
|
|
|
|
---
|
|
|
|
## Available workspace deps on genesis
|
|
|
|
| Package | Role |
|
|
|---|---|
|
|
| `@suaveplan/logger` (`workspace:^`) | Structured logging for helper diagnostics |
|
|
| `tslib` (`catalog:build`) | Runtime helpers |
|
|
|
|
`peerDependencies`: `@playwright/test` (used in public API types — must not be a dev-only dep).
|
|
|
|
Dev: `vitest`, `@vitest/coverage-istanbul`, `@playwright/test`, `@suaveplan/testing`.
|
|
|
|
No React runtime dep — this package is a Playwright helper library.
|
|
|
|
---
|
|
|
|
## Preserved public API surface
|
|
|
|
```ts
|
|
// Navigation
|
|
function navigateToStory(page: Page, meta: StoryMetadata): Promise<void>
|
|
function navigateToStoryWithUI(page: Page, meta: StoryMetadata): Promise<void>
|
|
function switchToStory(page: Page, meta: StoryMetadata): Promise<void>
|
|
function reloadStory(page: Page): Promise<void>
|
|
|
|
// Waiting
|
|
function waitForStoryLoaded(page: Page, options?: WaitOptions): Promise<void>
|
|
function waitForStorybookIframe(page: Page, options?: WaitOptions): Promise<FrameLocator>
|
|
function waitForStoryInteractive(page: Page, options?: WaitOptions): Promise<void>
|
|
|
|
// Iframe access
|
|
function getStorybookFrame(page: Page): FrameLocator
|
|
function getStoryCanvas(frame: FrameLocator): Locator
|
|
|
|
// Args
|
|
function getStoryArgs(page: Page): Promise<Record<string, unknown>>
|
|
function updateStoryArgs<T = Record<string, unknown>>(page: Page, args: Partial<T>): Promise<void>
|
|
|
|
// Interactions
|
|
function clickWithFallback(locator: Locator): Promise<void>
|
|
function getClickableElement(frame: FrameLocator, selector: string): Locator
|
|
|
|
// Forms
|
|
function getFormFromStory(frame: FrameLocator, selector?: string): Locator
|
|
|
|
// Port registry (new)
|
|
const STORYBOOK_PORTS: Record<string, number>
|
|
function storyUrl(pkg: string, storyId: string, opts?: UrlOptions): string
|
|
|
|
// Story index (new)
|
|
function loadStoryIndex(baseUrl: string): Promise<StoryIndex>
|
|
function defineStoryId(kind: string, name: string): string
|
|
|
|
// Assertions (new)
|
|
function expectNoConsoleErrors(page: Page, opts?: { allow?: RegExp[] }): Promise<void>
|
|
function expectNoFailedRequests(page: Page): Promise<void>
|
|
function expectAllFontsLoaded(page: Page): Promise<void>
|
|
function auditAccessibility(page: Page, opts?: AxeOptions): Promise<AxeResult>
|
|
|
|
// Fixtures (new)
|
|
const test: typeof baseTest // extended with storybook fixture
|
|
```
|
|
|
|
Subpath exports: `"."`, `"./navigation"`, `"./waiting"`, `"./iframe"`, `"./args"`, `"./interactions"`, `"./forms"`, `"./ports"`, `"./story-index"`, `"./assertions"`, `"./fixtures"`.
|
|
|
|
---
|
|
|
|
## New additions for genesis
|
|
|
|
### 1. Split helper modules
|
|
|
|
`storybook-helpers.ts` (monolith) → six focused modules under `src/`: `navigation`, `waiting`, `iframe`, `args`, `interactions`, `forms`, each with its own `.md`.
|
|
|
|
### 2. `STORYBOOK_PORTS` registry
|
|
|
|
`src/ports/registry.ts` — canonical `STORYBOOK_PORTS` map (package name → port number); `storyUrl(pkg, storyId)` helper replaces hard-coded `localhost:6001` across all consumer E2E suites.
|
|
|
|
### 3. Typed story-index discovery
|
|
|
|
`src/story-index/index.ts` — `loadStoryIndex(baseUrl)` fetches and parses Storybook `index.json`; `defineStoryId(kind, name)` generates a typed story ID.
|
|
|
|
### 4. Console/network/a11y assertion helpers
|
|
|
|
`src/assertions/console.ts` — `expectNoConsoleErrors(page, opts)`.
|
|
`src/assertions/network.ts` — `expectNoFailedRequests(page)`, `expectAllFontsLoaded(page)`.
|
|
`src/assertions/a11y.ts` — `auditAccessibility(page, opts)` running axe-core in the preview iframe; matches the CLAUDE.md web-app verification policy.
|
|
|
|
### 5. Reusable Playwright fixture
|
|
|
|
`src/fixtures/playwright.ts` — `test.extend` with a `storybook` fixture pre-configured for the port registry.
|
|
|
|
### 6. Tests (currently zero)
|
|
|
|
Write `src/**/*.test.ts` driving a local HTTP server with stub Storybook HTML; coverage ≥ 95%.
|
|
|
|
### 7. `@playwright/test` moved to `peerDependencies`
|
|
|
|
Removes the type-leak where public API signatures returned `Page`/`Locator`/`FrameLocator` but the package was declared dev-only.
|
|
|
|
---
|
|
|
|
## File layout
|
|
|
|
```text
|
|
browser/dev/storybook-test-utils/
|
|
package.json
|
|
tsconfig.json
|
|
vitest.config.ts
|
|
src/
|
|
index.ts
|
|
navigation/
|
|
navigation.ts
|
|
navigation.test.ts
|
|
navigation.md
|
|
index.ts
|
|
waiting/
|
|
waiting.ts
|
|
waiting.test.ts
|
|
waiting.md
|
|
index.ts
|
|
iframe/
|
|
iframe.ts
|
|
iframe.test.ts
|
|
iframe.md
|
|
index.ts
|
|
args/
|
|
args.ts
|
|
args.test.ts
|
|
args.md
|
|
index.ts
|
|
interactions/
|
|
interactions.ts
|
|
interactions.test.ts
|
|
interactions.md
|
|
index.ts
|
|
forms/
|
|
forms.ts
|
|
forms.test.ts
|
|
forms.md
|
|
index.ts
|
|
ports/
|
|
registry.ts
|
|
registry.test.ts
|
|
registry.md
|
|
index.ts
|
|
story-index/
|
|
index-loader.ts
|
|
index-loader.test.ts
|
|
index-loader.md
|
|
index.ts
|
|
assertions/
|
|
console.ts
|
|
console.test.ts
|
|
console.md
|
|
network.ts
|
|
network.test.ts
|
|
network.md
|
|
a11y.ts
|
|
a11y.test.ts
|
|
a11y.md
|
|
index.ts
|
|
fixtures/
|
|
playwright.ts
|
|
playwright.md
|
|
index.ts
|
|
types/
|
|
types.ts
|
|
types.md
|
|
tests/
|
|
integration.test.ts
|
|
```
|
|
|
|
---
|
|
|
|
## Impact
|
|
|
|
**Downstream**: every `@suaveplan/*` package with Storybook stories and a `e2e/*.spec.ts` file.
|
|
|
|
**Breaking changes**: `@playwright/test` becomes `peerDependency`; consumers must have it in their own `devDependencies` (they typically already do).
|
|
|
|
**Prerequisites**: None — no runtime workspace deps beyond `tslib`.
|