import { test, expect } from '@playwright/test' import { imhotep } from 'imhotep-playwright' import { loadFixtureInPage, waitForFixtureReady } from './harness.js' async function fixtureUrl(category: string): Promise { const { resolveFixturePage } = await import('./harness.js') return 'file://' + resolveFixturePage(category) } test.describe('E2E: Responsive', () => { test('across multiple viewport widths captures layout shifts', async ({ page }) => { const ui = await imhotep(page) await page.goto(await fixtureUrl('responsive')) const viewports = [ { width: 375, height: 667, name: 'mobile' }, { width: 768, height: 1024, name: 'tablet' }, { width: 1280, height: 720, name: 'desktop' }, ] const results: Array<{ viewport: string; sidebarWidth: number; layoutDirection: string }> = [] for (const viewport of viewports) { await ui.applyEnvironment({ viewport: { width: viewport.width, height: viewport.height }, }) await page.reload() await waitForFixtureReady(page) const sidebarData = await ui.extract('[data-testid="responsive-sidebar"]') const contentData = await ui.extract('[data-testid="responsive-content"]') const sidebarRect = (sidebarData as any[])[0].rect const contentRect = (contentData as any[])[0].rect // Determine layout direction by comparing y positions const layoutDirection = sidebarRect.y === contentRect.y ? 'row' : 'column' results.push({ viewport: viewport.name, sidebarWidth: sidebarRect.width, layoutDirection, }) } // Mobile: column layout, sidebar is nearly full width (accounting for body padding) const mobileResult = results.find((r) => r.viewport === 'mobile') expect(mobileResult!.layoutDirection).toBe('column') expect(mobileResult!.sidebarWidth).toBeGreaterThanOrEqual(200) expect(mobileResult!.sidebarWidth).toBeLessThanOrEqual(375) // Tablet+: row layout, fixed width sidebar const tabletResult = results.find((r) => r.viewport === 'tablet') expect(tabletResult!.layoutDirection).toBe('row') expect(tabletResult!.sidebarWidth).toBe(200) // Desktop: wider gap, wider sidebar const desktopResult = results.find((r) => r.viewport === 'desktop') expect(desktopResult!.layoutDirection).toBe('row') expect(desktopResult!.sidebarWidth).toBe(250) }) test('when guard for breakpoint behavior evaluates correctly', async ({ page }) => { const ui = await imhotep(page) await page.goto(await fixtureUrl('responsive')) // At mobile width, sidebar should be full width (column layout) await ui.applyEnvironment({ viewport: { width: 375, height: 667 }, }) await page.reload() await waitForFixtureReady(page) const sidebarData = await ui.extract('[data-testid="responsive-sidebar"]') const sidebarRect = (sidebarData as any[])[0].rect expect(sidebarRect.width).toBeGreaterThanOrEqual(200) // At desktop width, sidebar should be 250px await ui.applyEnvironment({ viewport: { width: 1280, height: 720 }, }) await page.reload() await waitForFixtureReady(page) const desktopSidebarData = await ui.extract('[data-testid="responsive-sidebar"]') const desktopSidebarRect = (desktopSidebarData as any[])[0].rect expect(desktopSidebarRect.width).toBe(250) }) test('invariant assertions hold across all widths', async ({ page }) => { const ui = await imhotep(page) await page.goto(await fixtureUrl('responsive')) const viewports = [ { width: 320, height: 568 }, { width: 768, height: 1024 }, { width: 1440, height: 900 }, ] for (const viewport of viewports) { await ui.applyEnvironment({ viewport: { width: viewport.width, height: viewport.height }, }) await page.reload() await waitForFixtureReady(page) const sidebarData = await ui.extract('[data-testid="responsive-sidebar"]') const contentData = await ui.extract('[data-testid="responsive-content"]') const sidebarRect = (sidebarData as any[])[0].rect const contentRect = (contentData as any[])[0].rect // Invariant: both elements should be visible expect(sidebarRect.width).toBeGreaterThan(0) expect(sidebarRect.height).toBeGreaterThan(0) expect(contentRect.width).toBeGreaterThan(0) expect(contentRect.height).toBeGreaterThan(0) // Invariant: content should be to the right of or below sidebar const isRightOf = contentRect.x >= sidebarRect.x + sidebarRect.width const isBelow = contentRect.y >= sidebarRect.y + sidebarRect.height expect(isRightOf || isBelow).toBe(true) } }) test('container query responds to container width not viewport', async ({ page }) => { const ui = await imhotep(page) await page.goto(await fixtureUrl('responsive')) await waitForFixtureReady(page) const containerData = await ui.extract('[data-testid="cq-container"]') const itemData = await ui.extract('[data-testid="cq-item"]') const containerRect = (containerData as any[])[0].rect const itemRect = (itemData as any[])[0].rect // Container is max-width: 600px, but let's check the item responds to it expect(containerRect.width).toBeGreaterThan(0) expect(itemRect.width).toBeGreaterThan(0) // Item should be within container expect(itemRect.x).toBeGreaterThanOrEqual(containerRect.x) expect(itemRect.x + itemRect.width).toBeLessThanOrEqual(containerRect.x + containerRect.width) }) })