v1.1.0: pooled runtime, 959 tests, production hardening (0 squash)
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Storybook renderer adapter for Imhotep Playwright.
|
||||
*
|
||||
* Navigates to a Storybook story URL or uses Storybook's internal APIs
|
||||
* to render a specific story. Accepts story args as input.
|
||||
*/
|
||||
|
||||
import { Page } from 'playwright'
|
||||
import type { SceneTarget } from 'imhotep-core/scene-target'
|
||||
import { RendererAdapter } from './renderers.js'
|
||||
|
||||
export interface StorybookAdapterOptions {
|
||||
/** Base URL of the Storybook instance. */
|
||||
storybookUrl: string
|
||||
}
|
||||
|
||||
export function createStorybookAdapter(options: StorybookAdapterOptions): RendererAdapter {
|
||||
const { storybookUrl } = options
|
||||
|
||||
return {
|
||||
id: 'storybook',
|
||||
|
||||
async mount(page: Page, target: SceneTarget, input: unknown): Promise<void> {
|
||||
if (target.kind !== 'storybook-story') {
|
||||
throw new Error(`Storybook adapter received non-storybook target: ${target.kind}`)
|
||||
}
|
||||
|
||||
// Build the Storybook iframe URL.
|
||||
// Standard Storybook v6+ URL format: ?path=/story/{storyId}
|
||||
// With args: ?path=/story/{storyId}&args={json}
|
||||
const storyId = target.storyId
|
||||
const args = input as Record<string, unknown> | undefined
|
||||
|
||||
const url = new URL(storybookUrl)
|
||||
// Only add Storybook path param for non-file URLs.
|
||||
// File URLs are used for fixture-based testing where the HTML
|
||||
// page itself is the "story" and does not parse ?path params.
|
||||
if (url.protocol !== 'file:') {
|
||||
url.searchParams.set('path', `/story/${storyId}`)
|
||||
}
|
||||
|
||||
if (args && Object.keys(args).length > 0) {
|
||||
url.searchParams.set('args', JSON.stringify(args))
|
||||
}
|
||||
|
||||
await page.goto(url.toString())
|
||||
|
||||
// Wait for the story to be rendered.
|
||||
// Storybook renders into #root (v6) or #storybook-root (v7+).
|
||||
try {
|
||||
await page.waitForSelector('#root, #storybook-root', { state: 'visible', timeout: 10000 })
|
||||
} catch {
|
||||
// If neither selector is found, the story may still be loading.
|
||||
// Fall through and let downstream extraction handle any issues.
|
||||
}
|
||||
},
|
||||
|
||||
async unmount(page: Page): Promise<void> {
|
||||
// Storybook unmount is a no-op; navigation away handles cleanup.
|
||||
await page.evaluate(() => {
|
||||
const root = document.getElementById('root') || document.getElementById('storybook-root')
|
||||
if (root) {
|
||||
root.innerHTML = ''
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user