import { test, expect } from '@playwright/test'; // Visual + structural coverage for the new briefwechsel row layout. // // Seeds a bilateral correspondence pair via the API (beforeAll) so the page // reaches the row state. The structural test asserts that a // ConversationThumbnail tile AND the DistributionBar render — regressions // that silently drop to the hero or break the {#each} wiring fail here. // // Snapshot assertions are gated on the VISUAL env flag because they need // pre-captured baselines (see `playwright test --update-snapshots` to // regenerate after intentional UI changes). CI can opt in via VISUAL=1. const VISUAL = process.env.VISUAL === '1'; let senderId: string; let receiverId: string; test.describe('Briefwechsel — thumbnail-row layout', () => { test.beforeAll(async ({ request }) => { const timestamp = Date.now(); const senderRes = await request.post('/api/persons', { data: { firstName: 'Visual', lastName: `Sender-${timestamp}` } }); if (!senderRes.ok()) throw new Error(`Create sender failed: ${senderRes.status()}`); senderId = (await senderRes.json()).id; const receiverRes = await request.post('/api/persons', { data: { firstName: 'Visual', lastName: `Receiver-${timestamp}` } }); if (!receiverRes.ok()) throw new Error(`Create receiver failed: ${receiverRes.status()}`); receiverId = (await receiverRes.json()).id; const docRes = await request.post('/api/documents', { multipart: { title: 'Visual Test Brief', documentDate: '1950-06-15', senderId, receiverIds: receiverId } }); if (!docRes.ok()) throw new Error(`Create document failed: ${docRes.status()}`); }); async function openBilateral(page: import('@playwright/test').Page) { await page.goto( `/briefwechsel?senderId=${encodeURIComponent(senderId)}&receiverId=${encodeURIComponent(receiverId)}` ); await page.waitForSelector('[data-hydrated]'); } test('renders a ConversationThumbnail tile and the DistributionBar', async ({ page }) => { await openBilateral(page); // Tile appears for the seeded document await expect(page.locator('[data-testid="conv-thumb-tile"]').first()).toBeVisible(); // DistributionBar is present (role=img with a descriptive aria-label) const bar = page.locator('[role="img"]'); await expect(bar).toBeVisible(); const label = (await bar.getAttribute('aria-label')) ?? ''; expect(label.length).toBeGreaterThan(0); }); // Visual regression — one snapshot per (viewport × theme). Tolerance stays // generous (maxDiffPixels: 100) so antialiasing jitter doesn't flip them on // unrelated runs; genuine layout changes are still caught because the // thumbnail tile and distribution bar dominate the frame. test.describe('snapshots', () => { test.skip(!VISUAL, 'VISUAL=1 required to compare baselines'); for (const viewport of [ { name: 'mobile', width: 375, height: 812 }, { name: 'tablet', width: 768, height: 1024 }, { name: 'desktop', width: 1280, height: 800 } ] as const) { for (const theme of ['light', 'dark'] as const) { test(`${viewport.name} / ${theme}`, async ({ page }) => { await page.setViewportSize({ width: viewport.width, height: viewport.height }); await page.emulateMedia({ colorScheme: theme }); await openBilateral(page); await expect(page).toHaveScreenshot(`briefwechsel-${viewport.name}-${theme}.png`, { maxDiffPixels: 100, fullPage: true }); }); } } }); });