import { test, expect } from '@playwright/test' import { imhotepStory } from 'imhotep-playwright' import { generatedDomain } from 'imhotep-core/property-contracts' import fc from 'fast-check' import { resolve } from 'node:path' import { fileURLToPath } from 'node:url' const __dirname = fileURLToPath(new URL('.', import.meta.url)) function fixtureUrl(name: string): string { return 'file://' + resolve(__dirname, 'pages', `${name}.html`) } test.describe('E2E: Storybook story property run', () => { test('imhotepStory forAllProps navigates to story URL', async ({ page }) => { const handle = imhotepStory('button--primary', { storybookUrl: fixtureUrl('storybook-like'), fc: fc as any, }) const domain = generatedDomain( fc.record({ size: fc.constantFrom('sm', 'md', 'lg'), disabled: fc.boolean(), label: fc.constant('Story Button'), }), { seed: 42, numRuns: 5 } ) let runCount = 0 const result = await handle.forAllProps( page, domain, async (scene, _ctx) => { runCount++ // Verify the page loaded by extracting an element const data = await scene.extract('[data-testid="story-button"]') expect(Array.isArray(data)).toBe(true) expect((data as any[]).length).toBe(1) } ) expect(result.passed).toBe(true) expect(runCount).toBe(5) }) test('imhotepStory forAllProps applies generated args', async ({ page }) => { const handle = imhotepStory('button--primary', { storybookUrl: fixtureUrl('storybook-like'), fc: fc as any, }) const domain = generatedDomain( fc.record({ size: fc.constantFrom('sm', 'md', 'lg'), disabled: fc.boolean(), label: fc.constant('Story Button'), }), { seed: 42, numRuns: 10 } ) const sizes: string[] = [] const result = await handle.forAllProps( page, domain, async (_scene, ctx) => { const input = ctx.input as { size: string; disabled: boolean; label: string } sizes.push(input.size) } ) expect(result.passed).toBe(true) // With 10 runs and 3 size values, we should see variety expect(sizes.length).toBe(10) expect(new Set(sizes).size).toBeGreaterThanOrEqual(1) }) test('button width invariant across all generated arg combinations', async ({ page }) => { const handle = imhotepStory('button--primary', { storybookUrl: fixtureUrl('storybook-like'), fc: fc as any, }) const domain = generatedDomain( fc.record({ size: fc.constantFrom('sm', 'md', 'lg'), disabled: fc.boolean(), label: fc.string({ minLength: 1, maxLength: 15 }), }), { seed: 42, numRuns: 20 } ) let runCount = 0 const result = await handle.forAllProps( page, domain, async (scene, _ctx) => { runCount++ const data = await scene.extract('[data-testid="story-button"]') expect(Array.isArray(data)).toBe(true) expect((data as any[]).length).toBe(1) const box = (data as any[])[0].rect as { width: number; height: number } // Minimum width invariant: all buttons must be at least 60px wide // (sm is 60px min-width per the fixture CSS) expect(box.width).toBeGreaterThanOrEqual(60) // Minimum height invariant: all buttons must be at least 32px tall expect(box.height).toBeGreaterThanOrEqual(32) } ) expect(result.passed).toBe(true) expect(result.mode).toBe('sampled') expect(runCount).toBe(20) }) test('size prop affects actual rendered dimensions', async ({ page }) => { const handle = imhotepStory('button--primary', { storybookUrl: fixtureUrl('storybook-like'), fc: fc as any, }) const domain = generatedDomain( fc.record({ size: fc.constantFrom('sm', 'md', 'lg'), disabled: fc.boolean(), label: fc.constant('Btn'), }), { seed: 123, numRuns: 15 } ) const observed: Array<{ size: string; width: number; height: number }> = [] const result = await handle.forAllProps( page, domain, async (scene, ctx) => { const input = ctx.input as { size: string } const data = await scene.extract('[data-testid="story-button"]') const box = (data as any[])[0].rect as { width: number; height: number } observed.push({ size: input.size, width: box.width, height: box.height }) } ) expect(result.passed).toBe(true) expect(observed.length).toBe(15) // Verify that different sizes were observed const sizes = new Set(observed.map(o => o.size)) expect(sizes.size).toBeGreaterThanOrEqual(1) // Verify that lg buttons are wider than sm buttons on average const avgWidth = (size: string) => { const widths = observed.filter(o => o.size === size).map(o => o.width) return widths.reduce((a, b) => a + b, 0) / (widths.length || 1) } expect(avgWidth('lg')).toBeGreaterThanOrEqual(avgWidth('sm')) }) test('reproducible by seed across story runs', async ({ page }) => { const handle = imhotepStory('button--primary', { storybookUrl: fixtureUrl('storybook-like'), fc: fc as any, }) const domain = generatedDomain( fc.record({ size: fc.constantFrom('sm', 'md', 'lg'), disabled: fc.boolean(), label: fc.string({ minLength: 1, maxLength: 5 }), }), { seed: 777, numRuns: 10 } ) const sizes1: string[] = [] await handle.forAllProps(page, domain, async (_scene, ctx) => { sizes1.push((ctx.input as { size: string }).size) }) const sizes2: string[] = [] await handle.forAllProps(page, domain, async (_scene, ctx) => { sizes2.push((ctx.input as { size: string }).size) }) expect(sizes1).toEqual(sizes2) }) })