From c335ddd686b81faf961d9e9eb72d83239fb5b03c Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 25 Apr 2026 10:22:52 +0200 Subject: [PATCH] test(e2e): add training footer positive-case test and fix broken selectors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - createEmptyDocument now uploads a minimal PDF so the Transkribieren button is rendered (requires isPdf = true in DocumentTopBar) - add 'Transcribe coach — with blocks' describe: seeds a block via API, waits for blocks to settle in read mode, switches to edit, confirms 'Für Training vormerken' is visible - fix dark-theme axe test: ThemeToggle uses aria-label 'dark mode', not the previous /Farbmodus|theme/ regex Co-Authored-By: Claude Sonnet 4.6 --- frontend/e2e/helpers/upload-empty-document.ts | 28 +++++++++++-- frontend/e2e/transcribe-coach.spec.ts | 42 ++++++++++++++++++- 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/frontend/e2e/helpers/upload-empty-document.ts b/frontend/e2e/helpers/upload-empty-document.ts index caba3615..635f176b 100644 --- a/frontend/e2e/helpers/upload-empty-document.ts +++ b/frontend/e2e/helpers/upload-empty-document.ts @@ -1,10 +1,30 @@ import type { APIRequestContext } from '@playwright/test'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import fs from 'fs'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const PDF_FIXTURE = path.resolve(__dirname, '../fixtures/minimal.pdf'); export async function createEmptyDocument(request: APIRequestContext): Promise { - const res = await request.post('/api/documents', { + const createRes = await request.post('/api/documents', { multipart: { title: 'E2E Transcribe Coach Test' } }); - if (!res.ok()) throw new Error(`Create document failed: ${res.status()}`); - const doc = await res.json(); - return doc.id as string; + if (!createRes.ok()) throw new Error(`Create document failed: ${createRes.status()}`); + const doc = await createRes.json(); + const docId = doc.id as string; + + const uploadRes = await request.put(`/api/documents/${docId}`, { + multipart: { + title: doc.title, + file: { + name: 'minimal.pdf', + mimeType: 'application/pdf', + buffer: fs.readFileSync(PDF_FIXTURE) + } + } + }); + if (!uploadRes.ok()) throw new Error(`Upload PDF failed: ${uploadRes.status()}`); + + return docId; } diff --git a/frontend/e2e/transcribe-coach.spec.ts b/frontend/e2e/transcribe-coach.spec.ts index 7ff94f64..e6205ff5 100644 --- a/frontend/e2e/transcribe-coach.spec.ts +++ b/frontend/e2e/transcribe-coach.spec.ts @@ -2,6 +2,24 @@ import { test, expect } from '@playwright/test'; import AxeBuilder from '@axe-core/playwright'; import { createEmptyDocument } from './helpers/upload-empty-document.js'; +async function createBlock( + request: Parameters[0], + docId: string +): Promise { + const res = await request.post(`/api/documents/${docId}/transcription-blocks`, { + data: { + pageNumber: 1, + x: 0.1, + y: 0.1, + width: 0.3, + height: 0.1, + text: 'Liebe Mutter,', + label: null + } + }); + if (!res.ok()) throw new Error(`Create block failed: ${res.status()}`); +} + function buildAxe(page: Parameters[0]['page']) { return new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa']); } @@ -53,7 +71,7 @@ test.describe('Transcribe coach — empty state', () => { test('axe: panel empty state — dark theme — no WCAG 2.1 AA violations', async ({ page }) => { await page.goto(`/documents/${docId}`); // Toggle dark theme - await page.getByRole('button', { name: /Farbmodus|theme/i }).click(); + await page.getByRole('button', { name: /dark mode/i }).click(); await page.getByRole('button', { name: 'Transkribieren' }).click(); await expect(page.getByRole('heading', { level: 2, name: /Erste Transkription/ })).toBeVisible({ timeout: 5000 @@ -63,3 +81,25 @@ test.describe('Transcribe coach — empty state', () => { expect(a11y.violations, JSON.stringify(a11y.violations, null, 2)).toHaveLength(0); }); }); + +test.describe('Transcribe coach — with blocks', () => { + let docId: string; + + test.beforeAll(async ({ request }) => { + docId = await createEmptyDocument(request); + await createBlock(request, docId); + }); + + test.afterAll(async ({ request }) => { + await request.delete(`/api/documents/${docId}`); + }); + + test('training footer IS visible when at least one block exists', async ({ page }) => { + await page.goto(`/documents/${docId}`); + await page.getByRole('button', { name: 'Transkribieren' }).click(); + // Wait for blocks to finish loading — block count confirms mode settled to 'read' + await expect(page.getByText(/1 Abschnitt/)).toBeVisible({ timeout: 5000 }); + await page.locator('[data-testid="mode-edit"]').click(); + await expect(page.getByText('Für Training vormerken')).toBeVisible({ timeout: 5000 }); + }); +});