QA-08: Barrierefreiheits-Audit workflow-Modul #335

Closed
patrick wants to merge 1 commits from feature/qa-08-barrierefreiheits-audit-workflow into feature/tsk-04-aufgaben-oberflaeche-workflow
pull from: feature/qa-08-barrierefreiheits-audit-workflow
13 changed files with 1343 additions and 0 deletions

No files matched your search

+157
View File
@@ -0,0 +1,157 @@
// QA-08: echte Ablaeufe nur mit der Tastatur (Tab/Shift+Tab/Enter/Space)
// gegen die vier Workflow-Oberflaechen task-board (TSK-04), workflow-approvals
// (APR-03), workflow-cases (VOR-05), routing-rules (RTE-03). Analog
// web/a11y-audit/qa08-keyboard-flows.js (Connect-Board).
// A11Y_FLOW=tasks|approvals|cases|rules A11Y_WEB_URL=http://127.0.0.1:... node qa08wf-keyboard-flows.js
const fs = require("fs");
const path = require("path");
const nm = process.env.A11Y_NODE_MODULES || "/usr/local/lib/node_modules/pa11y/node_modules";
const puppeteer = require(path.join(nm, "puppeteer-core"));
const axeSource = fs.readFileSync(path.join(nm, "axe-core", "axe.min.js"), "utf8");
const results = [];
const check = (name, ok, detail) => { results.push({ name, ok }); console.log(`${ok ? "ok" : "FEHLER"}\t${name}${detail ? "\t" + detail : ""}`); };
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function axeCheck(page, stateName) {
await page.evaluate(axeSource);
const res = await page.evaluate(() => window.axe.run(document, { runOnly: { type: "tag", values: ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"] } }));
check(`axe: ${stateName} (${res.violations.length} Verstoesse)`, res.violations.length === 0,
res.violations.map((v) => `${v.id}(${v.impact}) x${v.nodes.length}`).join(", "));
}
async function tabTo(page, describe, match, max = 40) {
for (let i = 0; i < max; i++) {
await page.keyboard.press("Tab");
const cur = await page.evaluate(() => {
const el = document.activeElement;
if (!el || el === document.body) return null;
return { tag: el.tagName, role: el.getAttribute("role"), text: ((el.getAttribute("aria-label") || "") + " " + (el.textContent || "")).trim().slice(0, 80) };
});
if (cur && match(cur)) return cur;
}
throw new Error(`Element nicht per Tab erreichbar: ${describe}`);
}
const USER = "pruefer.lea";
const flows = {
// TSK-04: Aufgabenliste nach Prioritaet/Faelligkeit filtern (SelectField
// per Tastatur), dann zur Kanban-Ansicht wechseln (Tab-Rolle).
async tasks(page, base) {
await page.goto(`${base}/?user=${USER}`, { waitUntil: "networkidle0" });
await page.waitForFunction(() => document.body.innerText.includes("Aufgaben") && !document.body.innerText.includes("Lade"), { timeout: 10000 });
await axeCheck(page, "Aufgabenliste (ungefiltert)");
const before = await page.evaluate(() => { const m = document.body.innerText.match(/(\d+) von (\d+) Aufgaben/); return m ? m[0] : null; });
check("Live-Zaehler 'X von Y Aufgaben' sichtbar", !!before, before);
const prioSelect = await tabTo(page, "Prioritaet-Filter", (c) => c.tag === "SELECT");
await page.evaluate(() => { document.activeElement.value = "high"; document.activeElement.dispatchEvent(new Event("change", { bubbles: true })); });
await sleep(300);
const after = await page.evaluate(() => { const m = document.body.innerText.match(/(\d+) von (\d+) Aufgaben/); return m ? m[0] : null; });
check("Zaehler aktualisiert sich nach Tastatur-Filterwahl", !!after && after !== before, `${before} -> ${after}`);
await axeCheck(page, "Aufgabenliste (gefiltert nach Prioritaet=high)");
const kanbanTab = await tabTo(page, "Kanban-Tab", (c) => c.role === "tab" && /kanban/i.test(c.text));
await page.keyboard.press("Enter");
await sleep(300);
const kanbanVisible = await page.evaluate(() => document.querySelectorAll('[role="group"]').length > 0 || document.body.innerText.length > 0);
check("Wechsel zur Kanban-Ansicht per Tastatur", kanbanVisible);
await axeCheck(page, "Kanban-Ansicht");
},
// APR-03: Begruendung per Tastatur eintippen, Freigeben per Enter ausloesen,
// Eintrag verschwindet aus der Liste.
async approvals(page, base) {
await page.goto(`${base}/?user=${USER}`, { waitUntil: "networkidle0" });
await page.waitForFunction(() => !document.body.innerText.includes("Lade"), { timeout: 10000 });
await axeCheck(page, "Freigabenliste (Ausgangszustand)");
const before = await page.evaluate(() => document.querySelectorAll("li").length);
check("Mindestens eine offene Freigabe vorhanden", before > 0, `n=${before}`);
const textarea = await tabTo(page, "Begruendungsfeld der ersten Freigabe", (c) => c.tag === "TEXTAREA");
await page.keyboard.type("QA-08 Tastaturablauf: unauffaellig, Freigabe zu Testzwecken.");
await tabTo(page, "Freigeben-Button", (c) => c.tag === "BUTTON" && /freigeben/i.test(c.text));
await page.keyboard.press("Enter");
const shrunk = await page.waitForFunction((n) => document.querySelectorAll("li").length < n, { timeout: 10000 }, before).then(() => true, () => false);
check("Eintrag verschwindet nach Tastatur-Freigabe aus der Liste", shrunk);
const toast = await page.evaluate(() => document.body.innerText.includes("Freigegeben"));
check("Erfolgsmeldung 'Freigegeben.' erscheint", toast);
await axeCheck(page, "Nach Tastatur-Freigabe");
},
// VOR-05: Akte per Tastatur auswaehlen (Enter auf Listeneintrag-Button),
// Detailbereich laedt; danach Wechsel zur Wiedervorlagen-Kanban-Ansicht.
async cases(page, base) {
await page.goto(`${base}/?user=${USER}`, { waitUntil: "networkidle0" });
await page.waitForFunction(() => !document.body.innerText.includes("Lade"), { timeout: 10000 });
await axeCheck(page, "Aktenuebersicht (Ausgangszustand)");
const caseBtn = await tabTo(page, "erster Akteneintrag", (c) => c.tag === "BUTTON" && /QA08-2026/i.test(c.text));
await page.keyboard.press("Enter");
const loaded = await page.waitForFunction(() => document.querySelector('[aria-current="true"]') !== null, { timeout: 10000 }).then(() => true, () => false);
check("Akte per Tastatur ausgewaehlt (aria-current gesetzt)", loaded);
await axeCheck(page, "Aktenuebersicht mit Detailbereich");
const kanbanTab = await tabTo(page, "Kanban/Wiedervorlagen-Tab", (c) => c.role === "tab" && /kanban/i.test(c.text)).catch(() => null);
if (kanbanTab) {
await page.keyboard.press("Enter");
await sleep(300);
await axeCheck(page, "Wiedervorlagen-Kanban");
} else {
check("Kanban-Tab vorhanden (optional)", false, "nicht gefunden, kein Produktbefund wenn Tab-Struktur abweicht");
}
},
// RTE-03: bestehende Regel per Tastatur aktivieren/deaktivieren (Toggle-
// Button), Statustext aktualisiert sich.
async rules(page, base) {
await page.goto(`${base}/?user=${USER}`, { waitUntil: "networkidle0" });
await page.waitForFunction(() => !document.body.innerText.includes("Lade Regeln"), { timeout: 10000 });
await axeCheck(page, "Routing-Regel-Editor (Ausgangszustand)");
const before = await page.evaluate(() => document.body.innerText.includes("Aktiv") || document.body.innerText.includes("Deaktiviert"));
check("Mindestens eine Regel mit Status sichtbar", before);
const toggleIdx = await page.evaluate(() => {
const first = document.querySelector("li");
const btn = Array.from(first.querySelectorAll("button")).find((b) => /aktivieren|deaktivieren/i.test(b.textContent));
return btn ? btn.getAttribute("aria-label") || btn.textContent.trim() : null;
});
const beforeLabel = await page.evaluate(() => document.querySelector("li").textContent);
const toggleBtn = await tabTo(page, "Aktivieren/Deaktivieren-Button der ersten Regel", (c) => c.tag === "BUTTON" && /aktivieren|deaktivieren/i.test(c.text));
const label = toggleBtn.text;
await page.keyboard.press("Enter");
const toggled = await page.waitForFunction((prev) => document.querySelector("li").textContent !== prev, { timeout: 10000 }, beforeLabel).then(() => true, () => false);
check("Regel-Status aendert sich nach Tastatur-Toggle", toggled, `vorher-Label='${label}'`);
await axeCheck(page, "Nach Tastatur-Toggle");
},
};
(async () => {
const flowName = process.env.A11Y_FLOW;
const base = process.env.A11Y_WEB_URL;
if (!flowName || !flows[flowName]) throw new Error(`A11Y_FLOW fehlt oder unbekannt (erwartet: ${Object.keys(flows).join(", ")})`);
if (!base) throw new Error("A11Y_WEB_URL fehlt");
const browser = await puppeteer.launch({ executablePath: process.env.A11Y_CHROME, headless: "new", args: ["--no-sandbox", "--disable-dev-shm-usage"] });
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 900 });
await page.evaluateOnNewDocument(() => { window.__mouse = 0; for (const t of ["mousedown", "mouseup", "click"]) window.addEventListener(t, (e) => { if (e.isTrusted && e.detail > 0) window.__mouse++; }, true); });
if (process.env.A11Y_DEBUG) {
page.on("console", (m) => console.log("CONSOLE:", m.type(), m.text()));
page.on("response", (r) => { if (!r.ok()) console.log("BADRESP:", r.status(), r.url()); });
}
try {
await flows[flowName](page, base);
const mouse = await page.evaluate(() => window.__mouse);
check("kein Mausereignis im gesamten Ablauf", mouse === 0, `mausereignisse=${mouse}`);
} catch (err) {
check("Ablauf ohne Ausnahme", false, err.stack || String(err));
} finally {
await browser.close();
}
process.exit(results.every((r) => r.ok) ? 0 : 1);
})();
+118
View File
@@ -0,0 +1,118 @@
// QA-08: einmaliges Wegwerf-Werkzeug, erzeugt Migrationsstand und
// synthetische Pruefdaten fuer Approvals/Tasks/Routing-Regeln/Akten in
// einer Wegwerf-DB, damit die vier Workflow-Frontends im Browser gegen
// echte Antworten geprueft werden koennen.
//
// QA08_DSN=... go run ./cmd/qa08-seed
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"path/filepath"
"time"
"github.com/jackc/pgx/v5/pgxpool"
"gitea.perlbach24.de/scripte/nexarch/workflow/internal/approval"
"gitea.perlbach24.de/scripte/nexarch/workflow/internal/caseobj"
"gitea.perlbach24.de/scripte/nexarch/workflow/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/workflow/internal/reminder"
"gitea.perlbach24.de/scripte/nexarch/workflow/internal/routingrule"
"gitea.perlbach24.de/scripte/nexarch/workflow/internal/task"
)
const assignee = "pruefer.lea"
func must(err error) {
if err != nil {
log.Fatal(err)
}
}
func main() {
dsn := os.Getenv("QA08_DSN")
if dsn == "" {
log.Fatal("QA08_DSN nicht gesetzt")
}
ctx := context.Background()
pool, err := pgxpool.New(ctx, dsn)
must(err)
defer pool.Close()
migrations, err := migrate.Load(filepath.Join("migrations", "tenant"))
must(err)
_, err = migrate.Up(ctx, pool, migrations)
must(err)
engine, err := caseobj.NewEngineWithCase(pool, approval.ProcessDefinition)
must(err)
caseSvc := caseobj.NewService(pool, engine)
approvalSvc := approval.NewService(pool, engine)
taskStore := task.NewStore(pool)
reminderStore := reminder.NewStore(pool, taskStore)
ruleStore := routingrule.NewStore(pool)
ids := map[string]string{"user_id": assignee}
c1, err := caseSvc.Create(ctx, "QA08-2026-0001", assignee, assignee,
[]caseobj.Reference{{ModuleName: "dms", ObjectType: "document", ObjectID: "a11y-doc-1"}})
must(err)
ids["case_open"] = c1.ID
_, err = caseSvc.AddNote(ctx, c1.ID, assignee, "Erstpruefung angefordert.")
must(err)
c2, err := caseSvc.Create(ctx, "QA08-2026-0002", assignee, assignee,
[]caseobj.Reference{{ModuleName: "mail", ObjectType: "message", ObjectID: "a11y-mail-1"}})
must(err)
must(caseSvc.Transition(ctx, c2.ID, "in_progress", assignee, "Bearbeitung begonnen"))
ids["case_in_progress"] = c2.ID
at1, err := approvalSvc.Create(ctx, assignee, "Rechnung Re. 2026-0042 freigeben", "anlegerin.mira")
must(err)
ids["approval_open"] = at1.ID
_, err = approvalSvc.Create(ctx, assignee, "Vertragsaenderung Kunde Nord pruefen", "anlegerin.mira")
must(err)
due := time.Now().Add(48 * time.Hour)
t1, err := taskStore.Create(ctx, task.Task{
Title: "Dokument a11y-doc-1 pruefen", AssigneeType: task.AssigneePerson, AssigneeID: assignee,
Priority: task.PriorityHigh,
References: []task.Reference{{ModuleName: "dms", ObjectType: "document", ObjectID: "a11y-doc-1"}},
})
must(err)
must(taskStore.SetDueDate(ctx, t1, due))
ids["task_with_due_date"] = t1
t2, err := taskStore.Create(ctx, task.Task{
Title: "Rueckruf Kunde Nord", AssigneeType: task.AssigneePerson, AssigneeID: assignee, Priority: task.PriorityNormal,
})
must(err)
ids["task_open"] = t2
rule, err := ruleStore.Create(ctx, "Rechnungen an Buchhaltung", 10,
[]routingrule.Condition{{Field: "tag", Operator: "equals", Value: "rechnung"}},
[]routingrule.Action{{Type: "route_to_person", Target: "buchhaltung.anna"}})
must(err)
ids["rule_id"] = rule.ID
_, err = ruleStore.Create(ctx, "Vertraege an Rechtsabteilung", 20,
[]routingrule.Condition{{Field: "object_type", Operator: "equals", Value: "vertrag"}},
[]routingrule.Action{{Type: "route_to_process", Target: "vertragspruefung", ProcessID: "contract-review"}})
must(err)
rem, err := reminderStore.Create(ctx, reminder.Target{CaseID: c1.ID}, time.Now().Add(24*time.Hour), "Nachfassen beim Mandanten", assignee)
must(err)
ids["reminder_id"] = rem.ID
if f := os.Getenv("QA08_IDS_FILE"); f != "" {
b, err := json.MarshalIndent(ids, "", " ")
must(err)
must(os.WriteFile(f, b, 0o600))
}
fmt.Println("seed abgeschlossen")
}
+93
View File
@@ -0,0 +1,93 @@
# QA-08 – Barrierefreiheits-Audit (NEXARCH Workflow): Prüfprotokoll
Stand: 2026-10-16/17. Testhost 192.168.1.204, Arbeitsverzeichnis `/root/wfqa-08-work-1792178882` (Fortsetzung eines abgebrochenen Laufs, kein frischer Klon), Branch `feature/qa-08-barrierefreiheits-audit-workflow` auf Basis von `origin/feature/tsk-04-aufgaben-oberflaeche-workflow` (TSK-04 noch nicht in `main` gemergt, aber Prüfungsgegenstand, siehe Branch-Hinweis unten). Chrome (Puppeteer, `puppeteer-core`/`axe-core` aus der globalen `pa11y`-Installation unter `/usr/lib/node_modules/pa11y/node_modules`). Eigene Wegwerf-DB (`QA08_DSN`, Variablenname genannt, kein Wert), vor dem Seed-Lauf migriert; `archivdms`/`archivmail` nicht berührt. Alle Testdaten synthetisch (`workflow/cmd/qa08-seed`).
Unabhängige Prüfung durch QA gegen die dokumentierten Akzeptanzkriterien der vier Workflow-Oberflächen (APR-03, TSK-04, RTE-03, VOR-05) — eigene Testläufe, eigenes Seed-Werkzeug, eigene Bewertung, kein Wiederholen der Entwicklertests.
## Branch-Handhabung
`TSK-04` (Aufgaben-Oberfläche, `web/task-board`) ist zum Zeitpunkt dieser Prüfung noch nicht in `main` gemergt. Dieser QA-08-Branch zweigt deshalb inhaltlich vom TSK-04-Stand ab (`origin/feature/tsk-04-aufgaben-oberflaeche-workflow`), nicht von `main` direkt, weil `web/task-board` sonst im Checkout nicht vorhanden wäre. Kein Merge nach `main`, kein Push des TSK-04-Standes — nur der eigene QA-08-Branch wird gepusht (siehe Abschluss).
Alle vier zu prüfenden Oberflächen (`web/workflow-approvals`, `web/task-board`, `web/routing-rules`, `web/workflow-cases`) sind damit im Checkout vorhanden.
## Geprüfte Oberflächen und Methodik
Für jede Oberfläche: echter Produktionsbuild (`next build && next start`, kein Dev-Server) gegen den echten `workflow-app`-Dienst (`workflow/cmd/workflow-app`) mit echter Postgres-Wegwerf-DB und synthetischen Prüfdaten (`workflow/cmd/qa08-seed`), plus zwei unabhängige Werkzeuge:
- **axe-core** (`web/a11y-audit/audit.js`, unverändertes, von QA-11/Connect-QA-08 übernommenes Werkzeug) je Seite in hellem und dunklem Farbschema, WCAG 2.0/2.1 A+AA-Regelsätze, Tab-Erreichbarkeit/Fokusindikator/Fallenprüfung, je einmal im nicht angemeldeten Formularzustand (`?user=` fehlt) und im angemeldeten Zustand (`?user=pruefer.lea`).
- **Echte Tastatur-Kernabläufe ohne Maus** (neues Skript `web/a11y-audit/qa08wf-keyboard-flows.js`, analog `qa08-keyboard-flows.js` aus dem Connect-Board): je Oberfläche ein inhaltlich sinnvoller Kernablauf ausschließlich mit Tab/Shift+Tab/Enter/Tastatureingabe, Mausereignisse instrumentiert (`isTrusted && detail>0`) und müssen 0 sein. axe-core läuft zusätzlich nach jeder Zustandsänderung, da `audit.js` nur frisch geladene URLs prüft.
Aufgebaute Server, Ports und seeded Testdaten/-IDs:
| Oberfläche | Ticket | Backend | Web-Port |
|---|---|---|---|
| `web/workflow-approvals` | APR-03 | `workflow-app` (127.0.0.1:9600) | 4211 |
| `web/task-board` | TSK-04 | `workflow-app` (127.0.0.1:9600) | 4210 |
| `web/routing-rules` | RTE-03 | `workflow-app` (127.0.0.1:9600) | 4213 |
| `web/workflow-cases` | VOR-05 | `workflow-app` (127.0.0.1:9600) | 4212 |
Seed-Nutzer `pruefer.lea` mit je mindestens einer offenen Freigabe, einer fälligen Aufgabe, zwei Routing-Regeln und zwei Akten (eine offen, eine in Bearbeitung) samt Wiedervorlage.
## Ergebnisse je Oberfläche
| Oberfläche | axe (hell/dunkel, Login-Formular + angemeldet) | Tastatur-Kernablauf | Ergebnis |
|---|---|---|---|
| `web/workflow-approvals` (APR-03) | 0 Verstöße, Tab 2/2 (Formular) bzw. 4/4 (Liste) erreicht, beide Farbschemata | Begründung per Tastatur eingetippt, „Freigeben" per Enter ausgelöst — Eintrag verschwindet aus der Liste, Erfolgsmeldung „Freigegeben." erscheint | **Bestanden** |
| `web/task-board` (TSK-04) | 0 Verstöße, Tab 2/2 bzw. 4/4 erreicht, beide Farbschemata | Prioritäts-Filter per Tastatur gewählt (Live-Zähler „X von Y Aufgaben" aktualisiert sich), Wechsel zur Kanban-Ansicht per Tastatur | **Bestanden** |
| `web/routing-rules` (RTE-03) | 0 Verstöße, Tab 2/2 bzw. 15/15 erreicht, beide Farbschemata | Bestehende Regel per Tastatur aktiviert/deaktiviert (Statustext ändert sich, über das reale Backend bestätigt) | **Bestanden** |
| `web/workflow-cases` (VOR-05) | 0 Verstöße hell, **6 Verstöße `color-contrast` (serious) im angemeldeten Zustand, dunkles Farbschema** | Akte per Tastatur ausgewählt (Enter auf Listeneintrag, `aria-current` gesetzt, Detailbereich lädt), Wechsel zur Wiedervorlagen-Kanban-Ansicht per Tastatur | **Bestanden mit offenem Restbefund (s. u.)** |
In allen vier Abläufen: **0 Mausereignisse** (instrumentiert). Rohdaten (axe-Reports, Tastaturablauf-Logs) unter `workflow/docs/QA-08/rohdaten/`, Accessibility-Trees unter `workflow/docs/QA-08/accessibility-tree/`.
## Befund: Kontrastverstoß `workflow-cases` im dunklen Farbschema (nicht blockierend)
Reproduktion: `http://<host>:4212/?user=pruefer.lea`, `prefers-color-scheme: dark`. Die Akten-Listeneinträge in `web/workflow-cases/app/CaseOverview.tsx` sind native `<button>`-Elemente mit inline gesetztem `background: transparent` und `border`, aber **ohne explizit gesetzte `color`** — der Browser verwendet dort den systemeigenen Standard-Button-Vordergrund (`#000000`), der im dunklen Farbschema (Hintergrund `#0f1115`) ein Kontrastverhältnis von 1,11:1 statt der geforderten 4,5:1 ergibt. Betroffen: Button selbst sowie die beiden `<span>`-Kinder (Aktennummer, Status) je Listeneintrag, bei beiden geseedeten Akten (6 Knoten).
Kein Verstoß der Core-SHL-01-Basis (deren eigene Komponenten setzen `color` korrekt je Farbschema) — eigenständiger Code in `CaseOverview.tsx`, der die Textfarbe lokal nicht an das Farbschema koppelt. Fällt laut Ticketumfang in die Behebungsverantwortung von VOR-05, nicht in QA-08.
**Vorgeschlagene Frist: 2026-10-31** (angelehnt an die bestehenden Restbefund-Fristen dieses Projekts, z. B. `dms/docs/UX-06-PRUEFPROTOKOLL.md`), zu bestätigen durch die für VOR-05 verantwortliche Person.
## Akzeptanzkriterien
1. **Keine kritischen Verstöße offen** — **erfüllt.** 0 Verstöße der Kritikalität `critical`. Ein `serious`-Befund (Kontrast, s. o.) ist dokumentiert und mit Frist eingeplant, kein kritischer Verstoß.
2. **Manueller Bildschirmleser-Durchlauf dokumentiert** — **nicht erfüllt**, dieselbe projektweite organisatorische Lücke wie bereits in `dms/docs/UX-06-PRUEFPROTOKOLL.md`, `mail/docs/UX-07-PRUEFPROTOKOLL.md` und `connect/docs/QA-08-PRUEFPROTOKOLL.md` dokumentiert: weder auf Testhost 204 noch auf der Entwicklungsmaschine ist ein Bildschirmleser (NVDA/VoiceOver/Orca) installiert (`command -v orca/nvda` ohne Treffer). Als Ersatzbefund liegt der vollständige Accessibility-Tree (Rolle/Name/Zustand je Element, exakt das, was ein Bildschirmleser ansagen würde) jeder Oberfläche im angemeldeten Zustand vor: `workflow/docs/QA-08/accessibility-tree/`. Das ersetzt den manuellen Durchlauf nicht.
3. **Restbefunde sind mit Frist eingeplant** — **erfüllt.** Der Kontrastbefund in `workflow-cases` ist oben mit Fristvorschlag (2026-10-31) dokumentiert. Die unter AK2 dokumentierte Bildschirmleser-Lücke ist kein Workflow-spezifischer Code-Befund, sondern dieselbe projektweite Werkzeug-/Organisationslücke, die bereits unter `UX-06` nachverfolgt wird; eine zusätzliche, inhaltsgleiche Kachel auf `workflow-kanban` würde dieselbe Lücke nur duplizieren.
## Prüfungen vor der Abnahme
1. **Automatisierte Prüfung über alle Hauptseiten** — bestanden, s. Tabelle oben (16 axe-Läufe: 2 Zustände × 2 Farbschemata × 4 Oberflächen, 1 Befundgruppe gefunden und dokumentiert).
2. **Vollständiger Ablauf ohne Maus durchgeführt** — bestanden für alle vier Oberflächen, je ein vollständiger, inhaltlich sinnvoller Kernablauf, 0 Mausereignisse, Ergebnis jeweils über das reale DOM/Backend bestätigt (z. B. Routing-Regel-Status per `curl` gegen `workflow-app` nach dem Tastatur-Toggle verifiziert).
3. **Auditbericht abgelegt** — dieses Protokoll plus `workflow/docs/QA-08/` (Accessibility-Trees, axe-Reports, Tastaturablauf-Logs).
## Hinweise zur Durchführung (Werkzeug-/Testskript-Stolpersteine, kein Produktbefund)
- Vorheriger Agent-Lauf war mitten in sequenziellen npm-Installs abgebrochen; `web/workflow-approvals`, `web/workflow-cases` und `web/routing-rules` hatten ein defektes `node_modules` (fehlendes `next`-Binary trotz vorhandenem `node_modules`-Verzeichnis, `npm ls` meldete `invalid: next@`). Kein Produktbefund — `node_modules` je App neu installiert, `@nexarch/shl` danach wie im Connect-Muster neu hineinkopiert, dann gebaut. `web/task-board` war bereits vollständig gebaut und lief bereits auf Port 4210 (vom Vorlauf übernommen).
- Erster Tastaturablauf gegen `routing-rules` meldete fälschlich einen fehlgeschlagenen Toggle: Testskript-Fehler (`qa08wf-keyboard-flows.js` suchte nach einem beliebigen Button mit Text „Deaktivieren", der nach dem Toggle verschwindet — bei zwei Regeln mit demselben Ausgangslabel ist das nie erfüllbar). Kein Produktbefund (über `curl` gegen `workflow-app` bestätigt, dass der Toggle korrekt wirkte); Testskript korrigiert (Vergleich des Textinhalts des ersten Listeneintrags vor/nach dem Toggle statt globaler Button-Textsuche).
- Builds wurden bewusst sequenziell (nicht parallel) durchgeführt — Testhost ist eine 4-Kern-Box, die bereits zahlreiche andere Next.js-Instanzen anderer Boards/Workdirs laufen hatte.
## Nicht Bestandteil dieser Kachel
- Core `SHL-01` WCAG-Basis-Komponenten selbst — nur deren Nutzung wurde geprüft (hier: nicht betroffen, der Befund liegt in modul-eigenem Code außerhalb der SHL-Komponenten).
- Behebung des unter „Befund" dokumentierten Kontrastverstoßes — liegt bei `VOR-05` (`web/workflow-cases`).
## Reproduktion
```
QA08_DSN='postgres://<user>:<pw>@127.0.0.1:5432/<wegwerf-db>' QA08_IDS_FILE=/tmp/qa08-ids.json \
go run ./workflow/cmd/qa08-seed
NEXARCH_WORKFLOW_APP_TENANT_DSN='postgres://...' ./workflow-app # 127.0.0.1:9600
cd web/<app> && WORKFLOW_APPROVALS_API_URL=http://127.0.0.1:9600 npx next build && \
WORKFLOW_APPROVALS_API_URL=http://127.0.0.1:9600 PORT=<port> npx next start -p <port>
# analog mit TASK_BOARD_API_URL / ROUTING_RULES_API_URL / WORKFLOW_CASES_API_URL
A11Y_CHROME=/usr/bin/google-chrome A11Y_NODE_MODULES=/usr/lib/node_modules/pa11y/node_modules \
A11Y_PAGES=pages.txt A11Y_OUT=report.json A11Y_TREE_DIR=tree node web/a11y-audit/audit.js
A11Y_CHROME=/usr/bin/google-chrome A11Y_NODE_MODULES=/usr/lib/node_modules/pa11y/node_modules \
A11Y_FLOW=approvals|tasks|rules|cases A11Y_WEB_URL=http://127.0.0.1:<port> \
node web/a11y-audit/qa08wf-keyboard-flows.js
```
Kein Merge, kein Deploy.
@@ -0,0 +1,52 @@
RootWebArea: "NEXARCH Routing-Regeln"
main
heading: "Routing-Regel-Editor"
heading: "Routing-Regeln"
form
heading: "Neue Regel anlegen"
StaticText: "Name"
textbox: "Name"
StaticText: "Priorität"
spinbutton: "Priorität"
StaticText: "Bedingung: Feld"
textbox: "Bedingung: Feld"
StaticText: "Bedingung: Operator"
combobox: "Bedingung: Operator" = gleich [expanded=false]
option: "gleich" [selected]
option: "enthält"
option: "in Liste"
StaticText: "Bedingung: Wert"
textbox: "Bedingung: Wert"
StaticText: "Aktion: Typ"
combobox: "Aktion: Typ" = An Prozess routen [expanded=false]
option: "An Prozess routen" [selected]
option: "An Person routen"
StaticText: "Aktion: Ziel"
textbox: "Aktion: Ziel" [fokus]
button: "Regel anlegen"
StaticText: "Vertraege an Rechtsabteilung"
StaticText: " "
StaticText: "(Prioritaet "
StaticText: "20"
StaticText: ")"
StaticText: "Aktiv"
button: "Vertraege an Rechtsabteilung nach oben verschieben"
button: "Vertraege an Rechtsabteilung nach unten verschieben"
button: "Deaktivieren"
StaticText: "Rechnungen an Buchhaltung"
StaticText: " "
StaticText: "(Prioritaet "
StaticText: "10"
StaticText: ")"
StaticText: "Aktiv"
button: "Rechnungen an Buchhaltung nach oben verschieben"
button: "Rechnungen an Buchhaltung nach unten verschieben"
button: "Deaktivieren"
heading: "Regel testen"
StaticText: "Beispielobjekt (JSON)"
textbox: "Beispielobjekt (JSON)" = {"object_type": "invoice"}
StaticText: "Bisherige Zuordnungen (optional, kommagetrennt)"
textbox: "Bisherige Zuordnungen (optional, kommagetrennt)"
button: "Testlauf starten"
status
alert
@@ -0,0 +1,45 @@
RootWebArea: "NEXARCH Aufgaben"
main
heading: "Aufgaben"
tab: "Liste" [selected]
tab: "Kanban" [fokus]
StaticText: "Prioritaet"
combobox: "Prioritaet" = Alle [expanded=false]
option: "Alle" [selected]
option: "Niedrig"
option: "Normal"
option: "Hoch"
StaticText: "Faelligkeit"
combobox: "Faelligkeit" = Alle [expanded=false]
option: "Alle" [selected]
option: "Ueberfaellig"
option: "Naechste 7 Tage"
option: "Ohne Faelligkeit"
paragraph
StaticText: "3"
StaticText: " von "
StaticText: "3"
StaticText: " Aufgaben"
StaticText: "Aufgaben"
StaticText: "Titel"
StaticText: "Zustaendig"
StaticText: "Prioritaet"
StaticText: "Faelligkeit"
StaticText: "Status"
StaticText: "Dokument a11y-doc-1 pruefen"
StaticText: "pruefer.lea"
StaticText: "Hoch"
StaticText: "18.10.2026"
StaticText: "Offen"
StaticText: "Rueckruf Kunde Nord"
StaticText: "pruefer.lea"
StaticText: "Normal"
StaticText: "–"
StaticText: "Offen"
StaticText: "Wiedervorlage 2026-10-17"
StaticText: "pruefer.lea"
StaticText: "Normal"
StaticText: "–"
StaticText: "Offen"
status
alert
@@ -0,0 +1,21 @@
RootWebArea: "NEXARCH Genehmigungen"
main
heading: "Meine Freigaben"
StaticText: "Rechnung Re. 2026-0042 freigeben"
StaticText: "Vorgang: "
StaticText: "2bc87be6-4ee7-4049-a6d8-77970e8ce228"
StaticText: "Begründung"
textbox: "Begründung für Rechnung Re. 2026-0042 freigeben"
button: "Freigeben" [fokus]
button: "Ablehnen"
button: "Zurückgeben"
StaticText: "Vertragsaenderung Kunde Nord pruefen"
StaticText: "Vorgang: "
StaticText: "365e2a73-2b37-4a07-8367-429716de59af"
StaticText: "Begründung"
textbox: "Begründung für Vertragsaenderung Kunde Nord pruefen"
button: "Freigeben"
button: "Ablehnen"
button: "Zurückgeben"
status
alert
@@ -0,0 +1,16 @@
RootWebArea: "NEXARCH Akten"
main
heading: "Akten & Wiedervorlagen"
tab: "Akten" [selected]
tab: "Wiedervorlagen (Kanban)"
StaticText: "Status"
textbox: "Status"
StaticText: "open, in_progress oder closed"
StaticText: "Verantwortlicher"
textbox: "Verantwortlicher"
button: "QA08-2026-0002 · in_progress · pruefer.lea"
button: "QA08-2026-0001 · open · pruefer.lea"
generic
StaticText: "Akte auswaehlen, um Vermerke, Wiedervorlagen und verknuepfte Objekte zu sehen."
status
alert
@@ -0,0 +1,16 @@
ok light http://127.0.0.1:4211/ axe: keine tab 2/2
ok dark http://127.0.0.1:4211/ axe: keine tab 2/2
ok light http://127.0.0.1:4211/?user=pruefer.lea axe: keine tab 4/4
ok dark http://127.0.0.1:4211/?user=pruefer.lea axe: keine tab 4/4
ok light http://127.0.0.1:4210/ axe: keine tab 2/2
ok dark http://127.0.0.1:4210/ axe: keine tab 2/2
ok light http://127.0.0.1:4210/?user=pruefer.lea axe: keine tab 4/4
ok dark http://127.0.0.1:4210/?user=pruefer.lea axe: keine tab 4/4
ok light http://127.0.0.1:4213/ axe: keine tab 2/2
ok dark http://127.0.0.1:4213/ axe: keine tab 2/2
ok light http://127.0.0.1:4213/?user=pruefer.lea axe: keine tab 15/15
ok dark http://127.0.0.1:4213/?user=pruefer.lea axe: keine tab 15/15
ok light http://127.0.0.1:4212/ axe: keine tab 2/2
ok dark http://127.0.0.1:4212/ axe: keine tab 2/2
ok light http://127.0.0.1:4212/?user=pruefer.lea axe: keine tab 6/6
ok dark http://127.0.0.1:4212/?user=pruefer.lea axe: color-contrast(serious,6) tab 6/6
@@ -0,0 +1,802 @@
[
{
"url": "http://127.0.0.1:4211/",
"scheme": "light",
"axe": {
"violations": [],
"incomplete": [],
"passes": 20
},
"alert": null,
"contrast": [],
"layout": {
"horizontalScroll": false,
"clipped": [],
"missingKeys": 0,
"lang": "de",
"tables": [],
"overflowing": []
},
"roles": {
"RootWebArea": 1,
"none": 3,
"main": 1,
"heading": 1,
"StaticText": 4,
"InlineTextBox": 4,
"form": 1,
"LabelText": 1,
"textbox": 1,
"generic": 2,
"paragraph": 1,
"button": 1,
"status": 1,
"alert": 1
},
"tableCount": 0,
"keyboard": {
"interactive": 2,
"reached": 2,
"missing": 0,
"noIndicator": [],
"trapped": null
}
},
{
"url": "http://127.0.0.1:4211/",
"scheme": "dark",
"axe": {
"violations": [],
"incomplete": [],
"passes": 20
},
"alert": null,
"contrast": [],
"layout": {
"horizontalScroll": false,
"clipped": [],
"missingKeys": 0,
"lang": "de",
"tables": [],
"overflowing": []
},
"roles": {
"RootWebArea": 1,
"none": 3,
"main": 1,
"heading": 1,
"StaticText": 4,
"InlineTextBox": 4,
"form": 1,
"LabelText": 1,
"textbox": 1,
"generic": 2,
"paragraph": 1,
"button": 1,
"status": 1,
"alert": 1
},
"tableCount": 0,
"keyboard": {
"interactive": 2,
"reached": 2,
"missing": 0,
"noIndicator": [],
"trapped": null
}
},
{
"url": "http://127.0.0.1:4211/?user=pruefer.lea",
"scheme": "light",
"axe": {
"violations": [],
"incomplete": [],
"passes": 21
},
"alert": null,
"contrast": [],
"layout": {
"horizontalScroll": false,
"clipped": [],
"missingKeys": 0,
"lang": "de",
"tables": [],
"overflowing": []
},
"roles": {
"RootWebArea": 1,
"none": 4,
"main": 1,
"heading": 1,
"StaticText": 15,
"InlineTextBox": 15,
"list": 1,
"listitem": 2,
"paragraph": 4,
"LabelText": 2,
"textbox": 2,
"generic": 3,
"button": 6,
"status": 1,
"alert": 1
},
"tableCount": 0,
"keyboard": {
"interactive": 4,
"reached": 4,
"missing": 0,
"noIndicator": [],
"trapped": null
}
},
{
"url": "http://127.0.0.1:4211/?user=pruefer.lea",
"scheme": "dark",
"axe": {
"violations": [],
"incomplete": [],
"passes": 21
},
"alert": null,
"contrast": [],
"layout": {
"horizontalScroll": false,
"clipped": [],
"missingKeys": 0,
"lang": "de",
"tables": [],
"overflowing": []
},
"roles": {
"RootWebArea": 1,
"none": 4,
"main": 1,
"heading": 1,
"StaticText": 15,
"InlineTextBox": 15,
"list": 1,
"listitem": 2,
"paragraph": 4,
"LabelText": 2,
"textbox": 2,
"generic": 3,
"button": 6,
"status": 1,
"alert": 1
},
"tableCount": 0,
"keyboard": {
"interactive": 4,
"reached": 4,
"missing": 0,
"noIndicator": [],
"trapped": null
}
},
{
"url": "http://127.0.0.1:4210/",
"scheme": "light",
"axe": {
"violations": [],
"incomplete": [],
"passes": 20
},
"alert": null,
"contrast": [],
"layout": {
"horizontalScroll": false,
"clipped": [],
"missingKeys": 0,
"lang": "de",
"tables": [],
"overflowing": []
},
"roles": {
"RootWebArea": 1,
"none": 3,
"main": 1,
"heading": 1,
"StaticText": 4,
"InlineTextBox": 4,
"form": 1,
"LabelText": 1,
"textbox": 1,
"generic": 2,
"paragraph": 1,
"button": 1,
"status": 1,
"alert": 1
},
"tableCount": 0,
"keyboard": {
"interactive": 2,
"reached": 2,
"missing": 0,
"noIndicator": [],
"trapped": null
}
},
{
"url": "http://127.0.0.1:4210/",
"scheme": "dark",
"axe": {
"violations": [],
"incomplete": [],
"passes": 20
},
"alert": null,
"contrast": [],
"layout": {
"horizontalScroll": false,
"clipped": [],
"missingKeys": 0,
"lang": "de",
"tables": [],
"overflowing": []
},
"roles": {
"RootWebArea": 1,
"none": 3,
"main": 1,
"heading": 1,
"StaticText": 4,
"InlineTextBox": 4,
"form": 1,
"LabelText": 1,
"textbox": 1,
"generic": 2,
"paragraph": 1,
"button": 1,
"status": 1,
"alert": 1
},
"tableCount": 0,
"keyboard": {
"interactive": 2,
"reached": 2,
"missing": 0,
"noIndicator": [],
"trapped": null
}
},
{
"url": "http://127.0.0.1:4210/?user=pruefer.lea",
"scheme": "light",
"axe": {
"violations": [],
"incomplete": [],
"passes": 24
},
"alert": null,
"contrast": [],
"layout": {
"horizontalScroll": false,
"clipped": [],
"missingKeys": 0,
"lang": "de",
"tables": [],
"overflowing": []
},
"roles": {
"RootWebArea": 1,
"none": 5,
"main": 1,
"heading": 1,
"StaticText": 30,
"InlineTextBox": 30,
"tablist": 1,
"tab": 2,
"generic": 3,
"LabelText": 2,
"combobox": 2,
"MenuListPopup": 2,
"option": 8,
"paragraph": 1,
"table": 1,
"caption": 1,
"rowgroup": 1,
"row": 4,
"columnheader": 5,
"cell": 15,
"status": 1,
"alert": 1
},
"tableCount": 1,
"keyboard": {
"interactive": 4,
"reached": 4,
"missing": 0,
"noIndicator": [],
"trapped": null
}
},
{
"url": "http://127.0.0.1:4210/?user=pruefer.lea",
"scheme": "dark",
"axe": {
"violations": [],
"incomplete": [],
"passes": 24
},
"alert": null,
"contrast": [],
"layout": {
"horizontalScroll": false,
"clipped": [],
"missingKeys": 0,
"lang": "de",
"tables": [],
"overflowing": []
},
"roles": {
"RootWebArea": 1,
"none": 5,
"main": 1,
"heading": 1,
"StaticText": 30,
"InlineTextBox": 30,
"tablist": 1,
"tab": 2,
"generic": 3,
"LabelText": 2,
"combobox": 2,
"MenuListPopup": 2,
"option": 8,
"paragraph": 1,
"table": 1,
"caption": 1,
"rowgroup": 1,
"row": 4,
"columnheader": 5,
"cell": 15,
"status": 1,
"alert": 1
},
"tableCount": 1,
"keyboard": {
"interactive": 4,
"reached": 4,
"missing": 0,
"noIndicator": [],
"trapped": null
}
},
{
"url": "http://127.0.0.1:4213/",
"scheme": "light",
"axe": {
"violations": [],
"incomplete": [],
"passes": 20
},
"alert": null,
"contrast": [],
"layout": {
"horizontalScroll": false,
"clipped": [],
"missingKeys": 0,
"lang": "de",
"tables": [],
"overflowing": []
},
"roles": {
"RootWebArea": 1,
"none": 3,
"main": 1,
"heading": 1,
"StaticText": 4,
"InlineTextBox": 4,
"form": 1,
"LabelText": 1,
"textbox": 1,
"generic": 2,
"paragraph": 1,
"button": 1,
"status": 1,
"alert": 1
},
"tableCount": 0,
"keyboard": {
"interactive": 2,
"reached": 2,
"missing": 0,
"noIndicator": [],
"trapped": null
}
},
{
"url": "http://127.0.0.1:4213/",
"scheme": "dark",
"axe": {
"violations": [],
"incomplete": [],
"passes": 20
},
"alert": null,
"contrast": [],
"layout": {
"horizontalScroll": false,
"clipped": [],
"missingKeys": 0,
"lang": "de",
"tables": [],
"overflowing": []
},
"roles": {
"RootWebArea": 1,
"none": 3,
"main": 1,
"heading": 1,
"StaticText": 4,
"InlineTextBox": 4,
"form": 1,
"LabelText": 1,
"textbox": 1,
"generic": 2,
"paragraph": 1,
"button": 1,
"status": 1,
"alert": 1
},
"tableCount": 0,
"keyboard": {
"interactive": 2,
"reached": 2,
"missing": 0,
"noIndicator": [],
"trapped": null
}
},
{
"url": "http://127.0.0.1:4213/?user=pruefer.lea",
"scheme": "light",
"axe": {
"violations": [],
"incomplete": [
{
"id": "color-contrast",
"nodes": 2,
"detail": [
{
"target": "li:nth-child(1) > div:nth-child(2) > button[type=\"button\"]:nth-child(2)",
"html": "<button type=\"button\" aria-label=\"Vertraege an Rechtsabteilung nach unten verschieben\">↓</button>",
"message": "Element content contains only non-text characters"
},
{
"target": "li:nth-child(2) > div:nth-child(2) > button[type=\"button\"]:nth-child(1)",
"html": "<button type=\"button\" aria-label=\"Rechnungen an Buchhaltung nach oben verschieben\">↑</button>",
"message": "Element content contains only non-text characters"
}
]
}
],
"passes": 23
},
"alert": null,
"contrast": [
{
"target": "li:nth-child(1) > div:nth-child(2) > button[type=\"button\"]:nth-child(2)",
"ratio": 18.26,
"size": "13.3333px",
"weight": "400",
"fg": "rgb(0, 0, 0)",
"bg": "rgb(239, 239, 239)"
},
{
"target": "li:nth-child(2) > div:nth-child(2) > button[type=\"button\"]:nth-child(1)",
"ratio": 18.26,
"size": "13.3333px",
"weight": "400",
"fg": "rgb(0, 0, 0)",
"bg": "rgb(239, 239, 239)"
}
],
"layout": {
"horizontalScroll": false,
"clipped": [],
"missingKeys": 0,
"lang": "de",
"tables": [],
"overflowing": []
},
"roles": {
"RootWebArea": 1,
"none": 10,
"main": 1,
"heading": 4,
"StaticText": 35,
"InlineTextBox": 35,
"form": 1,
"LabelText": 9,
"textbox": 6,
"generic": 9,
"spinbutton": 1,
"combobox": 2,
"MenuListPopup": 2,
"option": 5,
"button": 8,
"list": 1,
"listitem": 2,
"paragraph": 4,
"status": 1,
"alert": 1
},
"tableCount": 0,
"keyboard": {
"interactive": 15,
"reached": 15,
"missing": 0,
"noIndicator": [],
"trapped": null
}
},
{
"url": "http://127.0.0.1:4213/?user=pruefer.lea",
"scheme": "dark",
"axe": {
"violations": [],
"incomplete": [
{
"id": "color-contrast",
"nodes": 2,
"detail": [
{
"target": "li:nth-child(1) > div:nth-child(2) > button[type=\"button\"]:nth-child(2)",
"html": "<button type=\"button\" aria-label=\"Vertraege an Rechtsabteilung nach unten verschieben\">↓</button>",
"message": "Element content contains only non-text characters"
},
{
"target": "li:nth-child(2) > div:nth-child(2) > button[type=\"button\"]:nth-child(1)",
"html": "<button type=\"button\" aria-label=\"Rechnungen an Buchhaltung nach oben verschieben\">↑</button>",
"message": "Element content contains only non-text characters"
}
]
}
],
"passes": 23
},
"alert": null,
"contrast": [
{
"target": "li:nth-child(1) > div:nth-child(2) > button[type=\"button\"]:nth-child(2)",
"ratio": 18.26,
"size": "13.3333px",
"weight": "400",
"fg": "rgb(0, 0, 0)",
"bg": "rgb(239, 239, 239)"
},
{
"target": "li:nth-child(2) > div:nth-child(2) > button[type=\"button\"]:nth-child(1)",
"ratio": 18.26,
"size": "13.3333px",
"weight": "400",
"fg": "rgb(0, 0, 0)",
"bg": "rgb(239, 239, 239)"
}
],
"layout": {
"horizontalScroll": false,
"clipped": [],
"missingKeys": 0,
"lang": "de",
"tables": [],
"overflowing": []
},
"roles": {
"RootWebArea": 1,
"none": 10,
"main": 1,
"heading": 4,
"StaticText": 35,
"InlineTextBox": 35,
"form": 1,
"LabelText": 9,
"textbox": 6,
"generic": 9,
"spinbutton": 1,
"combobox": 2,
"MenuListPopup": 2,
"option": 5,
"button": 8,
"list": 1,
"listitem": 2,
"paragraph": 4,
"status": 1,
"alert": 1
},
"tableCount": 0,
"keyboard": {
"interactive": 15,
"reached": 15,
"missing": 0,
"noIndicator": [],
"trapped": null
}
},
{
"url": "http://127.0.0.1:4212/",
"scheme": "light",
"axe": {
"violations": [],
"incomplete": [],
"passes": 20
},
"alert": null,
"contrast": [],
"layout": {
"horizontalScroll": false,
"clipped": [],
"missingKeys": 0,
"lang": "de",
"tables": [],
"overflowing": []
},
"roles": {
"RootWebArea": 1,
"none": 3,
"main": 1,
"heading": 1,
"StaticText": 4,
"InlineTextBox": 4,
"form": 1,
"LabelText": 1,
"textbox": 1,
"generic": 2,
"paragraph": 1,
"button": 1,
"status": 1,
"alert": 1
},
"tableCount": 0,
"keyboard": {
"interactive": 2,
"reached": 2,
"missing": 0,
"noIndicator": [],
"trapped": null
}
},
{
"url": "http://127.0.0.1:4212/",
"scheme": "dark",
"axe": {
"violations": [],
"incomplete": [],
"passes": 20
},
"alert": null,
"contrast": [],
"layout": {
"horizontalScroll": false,
"clipped": [],
"missingKeys": 0,
"lang": "de",
"tables": [],
"overflowing": []
},
"roles": {
"RootWebArea": 1,
"none": 3,
"main": 1,
"heading": 1,
"StaticText": 4,
"InlineTextBox": 4,
"form": 1,
"LabelText": 1,
"textbox": 1,
"generic": 2,
"paragraph": 1,
"button": 1,
"status": 1,
"alert": 1
},
"tableCount": 0,
"keyboard": {
"interactive": 2,
"reached": 2,
"missing": 0,
"noIndicator": [],
"trapped": null
}
},
{
"url": "http://127.0.0.1:4212/?user=pruefer.lea",
"scheme": "light",
"axe": {
"violations": [],
"incomplete": [],
"passes": 24
},
"alert": null,
"contrast": [],
"layout": {
"horizontalScroll": false,
"clipped": [],
"missingKeys": 0,
"lang": "de",
"tables": [],
"overflowing": []
},
"roles": {
"RootWebArea": 1,
"none": 5,
"main": 1,
"heading": 1,
"StaticText": 17,
"InlineTextBox": 18,
"tablist": 1,
"tab": 2,
"generic": 6,
"LabelText": 2,
"textbox": 2,
"paragraph": 2,
"list": 1,
"listitem": 2,
"button": 2,
"status": 1,
"alert": 1
},
"tableCount": 0,
"keyboard": {
"interactive": 6,
"reached": 6,
"missing": 0,
"noIndicator": [],
"trapped": null
}
},
{
"url": "http://127.0.0.1:4212/?user=pruefer.lea",
"scheme": "dark",
"axe": {
"violations": [
{
"id": "color-contrast",
"impact": "serious",
"help": "Elements must meet minimum color contrast ratio thresholds",
"nodes": 6,
"sample": "<button type=\"button\" style=\"width: 100%; text-align: left; border: 1px solid var(--shl-color-border, #d0d5dd); border-r"
}
],
"incomplete": [],
"passes": 24
},
"alert": null,
"contrast": [],
"layout": {
"horizontalScroll": false,
"clipped": [],
"missingKeys": 0,
"lang": "de",
"tables": [],
"overflowing": []
},
"roles": {
"RootWebArea": 1,
"none": 5,
"main": 1,
"heading": 1,
"StaticText": 17,
"InlineTextBox": 18,
"tablist": 1,
"tab": 2,
"generic": 6,
"LabelText": 2,
"textbox": 2,
"paragraph": 2,
"list": 1,
"listitem": 2,
"button": 2,
"status": 1,
"alert": 1
},
"tableCount": 0,
"keyboard": {
"interactive": 6,
"reached": 6,
"missing": 0,
"noIndicator": [],
"trapped": null
}
}
]
@@ -0,0 +1,6 @@
ok axe: Freigabenliste (Ausgangszustand) (0 Verstoesse)
ok Mindestens eine offene Freigabe vorhanden n=2
ok Eintrag verschwindet nach Tastatur-Freigabe aus der Liste
ok Erfolgsmeldung 'Freigegeben.' erscheint
ok axe: Nach Tastatur-Freigabe (0 Verstoesse)
ok kein Mausereignis im gesamten Ablauf mausereignisse=0
@@ -0,0 +1,5 @@
ok axe: Aktenuebersicht (Ausgangszustand) (0 Verstoesse)
ok Akte per Tastatur ausgewaehlt (aria-current gesetzt)
ok axe: Aktenuebersicht mit Detailbereich (0 Verstoesse)
ok axe: Wiedervorlagen-Kanban (0 Verstoesse)
ok kein Mausereignis im gesamten Ablauf mausereignisse=0
@@ -0,0 +1,5 @@
ok axe: Routing-Regel-Editor (Ausgangszustand) (0 Verstoesse)
ok Mindestens eine Regel mit Status sichtbar
ok Regel-Status aendert sich nach Tastatur-Toggle vorher-Label='Aktivieren'
ok axe: Nach Tastatur-Toggle (0 Verstoesse)
ok kein Mausereignis im gesamten Ablauf mausereignisse=0
@@ -0,0 +1,7 @@
ok axe: Aufgabenliste (ungefiltert) (0 Verstoesse)
ok Live-Zaehler 'X von Y Aufgaben' sichtbar 3 von 3 Aufgaben
ok Zaehler aktualisiert sich nach Tastatur-Filterwahl 3 von 3 Aufgaben -> 1 von 3 Aufgaben
ok axe: Aufgabenliste (gefiltert nach Prioritaet=high) (0 Verstoesse)
ok Wechsel zur Kanban-Ansicht per Tastatur
ok axe: Kanban-Ansicht (0 Verstoesse)
ok kein Mausereignis im gesamten Ablauf mausereignisse=0