test(e2e): add transcribe-coach, richtlinien, and help-popover E2E specs; reducedMotion global default
Some checks failed
CI / Unit & Component Tests (push) Failing after 4m29s
CI / OCR Service Tests (push) Successful in 55s
CI / Backend Unit Tests (push) Failing after 3m16s
CI / Unit & Component Tests (pull_request) Failing after 3m3s
CI / OCR Service Tests (pull_request) Successful in 39s
CI / Backend Unit Tests (pull_request) Failing after 3m4s
Some checks failed
CI / Unit & Component Tests (push) Failing after 4m29s
CI / OCR Service Tests (push) Successful in 55s
CI / Backend Unit Tests (push) Failing after 3m16s
CI / Unit & Component Tests (pull_request) Failing after 3m3s
CI / OCR Service Tests (pull_request) Successful in 39s
CI / Backend Unit Tests (pull_request) Failing after 3m4s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
30
frontend/e2e/help-popover.spec.ts
Normal file
30
frontend/e2e/help-popover.spec.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { createEmptyDocument } from './helpers/upload-empty-document.js';
|
||||||
|
|
||||||
|
test.describe('Help chip — Read/Edit panel header', () => {
|
||||||
|
let docId: string;
|
||||||
|
|
||||||
|
test.beforeAll(async ({ request }) => {
|
||||||
|
docId = await createEmptyDocument(request);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('opens popover on click, closes on Esc, returns focus to chip', async ({ page }) => {
|
||||||
|
await page.goto(`/documents/${docId}`);
|
||||||
|
await page.getByRole('button', { name: 'Transkribieren' }).click();
|
||||||
|
|
||||||
|
// Find and click the (?) help chip
|
||||||
|
const helpBtn = page.locator('button[aria-expanded]');
|
||||||
|
await expect(helpBtn).toBeVisible({ timeout: 5000 });
|
||||||
|
await helpBtn.click();
|
||||||
|
|
||||||
|
// Popover should open
|
||||||
|
await expect(page.locator('[role="tooltip"]')).toBeVisible();
|
||||||
|
|
||||||
|
// Press Esc
|
||||||
|
await page.keyboard.press('Escape');
|
||||||
|
await expect(page.locator('[role="tooltip"]')).not.toBeVisible();
|
||||||
|
|
||||||
|
// Focus should have returned to the chip
|
||||||
|
await expect(helpBtn).toBeFocused();
|
||||||
|
});
|
||||||
|
});
|
||||||
10
frontend/e2e/helpers/upload-empty-document.ts
Normal file
10
frontend/e2e/helpers/upload-empty-document.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import type { APIRequestContext } from '@playwright/test';
|
||||||
|
|
||||||
|
export async function createEmptyDocument(request: APIRequestContext): Promise<string> {
|
||||||
|
const res = 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;
|
||||||
|
}
|
||||||
68
frontend/e2e/richtlinien.spec.ts
Normal file
68
frontend/e2e/richtlinien.spec.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import AxeBuilder from '@axe-core/playwright';
|
||||||
|
|
||||||
|
function buildAxe(page: Parameters<typeof AxeBuilder>[0]['page']) {
|
||||||
|
return new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa']);
|
||||||
|
}
|
||||||
|
|
||||||
|
test.describe('Richtlinien page — content', () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto('/hilfe/transkription');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders h1 title, intro, five rules, four chips, closing card', async ({ page }) => {
|
||||||
|
await expect(
|
||||||
|
page.getByRole('heading', { level: 1, name: /Transkriptions-Richtlinien/ })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText(/Damit alle Briefe einheitlich/)).toBeVisible();
|
||||||
|
await expect(page.getByText('Nicht lesbare Wörter')).toBeVisible();
|
||||||
|
await expect(page.getByText('Durchgestrichene Wörter')).toBeVisible();
|
||||||
|
await expect(page.getByText(/Das lange s/)).toBeVisible();
|
||||||
|
await expect(page.getByText('Unsichere Namen')).toBeVisible();
|
||||||
|
await expect(page.getByText(/Dialekt/)).toBeVisible();
|
||||||
|
await expect(page.getByText('Abkürzungen')).toBeVisible();
|
||||||
|
await expect(page.getByText('Datumsformate')).toBeVisible();
|
||||||
|
await expect(page.getByText(/Fehlt eine Regel/)).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Wikipedia link opens in new tab with annotation', async ({ page }) => {
|
||||||
|
const wikiLink = page.getByRole('link', { name: /Wikipedia/ });
|
||||||
|
await expect(wikiLink).toHaveAttribute('target', '_blank');
|
||||||
|
await expect(wikiLink).toHaveAttribute('rel', 'noopener noreferrer');
|
||||||
|
await expect(wikiLink).toHaveAttribute('referrerpolicy', 'no-referrer');
|
||||||
|
await expect(wikiLink).toContainText(/öffnet in neuem Tab/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('Richtlinien page — accessibility', () => {
|
||||||
|
for (const viewport of [320, 768, 1440]) {
|
||||||
|
test(`axe: light theme at ${viewport}px — no WCAG 2.1 AA violations`, async ({ page }) => {
|
||||||
|
await page.setViewportSize({ width: viewport, height: 800 });
|
||||||
|
await page.goto('/hilfe/transkription');
|
||||||
|
const a11y = await buildAxe(page).analyze();
|
||||||
|
expect(a11y.violations, JSON.stringify(a11y.violations, null, 2)).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`axe: dark theme at ${viewport}px — no WCAG 2.1 AA violations`, async ({ page }) => {
|
||||||
|
await page.setViewportSize({ width: viewport, height: 800 });
|
||||||
|
await page.goto('/hilfe/transkription');
|
||||||
|
await page.getByRole('button', { name: /Farbmodus|theme/i }).click();
|
||||||
|
const a11y = await buildAxe(page).analyze();
|
||||||
|
expect(a11y.violations, JSON.stringify(a11y.violations, null, 2)).toHaveLength(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('Richtlinien page — print media', () => {
|
||||||
|
test('print snapshot hides nav, annotation chip, and new-tab spans', async ({ page }) => {
|
||||||
|
await page.emulateMedia({ media: 'print' });
|
||||||
|
await page.goto('/hilfe/transkription');
|
||||||
|
|
||||||
|
const nav = page.locator('.app-nav');
|
||||||
|
if ((await nav.count()) > 0) {
|
||||||
|
await expect(nav).toBeHidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.screenshot({ path: 'test-results/e2e/richtlinien-print.png', fullPage: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
65
frontend/e2e/transcribe-coach.spec.ts
Normal file
65
frontend/e2e/transcribe-coach.spec.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import AxeBuilder from '@axe-core/playwright';
|
||||||
|
import { createEmptyDocument } from './helpers/upload-empty-document.js';
|
||||||
|
|
||||||
|
function buildAxe(page: Parameters<typeof AxeBuilder>[0]['page']) {
|
||||||
|
return new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa']);
|
||||||
|
}
|
||||||
|
|
||||||
|
test.describe('Transcribe coach — empty state', () => {
|
||||||
|
let docId: string;
|
||||||
|
|
||||||
|
test.beforeAll(async ({ request }) => {
|
||||||
|
docId = await createEmptyDocument(request);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shows coach card (title, preamble, three step bodies, animation region)', async ({
|
||||||
|
page
|
||||||
|
}) => {
|
||||||
|
await page.emulateMedia({ reducedMotion: 'reduce' });
|
||||||
|
await page.goto(`/documents/${docId}`);
|
||||||
|
await page.getByRole('button', { name: 'Transkribieren' }).click();
|
||||||
|
|
||||||
|
await expect(page.getByRole('heading', { level: 2, name: /Erste Transkription/ })).toBeVisible({
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
await expect(page.getByText(/Kurrent-Erkenner lernt noch/)).toBeVisible();
|
||||||
|
await expect(page.getByText(/Rahmen ziehen/)).toBeVisible();
|
||||||
|
await expect(page.getByText(/Text eingeben/)).toBeVisible();
|
||||||
|
await expect(page.getByText(/Speichert automatisch/)).toBeVisible();
|
||||||
|
await expect(page.getByRole('img', { name: /Rahmen ziehen|Animation/i })).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('training footer is NOT visible on empty doc', async ({ page }) => {
|
||||||
|
await page.emulateMedia({ reducedMotion: 'reduce' });
|
||||||
|
await page.goto(`/documents/${docId}`);
|
||||||
|
await page.getByRole('button', { name: 'Transkribieren' }).click();
|
||||||
|
await expect(page.getByText('Für Training vormerken')).not.toBeVisible({ timeout: 3000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('axe: panel empty state — light theme — no WCAG 2.1 AA violations', async ({ page }) => {
|
||||||
|
await page.emulateMedia({ reducedMotion: 'reduce' });
|
||||||
|
await page.goto(`/documents/${docId}`);
|
||||||
|
await page.getByRole('button', { name: 'Transkribieren' }).click();
|
||||||
|
await expect(page.getByRole('heading', { level: 2, name: /Erste Transkription/ })).toBeVisible({
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
|
||||||
|
const a11y = await buildAxe(page).analyze();
|
||||||
|
expect(a11y.violations, JSON.stringify(a11y.violations, null, 2)).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('axe: panel empty state — dark theme — no WCAG 2.1 AA violations', async ({ page }) => {
|
||||||
|
await page.emulateMedia({ reducedMotion: 'reduce' });
|
||||||
|
await page.goto(`/documents/${docId}`);
|
||||||
|
// Toggle dark theme
|
||||||
|
await page.getByRole('button', { name: /Farbmodus|theme/i }).click();
|
||||||
|
await page.getByRole('button', { name: 'Transkribieren' }).click();
|
||||||
|
await expect(page.getByRole('heading', { level: 2, name: /Erste Transkription/ })).toBeVisible({
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
|
||||||
|
const a11y = await buildAxe(page).analyze();
|
||||||
|
expect(a11y.violations, JSON.stringify(a11y.violations, null, 2)).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -26,6 +26,7 @@ export default defineConfig({
|
|||||||
use: {
|
use: {
|
||||||
baseURL: process.env.E2E_BASE_URL ?? 'http://localhost:3000',
|
baseURL: process.env.E2E_BASE_URL ?? 'http://localhost:3000',
|
||||||
locale: 'de-DE', // ensures Accept-Language: de is sent so locale detection defaults to German
|
locale: 'de-DE', // ensures Accept-Language: de is sent so locale detection defaults to German
|
||||||
|
reducedMotion: 'reduce', // prevents SMIL/CSS animations from flaking tests
|
||||||
screenshot: 'on', // always capture screenshots
|
screenshot: 'on', // always capture screenshots
|
||||||
video: 'retain-on-failure',
|
video: 'retain-on-failure',
|
||||||
trace: 'retain-on-failure'
|
trace: 'retain-on-failure'
|
||||||
|
|||||||
Reference in New Issue
Block a user