QA-11: tastaturablaeufe ohne maus im echten browser
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
patrickandClaude Sonnet 5 committed 2026-09-19 07:14:38 +02:00
1 parent 5cf568e6c0
commit 5d78dbc4f7
1 file changed
+95
+95
View File
@@ -0,0 +1,95 @@
// QA-11: vollstaendige Ablaeufe nur mit der Tastatur (keine Mausereignisse) im
// echten Browser gegen die echten Handler (a11y-fixture). Jeder Ablauf prueft
// das Ergebnis ueber die API, nicht nur die Oberflaeche.
//
// A11Y_CHROME=... A11Y_API=http://127.0.0.1:8299 A11Y_BROWSER=http://127.0.0.1:4110 A11Y_IDS=ids.json node keyboard-flow.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 ids = JSON.parse(fs.readFileSync(process.env.A11Y_IDS, "utf8"));
const API = process.env.A11Y_API;
const BROWSER = process.env.A11Y_BROWSER;
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"), type: el.getAttribute("type"),
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 sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const getJSON = async (p) => (await fetch(API + p)).json();
const flows = {
async "Papierkorb: Dokument nur mit Tastatur wiederherstellen"(page) {
await page.goto(`${BROWSER}/trash`, { waitUntil: "networkidle0" });
const before = await page.evaluate(() => document.body.innerText.includes("alt.txt"));
if (!before) throw new Error("alt.txt nicht im Papierkorb sichtbar");
await tabTo(page, "Auswahl-Kontrollkaestchen der Zeile", (c) => c.tag === "INPUT" && c.type === "checkbox");
await page.keyboard.press("Space");
await tabTo(page, "Button Auswahl wiederherstellen", (c) => c.tag === "BUTTON" && /wiederherstell/i.test(c.text));
await page.keyboard.press("Enter");
await sleep(1500);
const gone = await page.evaluate(() => !document.body.innerText.includes("alt.txt"));
if (!gone) throw new Error("alt.txt nach Wiederherstellen noch im Papierkorb");
const docs = await getJSON(`/folders/${ids.folder_inbox}/documents`);
if (!docs.some((d) => d.id === ids.trashed_doc)) throw new Error("Dokument ist per API nicht wiederhergestellt");
},
async "Ordnerbaum: Ordner nur mit Tastatur auswaehlen, Dokumente erscheinen"(page) {
await page.goto(`${BROWSER}/browse`, { waitUntil: "networkidle0" });
await tabTo(page, "Ordnerbaum", (c) => c.role === "tree");
let opened = false;
for (let i = 0; i < 6 && !opened; i++) {
await page.keyboard.press("ArrowDown");
await page.keyboard.press("Enter");
await sleep(600);
opened = (await page.evaluate(() => document.querySelectorAll('[role="option"]').length)) > 0;
}
if (!opened) throw new Error("nach Tastaturauswahl erscheinen keine Dokumente in der Liste");
},
async "Metadaten: Wert nur mit Tastatur erfassen und speichern"(page) {
await page.goto(`${BROWSER}/documents/${ids.pdf_doc}/metadata`, { waitUntil: "networkidle0" });
await tabTo(page, "Betrag-Feld", (c) => c.tag === "INPUT" && c.type !== "checkbox" && !/tag/i.test(c.text));
await page.keyboard.type("12.5");
await tabTo(page, "Speichern", (c) => c.tag === "BUTTON" && /speicher|save/i.test(c.text));
await page.keyboard.press("Enter");
await sleep(1500);
const values = await getJSON(`/documents/${ids.pdf_doc}/metadata`);
if (!JSON.stringify(values).includes("12.5")) throw new Error(`Wert per API nicht gespeichert: ${JSON.stringify(values).slice(0, 200)}`);
},
};
(async () => {
const browser = await puppeteer.launch({ executablePath: process.env.A11Y_CHROME, args: ["--no-sandbox", "--disable-setuid-sandbox"] });
let failed = false;
const mouse = { events: 0 };
for (const [name, fn] of Object.entries(flows)) {
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);
});
try {
await fn(page);
const clicks = await page.evaluate(() => window.__mouse);
mouse.events += clicks;
console.log(`ok\t${name}\t(Mausereignisse: ${clicks})`);
} catch (e) {
failed = true;
console.log(`FEHLER\t${name}\t${e.message}`);
}
await page.close();
}
await browser.close();
process.exit(failed ? 1 : 0);
})();