test(briefwechsel): visual spec seeds bilateral pair and asserts row structure
Extends the seeding pattern from the a11y spec: beforeAll creates two persons + one document so the page renders the row layout. The structural test now asserts the ConversationThumbnail tile AND the DistributionBar are present — a regression that drops to the hero or breaks the row wiring fails here instead of silently passing a hero-state check. Snapshot block stays gated on VISUAL=1 (baselines captured during review against a seeded backend) so the structural coverage ships immediately and the pixel-diff coverage ships once baselines land. Refs #305 Fixes @saraholt blocker 2 from PR review Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,23 +2,69 @@ import { test, expect } from '@playwright/test';
|
|||||||
|
|
||||||
// Visual + structural coverage for the new briefwechsel row layout.
|
// Visual + structural coverage for the new briefwechsel row layout.
|
||||||
//
|
//
|
||||||
// Snapshot assertions are gated on the VISUAL env flag because they require
|
// Seeds a bilateral correspondence pair via the API (beforeAll) so the page
|
||||||
// pre-captured baselines (see `playwright test --update-snapshots` to regenerate
|
// reaches the row state. The structural test asserts that a
|
||||||
// after intentional UI changes). CI only runs the structural assertions until
|
// ConversationThumbnail tile AND the DistributionBar render — regressions
|
||||||
// the baseline set is populated and committed.
|
// 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';
|
const VISUAL = process.env.VISUAL === '1';
|
||||||
|
|
||||||
|
let senderId: string;
|
||||||
|
let receiverId: string;
|
||||||
|
|
||||||
test.describe('Briefwechsel — thumbnail-row layout', () => {
|
test.describe('Briefwechsel — thumbnail-row layout', () => {
|
||||||
test('structural row elements render for a bilateral pair', async ({ page }) => {
|
test.beforeAll(async ({ request }) => {
|
||||||
await page.goto('/briefwechsel');
|
const timestamp = Date.now();
|
||||||
// Hero is visible until a person is selected.
|
const senderRes = await request.post('/api/persons', {
|
||||||
await expect(page.getByTestId('conv-hero')).toBeVisible();
|
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()}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Visual regression — one snapshot per (viewport × theme). Snapshot tolerance
|
async function openBilateral(page: import('@playwright/test').Page) {
|
||||||
// stays generous (maxDiffPixels: 100) so that antialiasing jitter on text
|
await page.goto(
|
||||||
// doesn't flip them on unrelated runs; genuine layout changes are still
|
`/briefwechsel?senderId=${encodeURIComponent(senderId)}&receiverId=${encodeURIComponent(receiverId)}`
|
||||||
// caught because of the thumbnail tile and distribution bar.
|
);
|
||||||
|
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.describe('snapshots', () => {
|
||||||
test.skip(!VISUAL, 'VISUAL=1 required to compare baselines');
|
test.skip(!VISUAL, 'VISUAL=1 required to compare baselines');
|
||||||
|
|
||||||
@@ -31,7 +77,7 @@ test.describe('Briefwechsel — thumbnail-row layout', () => {
|
|||||||
test(`${viewport.name} / ${theme}`, async ({ page }) => {
|
test(`${viewport.name} / ${theme}`, async ({ page }) => {
|
||||||
await page.setViewportSize({ width: viewport.width, height: viewport.height });
|
await page.setViewportSize({ width: viewport.width, height: viewport.height });
|
||||||
await page.emulateMedia({ colorScheme: theme });
|
await page.emulateMedia({ colorScheme: theme });
|
||||||
await page.goto('/briefwechsel');
|
await openBilateral(page);
|
||||||
await expect(page).toHaveScreenshot(`briefwechsel-${viewport.name}-${theme}.png`, {
|
await expect(page).toHaveScreenshot(`briefwechsel-${viewport.name}-${theme}.png`, {
|
||||||
maxDiffPixels: 100,
|
maxDiffPixels: 100,
|
||||||
fullPage: true
|
fullPage: true
|
||||||
|
|||||||
Reference in New Issue
Block a user