diff --git a/frontend/e2e/briefwechsel-a11y.spec.ts b/frontend/e2e/briefwechsel-a11y.spec.ts index 2a001afb..74d659b9 100644 --- a/frontend/e2e/briefwechsel-a11y.spec.ts +++ b/frontend/e2e/briefwechsel-a11y.spec.ts @@ -1,8 +1,13 @@ import AxeBuilder from '@axe-core/playwright'; import { test, expect } from '@playwright/test'; +import { + seedBilateralPair, + cleanupBilateralPair, + type BilateralPair +} from './fixtures/bilateral-correspondence'; -// Accessibility coverage specifically for the briefwechsel thumbnail-row -// layout. Seeds two persons + a bilateral document via the API so the page +// Accessibility coverage for the briefwechsel thumbnail-row layout. Seeds +// two persons + a bilateral document via the shared fixture so the page // reaches the results state (not the hero), then runs axe-core // (wcag2a + wcag2aa) across three viewports and two color schemes. @@ -14,33 +19,15 @@ const VIEWPORTS = [ const THEMES = ['light', 'dark'] as const; -let senderId: string; -let receiverId: string; +let pair: BilateralPair; test.describe('Accessibility — /briefwechsel row layout', () => { test.beforeAll(async ({ request }) => { - const timestamp = Date.now(); - const senderRes = await request.post('/api/persons', { - data: { firstName: 'A11y', lastName: `Sender-${timestamp}` } - }); - if (!senderRes.ok()) throw new Error(`Create sender failed: ${senderRes.status()}`); - senderId = (await senderRes.json()).id; + pair = await seedBilateralPair(request, 'A11y'); + }); - const receiverRes = await request.post('/api/persons', { - data: { firstName: 'A11y', 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: 'A11y Test Brief', - documentDate: '1950-06-15', - senderId, - receiverIds: receiverId - } - }); - if (!docRes.ok()) throw new Error(`Create document failed: ${docRes.status()}`); + test.afterAll(async ({ request }) => { + await cleanupBilateralPair(request, pair); }); for (const vp of VIEWPORTS) { @@ -49,7 +36,7 @@ test.describe('Accessibility — /briefwechsel row layout', () => { await page.setViewportSize({ width: vp.width, height: vp.height }); await page.emulateMedia({ colorScheme: theme }); await page.goto( - `/briefwechsel?senderId=${encodeURIComponent(senderId)}&receiverId=${encodeURIComponent(receiverId)}` + `/briefwechsel?senderId=${encodeURIComponent(pair.senderId)}&receiverId=${encodeURIComponent(pair.receiverId)}` ); await page.waitForSelector('[data-hydrated]'); diff --git a/frontend/e2e/fixtures/bilateral-correspondence.ts b/frontend/e2e/fixtures/bilateral-correspondence.ts new file mode 100644 index 00000000..ee32ef96 --- /dev/null +++ b/frontend/e2e/fixtures/bilateral-correspondence.ts @@ -0,0 +1,62 @@ +import type { APIRequestContext } from '@playwright/test'; + +/** + * Test fixture for the briefwechsel row layout. + * + * Creates two persons and one document with sender/receiver between them so + * that `/briefwechsel?senderId=X&receiverId=Y` navigates straight to the row + * state (not the hero). Each seed uses a `Date.now()`-suffixed last name so + * parallel runs and reruns never collide. + * + * The backend does not expose a person-delete endpoint, so only the document + * is cleaned up in {@link cleanupBilateralPair}. The two timestamped persons + * remain in the DB — acceptable for the test environment, and the unique + * suffix means they cannot conflict with later runs. + */ + +export interface BilateralPair { + senderId: string; + receiverId: string; + documentId: string; +} + +export async function seedBilateralPair( + request: APIRequestContext, + prefix: string +): Promise { + const timestamp = Date.now(); + + const senderRes = await request.post('/api/persons', { + data: { firstName: prefix, lastName: `Sender-${timestamp}` } + }); + if (!senderRes.ok()) throw new Error(`Create sender failed: ${senderRes.status()}`); + const senderId = (await senderRes.json()).id as string; + + const receiverRes = await request.post('/api/persons', { + data: { firstName: prefix, lastName: `Receiver-${timestamp}` } + }); + if (!receiverRes.ok()) throw new Error(`Create receiver failed: ${receiverRes.status()}`); + const receiverId = (await receiverRes.json()).id as string; + + const docRes = await request.post('/api/documents', { + multipart: { + title: `${prefix} Brief`, + documentDate: '1950-06-15', + senderId, + receiverIds: receiverId + } + }); + if (!docRes.ok()) throw new Error(`Create document failed: ${docRes.status()}`); + const documentId = (await docRes.json()).id as string; + + return { senderId, receiverId, documentId }; +} + +export async function cleanupBilateralPair( + request: APIRequestContext, + pair: BilateralPair +): Promise { + // Only the document is purged — the backend has no person-delete endpoint + // and the timestamped last names make orphaned person rows safe to leave. + await request.delete(`/api/documents/${pair.documentId}`); +}