/** * Grundprüfungen der Barrierefreiheit am Startbildschirm, im echten Fenster. * * ## Was diese Datei prüft * * Drei Dinge, die keine Ansichtsrundreise brauchen: * * 1. einen axe-Rauchtest am Startbildschirm gegen den vollen Satz WCAG 2.2 AA * – die schnellste Antwort auf „ist überhaupt etwas kaputt?“, * 2. den **AAA**-Zielwert 7:1 (`color-contrast-enhanced`) als Bericht: Er wird * erhoben und ausgegeben, aber nicht erzwungen. AA ist das * Abnahmekriterium, AAA das Ziel, * 3. Struktureigenschaften, die axe nicht misst: die drei Landmarken, ein * zugänglicher Name an **jeder** vorhandenen `
` und ein sichtbarer * Fokusring nach echten Tabulatorschritten. * * ## Was diese Datei ausdrücklich NICHT prüft * * Die Abdeckung **je Ansicht und je Farbschema** steht vollständig in * `barrierefreiheit-ansichten.spec.ts`. Hier lief früher zusätzlich eine * Schleife über die drei Schemata – aber nur mit der Einzelregel * `color-contrast` und ohne den Startbildschirm je zu verlassen. Das las sich * nach „alle drei Farbschemata geprüft“ und war die Messung einer einzigen * Ansicht. Die Schleife ist dorthin gewandert, wo sie etwas aussagt; hier * stünde sie nur noch doppelt. * * Ein grüner Lauf heißt auch hier nicht „barrierefrei“: Automatische Prüfung * entscheidet nur, was sich maschinell entscheiden lässt. Die Szenarien an * NVDA, JAWS und VoiceOver in `docs/barrierefreiheit-pruefplan.md` ersetzt * diese Suite nicht. */ import { type ElectronApplication, type Page } from '@playwright/test'; import { appStarten, bauPruefen, themaSetzen } from './electron-hilfe'; import { axePruefen, befunde, WCAG_TAGS } from './axe-hilfe'; import { expect, test } from './konsolenwache'; // `undefined`, solange der beforeAll-Hook übersprungen wurde. let app: ElectronApplication | undefined; let fenster: Page; test.beforeAll(async () => { // Fehlender oder veralteter Bau ist ein Fehler, kein Grund zum Schweigen. bauPruefen(); ({ app, fenster } = await appStarten()); }); test.afterAll(async () => { await app?.close(); }); test.describe('axe-core Rauchtest', () => { test('Startbildschirm ist frei von WCAG-A/AA-Verstößen', async () => { const ergebnis = await axePruefen(fenster, { runOnly: { type: 'tag', values: [...WCAG_TAGS] }, }); expect(befunde(ergebnis)).toEqual([]); }); test('meldet den AAA-Zielwert (7:1) als Information, ohne den Lauf zu brechen', async () => { await themaSetzen(fenster, 'hell'); const ergebnis = await axePruefen(fenster, { runOnly: { type: 'rule', values: ['color-contrast-enhanced'] }, }); const offen = befunde(ergebnis, ['color-contrast-enhanced']); if (offen.length > 0) { console.info( `[a11y] AAA-Zielwert noch nicht überall erreicht (${String(offen.length)} Stellen):`, JSON.stringify(offen, null, 2), ); } // Bewusst keine Zusicherung: AAA ist Zielwert, nicht Abnahmekriterium. expect(Array.isArray(offen)).toBe(true); }); }); test.describe('Strukturelle Barrierefreiheit', () => { test('bietet die erwarteten Landmarken', async () => { await expect(fenster.getByRole('banner')).toBeVisible(); await expect(fenster.getByRole('main')).toBeVisible(); await expect(fenster.getByRole('contentinfo')).toBeVisible(); }); test('jede Sektion trägt einen zugänglichen Namen', async () => { /* Eine
wird nur dann als Landmarke („region“) an den Screenreader gemeldet, wenn sie benannt ist – sonst ist sie für die Navigation wertlos. Geprüft wird deshalb jede vorhandene Sektion, nicht eine feste Anzahl: So fällt auch eine später hinzugefügte Sektion ohne Namen auf. */ const namenlose = await fenster.evaluate(() => [...document.querySelectorAll('section')] .filter((s) => !s.getAttribute('aria-labelledby') && !s.getAttribute('aria-label')) .map((s) => s.className || s.textContent.trim().slice(0, 40) || '(ohne Kennzeichen)'), ); expect(namenlose, `Sektionen ohne zugänglichen Namen: ${namenlose.join(' | ')}`).toEqual([]); expect(await fenster.locator('section').count()).toBeGreaterThan(0); }); test('zeigt bei Tastaturfokus einen sichtbaren Fokusindikator', async () => { /* Der Ring hängt an `:focus-visible`. Chromium wendet das nur an, wenn zuletzt die Tastatur benutzt wurde – ein programmatisches `.focus()` nach einem Mausklick reicht nicht. Deshalb echte Tab-Schritte. Der erste Tabstopp ist der Skip-Link; geprüft wird der zweite, also das erste reguläre Bedienelement. Welches das ist, hängt vom Funktionsumfang ab – der Test stellt deshalb nur sicher, dass es überhaupt eines gibt. */ await fenster.locator('body').press('Tab'); await fenster.keyboard.press('Tab'); const fokussiert = fenster.locator(':focus'); await expect(fokussiert).toBeVisible(); const stil = await fokussiert.evaluate((element) => { const berechnet = getComputedStyle(element); return { outlineStyle: berechnet.outlineStyle, outlineWidth: berechnet.outlineWidth, outlineColor: berechnet.outlineColor, boxShadow: berechnet.boxShadow, }; }); expect(stil.outlineStyle).toBe('solid'); expect(Number.parseFloat(stil.outlineWidth)).toBeGreaterThanOrEqual(2); expect(stil.outlineColor).not.toBe('transparent'); // Zweiter, gegenfarbiger Ring aus `box-shadow` – sorgt für Sichtbarkeit // auf hellem UND dunklem Untergrund. expect(stil.boxShadow).not.toBe('none'); }); });