Frontend Testanforderungen: Accessibility (A11y)
Version: 1.0 | Stand: 30. Dezember 2025
Übersicht
Dieses Dokument beschreibt Accessibility-Tests nach WCAG 2.1 AA Standards.
Bereiche: Keyboard Navigation | Screen Reader | Focus Management | Color Contrast
1. Keyboard Navigation
Tab-Reihenfolge
| ID |
Test |
Erwartung |
Art |
| A11Y-KB01 |
Tab durch Formular |
Logische Reihenfolge |
Manual |
| A11Y-KB02 |
Tab durch Navigation |
Top→Left→Main→Footer |
Manual |
| A11Y-KB03 |
Tab in Dialog |
Fokus bleibt in Dialog |
E2E |
| A11Y-KB04 |
Tab-Trap vermeiden |
Kein Fokus-Gefängnis |
Manual |
| A11Y-KB05 |
Skip-Link |
"Zum Hauptinhalt" |
E2E |
Fokus-Indikatoren
| ID |
Test |
Erwartung |
Art |
| A11Y-FI01 |
Button fokussiert |
Sichtbarer Outline |
Visual |
| A11Y-FI02 |
Link fokussiert |
Sichtbarer Outline |
Visual |
| A11Y-FI03 |
Input fokussiert |
Border-Änderung |
Visual |
| A11Y-FI04 |
Custom Focus Style |
LLARS-konform (2px solid) |
Visual |
| A11Y-FI05 |
Focus nicht versteckt |
Immer sichtbar |
Manual |
Keyboard-Aktivierung
| ID |
Test |
Erwartung |
Art |
| A11Y-KA01 |
Enter auf Button |
Click-Event |
E2E |
| A11Y-KA02 |
Space auf Button |
Click-Event |
E2E |
| A11Y-KA03 |
Enter auf Link |
Navigation |
E2E |
| A11Y-KA04 |
Escape schließt Dialog |
Dialog geschlossen |
E2E |
| A11Y-KA05 |
Arrow Keys in Select |
Navigation funktioniert |
E2E |
| A11Y-KA06 |
Arrow Keys in Slider |
Wert ändert sich |
E2E |
| A11Y-KA07 |
Arrow Keys in Tabs |
Tab wechselt |
E2E |
Test-Code
// e2e/a11y/keyboard.spec.ts
test('A11Y-KB01: tab order is logical in forms', async ({ page }) => {
await page.goto('/login')
// Start at username
await page.keyboard.press('Tab')
await expect(page.locator('input[name="username"]')).toBeFocused()
// Tab to password
await page.keyboard.press('Tab')
await expect(page.locator('input[name="password"]')).toBeFocused()
// Tab to submit
await page.keyboard.press('Tab')
await expect(page.locator('button[type="submit"]')).toBeFocused()
})
test('A11Y-KB03: focus stays in dialog', async ({ page }) => {
await page.goto('/Home')
await page.click('[data-test="settings-btn"]')
// Tab through dialog
for (let i = 0; i < 20; i++) {
await page.keyboard.press('Tab')
const focusedInDialog = await page.evaluate(() => {
const focused = document.activeElement
return document.querySelector('.v-dialog')?.contains(focused)
})
expect(focusedInDialog).toBe(true)
}
})
test('A11Y-KA04: escape closes dialog', async ({ page }) => {
await page.goto('/Home')
await page.click('[data-test="settings-btn"]')
await expect(page.locator('.v-dialog')).toBeVisible()
await page.keyboard.press('Escape')
await expect(page.locator('.v-dialog')).not.toBeVisible()
})
2. Screen Reader Support
ARIA Labels
| ID |
Test |
Erwartung |
Art |
| A11Y-AR01 |
Icon-Buttons |
aria-label vorhanden |
Audit |
| A11Y-AR02 |
Bilder |
alt-Text vorhanden |
Audit |
| A11Y-AR03 |
Formulare |
aria-describedby bei Errors |
Audit |
| A11Y-AR04 |
Live Regions |
aria-live für Updates |
Audit |
| A11Y-AR05 |
Dialoge |
role="dialog" + aria-modal |
Audit |
Semantic HTML
| ID |
Test |
Erwartung |
Art |
| A11Y-SM01 |
Überschriften |
h1-h6 hierarchisch |
Audit |
| A11Y-SM02 |
Listen |
ul/ol mit li |
Audit |
| A11Y-SM03 |
Navigation |
nav Element |
Audit |
| A11Y-SM04 |
Main Content |
main Element |
Audit |
| A11Y-SM05 |
Footer |
footer Element |
Audit |
Screen Reader Announcements
| ID |
Test |
Erwartung |
Art |
| A11Y-SR01 |
Toast/Snackbar |
aria-live="polite" |
Manual |
| A11Y-SR02 |
Loading States |
aria-busy="true" |
Manual |
| A11Y-SR03 |
Fehler-Meldungen |
Vorgelesen |
Manual |
| A11Y-SR04 |
Erfolg-Meldungen |
Vorgelesen |
Manual |
| A11Y-SR05 |
Progress Updates |
aria-valuenow |
Manual |
Test-Code (axe-core)
// e2e/a11y/axe-audit.spec.ts
import { test, expect } from '@playwright/test'
import AxeBuilder from '@axe-core/playwright'
test.describe('Accessibility Audit', () => {
test('A11Y-AR01: home page has no violations', async ({ page }) => {
await page.goto('/Home')
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa'])
.analyze()
expect(results.violations).toEqual([])
})
test('A11Y-AR01: login page has no violations', async ({ page }) => {
await page.goto('/login')
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa'])
.analyze()
expect(results.violations).toEqual([])
})
test('A11Y-AR01: admin page has no violations', async ({ adminPage }) => {
await adminPage.goto('/admin')
const results = await new AxeBuilder({ page: adminPage })
.withTags(['wcag2a', 'wcag2aa'])
.analyze()
expect(results.violations).toEqual([])
})
})
3. Color Contrast
Text Contrast (WCAG AA: 4.5:1)
| ID |
Test |
Element |
Erwartung |
Art |
| A11Y-CC01 |
Body Text |
Normaler Text |
≥4.5:1 |
Audit |
| A11Y-CC02 |
Button Text |
LBtn |
≥4.5:1 |
Audit |
| A11Y-CC03 |
Link Text |
Links |
≥4.5:1 |
Audit |
| A11Y-CC04 |
Error Text |
Fehlermeldungen |
≥4.5:1 |
Audit |
| A11Y-CC05 |
Placeholder |
Input Placeholder |
≥4.5:1 |
Audit |
Large Text Contrast (WCAG AA: 3:1)
| ID |
Test |
Element |
Erwartung |
Art |
| A11Y-LC01 |
Headings |
h1-h3 |
≥3:1 |
Audit |
| A11Y-LC02 |
Large Buttons |
size="large" |
≥3:1 |
Audit |
Non-Text Contrast (WCAG AA: 3:1)
| ID |
Test |
Element |
Erwartung |
Art |
| A11Y-NC01 |
Focus Indicator |
Outline |
≥3:1 |
Audit |
| A11Y-NC02 |
Form Borders |
Input Borders |
≥3:1 |
Audit |
| A11Y-NC03 |
Icons |
Actionable Icons |
≥3:1 |
Audit |
| A11Y-NC04 |
Charts |
Chart Lines/Bars |
≥3:1 |
Audit |
LLARS Farben Kontrast-Check
| Farbe |
Hex |
Auf Weiß |
Auf Schwarz |
| Primary |
#b0ca97 |
1.8:1 ⚠️ |
8.5:1 ✅ |
| Secondary |
#D1BC8A |
1.7:1 ⚠️ |
7.6:1 ✅ |
| Accent |
#88c4c8 |
2.1:1 ⚠️ |
7.0:1 ✅ |
| Success |
#98d4bb |
1.9:1 ⚠️ |
7.9:1 ✅ |
| Danger |
#e8a087 |
2.0:1 ⚠️ |
6.1:1 ✅ |
| Text Dark |
#333333 |
12.6:1 ✅ |
1.7:1 ⚠️ |
Hinweis: LLARS Pastel-Farben benötigen dunklen Text auf hellem Hintergrund.
4. Focus Management
Dialog Focus
| ID |
Test |
Erwartung |
Art |
| A11Y-DF01 |
Dialog öffnet |
Fokus auf erstes Element |
E2E |
| A11Y-DF02 |
Dialog schließt |
Fokus zurück zum Trigger |
E2E |
| A11Y-DF03 |
Nested Dialog |
Fokus korrekt verwaltet |
E2E |
Page Navigation
| ID |
Test |
Erwartung |
Art |
| A11Y-PN01 |
Route-Wechsel |
Fokus auf h1 oder main |
E2E |
| A11Y-PN02 |
Nach Formular-Submit |
Fokus auf Ergebnis/Fehler |
E2E |
Dynamic Content
| ID |
Test |
Erwartung |
Art |
| A11Y-DC01 |
Liste erweitert |
Fokus nicht verloren |
E2E |
| A11Y-DC02 |
Accordion öffnet |
Fokus auf Content |
E2E |
| A11Y-DC03 |
Tab wechselt |
Fokus auf neuen Content |
E2E |
5. Motion & Animation
Reduced Motion
| ID |
Test |
Erwartung |
Art |
| A11Y-RM01 |
prefers-reduced-motion |
Animationen reduziert |
E2E |
| A11Y-RM02 |
Loading Animation |
Statisch oder minimal |
E2E |
| A11Y-RM03 |
Hover Effects |
Keine Bewegung |
E2E |
| A11Y-RM04 |
Transitions |
Instant oder minimal |
E2E |
Test-Code
// e2e/a11y/reduced-motion.spec.ts
test('A11Y-RM01: respects prefers-reduced-motion', async ({ browser }) => {
const context = await browser.newContext({
reducedMotion: 'reduce'
})
const page = await context.newPage()
await page.goto('/Home')
// Check that animations are disabled
const animation = await page.evaluate(() => {
const element = document.querySelector('.l-loading')
if (!element) return 'no-element'
const styles = getComputedStyle(element)
return styles.animation
})
expect(animation).toMatch(/none|0s/)
await context.close()
})
Labels
| ID |
Test |
Erwartung |
Art |
| A11Y-FL01 |
Input hat Label |
Verknüpft via for/id |
Audit |
| A11Y-FL02 |
Required markiert |
aria-required="true" |
Audit |
| A11Y-FL03 |
Error verknüpft |
aria-describedby |
Audit |
Validation
| ID |
Test |
Erwartung |
Art |
| A11Y-FV01 |
Error bei Submit |
aria-invalid="true" |
E2E |
| A11Y-FV02 |
Error-Text |
Screen Reader liest vor |
Manual |
| A11Y-FV03 |
Fokus auf erstem Fehler |
Automatisch fokussiert |
E2E |
Autocomplete
| ID |
Test |
Erwartung |
Art |
| A11Y-FA01 |
Username |
autocomplete="username" |
Audit |
| A11Y-FA02 |
Password |
autocomplete="current-password" |
Audit |
| A11Y-FA03 |
Email |
autocomplete="email" |
Audit |
Images
| ID |
Test |
Erwartung |
Art |
| A11Y-IM01 |
Informative Images |
Descriptiver alt-Text |
Audit |
| A11Y-IM02 |
Decorative Images |
alt="" oder role="presentation" |
Audit |
| A11Y-IM03 |
Complex Images |
Lange Beschreibung |
Audit |
Icons
| ID |
Test |
Erwartung |
Art |
| A11Y-IC01 |
Standalone Icons |
aria-label |
Audit |
| A11Y-IC02 |
Icons in Buttons |
aria-hidden + Button Label |
Audit |
| A11Y-IC03 |
Status Icons |
Zusätzlicher Text |
Audit |
8. Tables
Data Tables
| ID |
Test |
Erwartung |
Art |
| A11Y-TB01 |
Table Headers |
th mit scope |
Audit |
| A11Y-TB02 |
Caption |
caption oder aria-label |
Audit |
| A11Y-TB03 |
Sortierbare Columns |
aria-sort |
Audit |
| A11Y-TB04 |
Pagination |
Status announced |
Manual |
9. Komplette Seiten-Audits
Kritische Seiten
| ID |
Seite |
Erwartung |
Art |
| A11Y-PG01 |
/login |
Keine Violations |
Audit |
| A11Y-PG02 |
/Home |
Keine Violations |
Audit |
| A11Y-PG03 |
/chat |
Keine Violations |
Audit |
| A11Y-PG04 |
/admin |
Keine Violations |
Audit |
| A11Y-PG05 |
/Ranker |
Keine Violations |
Audit |
| A11Y-PG06 |
/MarkdownCollab |
Keine Violations |
Audit |
| A11Y-PG07 |
/LatexCollab |
Keine Violations |
Audit |
| A11Y-PG08 |
/Anonymize |
Keine Violations |
Audit |
Test-Code (Automated Audit)
// e2e/a11y/full-audit.spec.ts
import AxeBuilder from '@axe-core/playwright'
const criticalPages = [
'/login',
'/Home',
'/chat',
'/admin',
'/Ranker',
'/Rater',
'/MarkdownCollab',
'/LatexCollab',
'/Anonymize'
]
for (const pagePath of criticalPages) {
test(`A11Y audit: ${pagePath}`, async ({ authenticatedPage }) => {
await authenticatedPage.goto(pagePath)
const results = await new AxeBuilder({ page: authenticatedPage })
.withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
.exclude('.monaco-editor') // Exclude third-party
.analyze()
// Log violations for debugging
if (results.violations.length > 0) {
console.log(`Violations on ${pagePath}:`, JSON.stringify(results.violations, null, 2))
}
expect(results.violations).toEqual([])
})
}
10. Checkliste für manuelle Tests
Keyboard
Screen Reader
Farben
Motion
| Tool |
Verwendung |
| axe-core |
Automatisierte Audits |
| WAVE |
Browser Extension |
| Lighthouse |
Chrome DevTools |
| VoiceOver |
macOS Screen Reader |
| NVDA |
Windows Screen Reader |
| Color Contrast Analyzer |
Kontrast-Check |
Letzte Aktualisierung: 30. Dezember 2025