Files
sshkeyvault/scripts/screenshots.ts
T
florian.wildeandClaude Opus 5.5 d1ba822a4a feat(auth): Anmeldeseiten, TOTP-Einrichtung und Bestaetigung (#004)
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>
2026-10-12 09:28:51 +02:00

98 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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();