From f761dbb9b178ea41eb9f672ab5ef012a12f01d43 Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Wed, 13 May 2026 08:26:35 +0200 Subject: [PATCH] feat(tests): Storybook visual regression harness (Playwright) --- apps/storybook/tests/visual.spec.ts | 51 +++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 52 insertions(+) create mode 100644 apps/storybook/tests/visual.spec.ts diff --git a/apps/storybook/tests/visual.spec.ts b/apps/storybook/tests/visual.spec.ts new file mode 100644 index 0000000..a807c12 --- /dev/null +++ b/apps/storybook/tests/visual.spec.ts @@ -0,0 +1,51 @@ +import { test, expect } from "@playwright/test"; + +/** + * Iterates every story registered in Storybook and takes a screenshot. + * + * Storybook exposes its story manifest at /index.json (Storybook 7+). For + * each entry where `type === "story"`, we navigate to the iframe URL and + * snapshot. + * + * Today the index is empty (no components in the repo). The harness still + * runs — it just finds zero stories. The moment a story lands, the + * baseline is captured on first run and subsequent runs diff against it. + */ +type StoryEntry = { + id: string; + title: string; + name: string; + type: "story" | "docs"; +}; + +async function fetchStoryIndex(baseURL: string): Promise { + const res = await fetch(`${baseURL}/index.json`); + if (!res.ok) return []; + const json = (await res.json()) as { + entries?: Record; + }; + return Object.values(json.entries ?? {}).filter((e) => e.type === "story"); +} + +test.describe("Storybook visual regression", () => { + test("captures a screenshot for every registered story", async ({ + page, + baseURL, + }) => { + const stories = await fetchStoryIndex(baseURL!); + if (stories.length === 0) { + test.skip( + true, + "No stories registered yet — visual regression harness is inactive until the first story lands.", + ); + return; + } + for (const story of stories) { + await test.step(`${story.title} — ${story.name}`, async () => { + await page.goto(`/iframe.html?id=${story.id}&viewMode=story`); + await page.waitForLoadState("networkidle"); + await expect(page).toHaveScreenshot(`${story.id}.png`); + }); + } + }); +}); diff --git a/package.json b/package.json index 0f38e95..dcd747b 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "test": "turbo run test", "test:e2e": "turbo run test:e2e", "test:stories": "turbo run test:stories", + "test:visual": "pnpm --filter @repo/storybook exec concurrently -k -s first -n 'SB,VRT' -c 'magenta,blue' 'pnpm --filter @repo/storybook exec http-server storybook-static --port 6006 --silent' 'pnpm --filter @repo/storybook exec wait-on tcp:6006 && pnpm exec playwright test'", "typecheck": "turbo run typecheck", "conformance": "node scripts/conformance.mjs", "work": "node scripts/work/cli.mjs",