veza/apps/web/e2e/tests/storybook/storybook-all.spec.ts
senke 1efafe0cc5 test(storybook): Playwright suite for full Storybook + Spotify/Discord polish
- Add playwright.config.storybook.ts: runs against storybook-static on :6007
- Add e2e/tests/storybook/storybook-all.spec.ts: one test per story (load iframe, no errors)
- Add scripts/serve-storybook-static.cjs: serves build or stub (empty index) when no build
- npm run test:storybook:playwright (after build-storybook) for full coverage
- Storybook decorator: use bg-background / design tokens for dark (#121212)
- Preview: default dark background #121212
- Button: secondary/ghost/glass aligned to Spotify/Discord (white/5, white/10 hover)
- KodoEmptyState: softer orbs, compact copy, primary CTA without heavy glow

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 20:30:49 +01:00

80 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { test, expect } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';
const INDEX_PATH = path.join(process.cwd(), 'storybook-static', 'index.json');
const IFRAME_URL = (id: string) => `/iframe.html?id=${encodeURIComponent(id)}&viewMode=story`;
const NAV_TIMEOUT_MS = 20000;
const POST_LOAD_MS = 200;
/** Story IDs from built Storybook index (available at load time). */
function getStoryIds(): string[] {
if (!fs.existsSync(INDEX_PATH)) return [];
try {
const index = JSON.parse(fs.readFileSync(INDEX_PATH, 'utf8'));
const entries = index.entries ?? {};
return Object.values(entries).map((e: { id?: string }) => e.id).filter(Boolean);
} catch {
return [];
}
}
const storyIds = getStoryIds();
test.describe('Storybook all stories', () => {
if (storyIds.length === 0) {
test('run build-storybook first', async () => {
test.skip(true, 'Run npm run build-storybook first. Then start a server on port 6007 (e.g. npx serve storybook-static -l 6007) or use the Playwright webServer.');
});
return;
}
for (const storyId of storyIds) {
test(storyId, async ({ page }) => {
const consoleErrors: string[] = [];
const pageErrors: string[] = [];
page.on('console', (msg) => {
const type = msg.type();
if (type === 'error') {
const text = msg.text();
if (!isIgnoredConsoleError(text)) consoleErrors.push(text);
}
});
page.on('pageerror', (err) => {
pageErrors.push(err.message);
});
const response = await page.goto(IFRAME_URL(storyId), {
waitUntil: 'domcontentloaded',
timeout: NAV_TIMEOUT_MS,
});
expect(response?.status()).toBe(200);
await page.waitForTimeout(POST_LOAD_MS);
const errors = [...pageErrors, ...consoleErrors];
expect(
errors,
errors.length ? `Story ${storyId}: ${errors.slice(0, 3).join('; ')}` : undefined
).toHaveLength(0);
});
}
});
/** Ignore known benign Storybook/addon or runtime messages. */
function isIgnoredConsoleError(text: string): boolean {
const ignored = [
'ResizeObserver',
'Warning: ReactDOM.render',
'Download the React DevTools',
'sb-manager',
'sb-addons',
'sb-common-assets',
'mockServiceWorker',
'Failed to load resource: net::ERR_ABORTED',
'ChunkLoadError',
'Loading chunk',
'hydration',
];
return ignored.some((s) => text.includes(s));
}