Files
Imhotep/packages/imhotep-playwright/src/storybook-adapter.ts
T

69 lines
2.3 KiB
TypeScript
Raw Normal View History

/**
* 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 = ''
}
})
},
}
}