Anmeldung, Einrichtung des zweiten Faktors fuer den Notfall-Admin, erneute Bestaetigung vor kritischen Aktionen und die Seite fuer fehlende Rechte. Die Huelle zeigt jetzt den echten Benutzer, und jede Seite prueft die Sitzung selbst; ein struktureller Test haelt das fest. Die Ablaeufe sind im gebauten Stand gegen den Mock-OIDC geprueft (pnpm test:e2e): Admin, Konto ohne Gruppe, Notfall-Admin mit TOTP. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
98 lines
3.7 KiB
TypeScript
98 lines
3.7 KiB
TypeScript
/**
|
||
* Screenshots der Oberfläche für Anleitung und Design-Abnahme (#002).
|
||
*
|
||
* pnpm build && pnpm start # erstes Terminal
|
||
* pnpm exec tsx scripts/screenshots.ts # zweites Terminal
|
||
*
|
||
* Umgebung:
|
||
* SSHKV_URL Basis-URL (Standard http://localhost:3400)
|
||
* SSHKV_BILDER Zielordner (Standard docs/anleitung/bilder)
|
||
* SSHKV_SEITEN Kommagetrennte Auswahl der Seitennamen (Standard: alle)
|
||
*
|
||
* Prüft nebenbei, dass keine Seite horizontal scrollt (Design-Vorgabe ab 390 px), und
|
||
* endet dann mit Fehlercode – so taugt das Skript auch als schneller Layout-Test.
|
||
*/
|
||
import { mkdir } from "node:fs/promises";
|
||
import { join } from "node:path";
|
||
import { chromium } from "@playwright/test";
|
||
|
||
const BASIS = (process.env["SSHKV_URL"] ?? "http://localhost:3400").replace(/\/$/, "");
|
||
const ZIEL = process.env["SSHKV_BILDER"] ?? join(process.cwd(), "docs/anleitung/bilder");
|
||
|
||
/** `oeffentlich`: ohne Anmeldung aufnehmen. Alle anderen nach Anmeldung ueber den Mock-OIDC (#004). */
|
||
const SEITEN: { name: string; pfad: string; oeffentlich?: boolean }[] = [
|
||
{ name: "anmelden", pfad: "/anmelden", oeffentlich: true },
|
||
{ name: "uebersicht", pfad: "/" },
|
||
{ name: "schluessel-detail", pfad: "/schluessel/beispiel" },
|
||
];
|
||
|
||
const ANSICHTEN = [
|
||
{ name: "desktop", width: 1440, height: 900 },
|
||
{ name: "mobil", width: 390, height: 844 },
|
||
];
|
||
|
||
async function main() {
|
||
const auswahl = process.env["SSHKV_SEITEN"]?.split(",").map((s) => s.trim());
|
||
const seiten = auswahl ? SEITEN.filter((s) => auswahl.includes(s.name)) : SEITEN;
|
||
await mkdir(ZIEL, { recursive: true });
|
||
|
||
const browser = await chromium.launch();
|
||
const fehler: string[] = [];
|
||
try {
|
||
for (const ansicht of ANSICHTEN) {
|
||
const kontext = await browser.newContext({
|
||
viewport: { width: ansicht.width, height: ansicht.height },
|
||
deviceScaleFactor: 1,
|
||
locale: "de-DE",
|
||
timezoneId: "Europe/Berlin",
|
||
colorScheme: "dark",
|
||
});
|
||
const seite = await kontext.newPage();
|
||
let angemeldet = false;
|
||
for (const s of seiten) {
|
||
if (!s.oeffentlich && !angemeldet) {
|
||
// Anmeldung ueber den Mock-OIDC aus docker-compose.test.yml, Rolle Admin.
|
||
await seite.goto(`${BASIS}/anmelden`);
|
||
await seite
|
||
.getByRole("link", { name: /anmelden/i })
|
||
.first()
|
||
.click();
|
||
await seite.locator('button[data-rolle="admin"]').click();
|
||
await seite.getByRole("button", { name: "Anmelden", exact: true }).click();
|
||
await seite.waitForURL(`${BASIS}/`);
|
||
angemeldet = true;
|
||
}
|
||
const antwort = await seite.goto(`${BASIS}${s.pfad}`, { waitUntil: "networkidle" });
|
||
if (!antwort?.ok()) {
|
||
fehler.push(`${s.pfad}: HTTP ${antwort?.status() ?? "keine Antwort"}`);
|
||
continue;
|
||
}
|
||
// Erst nach dem Laden der selbst gehosteten Schriften fotografieren, sonst
|
||
// stehen Fallback-Schriften im Bild.
|
||
await seite.evaluate(() => document.fonts.ready);
|
||
|
||
const ueberlauf = await seite.evaluate(
|
||
() => document.documentElement.scrollWidth - document.documentElement.clientWidth,
|
||
);
|
||
if (ueberlauf > 0) {
|
||
fehler.push(`${s.pfad} @ ${ansicht.width}px: ${ueberlauf}px horizontaler Überlauf`);
|
||
}
|
||
|
||
const datei = join(ZIEL, `${s.name}-${ansicht.name}.png`);
|
||
await seite.screenshot({ path: datei, fullPage: true });
|
||
console.log(`✓ ${datei}`);
|
||
}
|
||
await kontext.close();
|
||
}
|
||
} finally {
|
||
await browser.close();
|
||
}
|
||
|
||
if (fehler.length > 0) {
|
||
console.error(`\n${fehler.length} Problem(e):\n- ${fehler.join("\n- ")}`);
|
||
process.exitCode = 1;
|
||
}
|
||
}
|
||
|
||
await main();
|