diff --git a/playwright.config.ts b/playwright.config.ts index 2785c33..221c1af 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -2,6 +2,8 @@ import { defineConfig, devices } from "@playwright/test"; const port = Number(process.env.PLAYWRIGHT_PORT || 4173); const baseURL = `http://127.0.0.1:${port}`; +const storybookPort = Number(process.env.PLAYWRIGHT_STORYBOOK_PORT || 6007); +const storybookURL = `http://127.0.0.1:${storybookPort}`; const databaseUrl = process.env.PLAYWRIGHT_DATABASE_URL || `file:./data/playwright-${process.pid}-${Date.now()}.sqlite`; @@ -17,12 +19,20 @@ export default defineConfig({ trace: "retain-on-failure", screenshot: "only-on-failure", }, - webServer: { - command: `DISABLE_LIVE_DATASOURCES=1 bun run build && DISABLE_LIVE_DATASOURCES=1 DATABASE_URL=${databaseUrl} HOST=127.0.0.1 PORT=${port} bun build/index.js`, - url: baseURL, - reuseExistingServer: false, - timeout: 120_000, - }, + webServer: [ + { + command: `DISABLE_LIVE_DATASOURCES=1 bun run build && DISABLE_LIVE_DATASOURCES=1 DATABASE_URL=${databaseUrl} HOST=127.0.0.1 PORT=${port} bun build/index.js`, + url: baseURL, + reuseExistingServer: false, + timeout: 120_000, + }, + { + command: `[ -f storybook-static/iframe.html ] || bun run build-storybook; STORYBOOK_STATIC_PORT=${storybookPort} bun tests/e2e/storybook-server.ts`, + url: storybookURL, + reuseExistingServer: false, + timeout: 120_000, + }, + ], projects: [ { name: "chromium-desktop", diff --git a/src/lib/ui/stories/ThemeToggle.stories.tsx b/src/lib/ui/stories/ThemeToggle.stories.tsx index ffcfeef..33e494a 100644 --- a/src/lib/ui/stories/ThemeToggle.stories.tsx +++ b/src/lib/ui/stories/ThemeToggle.stories.tsx @@ -20,9 +20,7 @@ export const Light: Story = { args: { theme: "light", }, - parameters: { - globals: { - theme: "light", - }, + globals: { + theme: "light", }, }; diff --git a/tests/e2e/storybook-server.ts b/tests/e2e/storybook-server.ts new file mode 100644 index 0000000..e6e3f63 --- /dev/null +++ b/tests/e2e/storybook-server.ts @@ -0,0 +1,33 @@ +import { existsSync } from "node:fs"; +import { resolve, sep } from "node:path"; + +const root = resolve(process.cwd(), "storybook-static"); +const port = Number(process.env.STORYBOOK_STATIC_PORT || 6007); + +if (!existsSync(resolve(root, "iframe.html"))) { + throw new Error("storybook-static is missing. Run bun run build-storybook first."); +} + +Bun.serve({ + hostname: "127.0.0.1", + port, + async fetch(request) { + const url = new URL(request.url); + const pathname = decodeURIComponent(url.pathname); + const relativePath = pathname === "/" ? "/index.html" : pathname; + const filePath = resolve(root, `.${relativePath}`); + + if (!filePath.startsWith(`${root}${sep}`)) { + return new Response("Forbidden", { status: 403 }); + } + + const file = Bun.file(filePath); + if (!(await file.exists())) { + return new Response("Not found", { status: 404 }); + } + + return new Response(file); + }, +}); + +console.log(`Storybook static listening on http://127.0.0.1:${port}`); diff --git a/tests/e2e/storybook.spec.ts b/tests/e2e/storybook.spec.ts new file mode 100644 index 0000000..fda6730 --- /dev/null +++ b/tests/e2e/storybook.spec.ts @@ -0,0 +1,32 @@ +import { expect, test } from "@playwright/test"; + +const storybookPort = Number(process.env.PLAYWRIGHT_STORYBOOK_PORT || 6007); +const storybookURL = `http://127.0.0.1:${storybookPort}`; + +test.describe("Storybook theme QA", () => { + test("renders the ThemeToggle light story on the light canvas", async ({ + page, + }, testInfo) => { + test.skip(testInfo.project.name !== "chromium-desktop"); + + const consoleMessages: string[] = []; + page.on("console", (message) => { + if (["error", "warning"].includes(message.type())) { + consoleMessages.push(message.text()); + } + }); + + const url = `${storybookURL}/iframe.html?id=ui-themetoggle--light&viewMode=story`; + await page.goto(url, { waitUntil: "networkidle" }); + + await expect(page.locator("html")).toHaveAttribute("data-ui-theme", "light"); + await expect( + page.getByRole("button", { name: "Switch to dark theme" }), + ).toBeVisible(); + expect( + consoleMessages.filter((message) => + message.includes("Global args/argTypes can only be set globally"), + ), + ).toEqual([]); + }); +});