/** * Screenshots für das README aus dem gebauten Stand (`scripts/neu-starten.sh`, Port 3310), dunkles * Theme, 1440 px und 390 px. Ergebnis: docs/bilder/*.png * * pnpm exec tsx scripts/screenshots.ts [auftrag-kurzid] */ import { mkdirSync } from "node:fs"; import { chromium, type Page } from "@playwright/test"; const BASIS = process.env["SCREENSHOT_URL"] ?? "http://127.0.0.1:3310"; const BENUTZER = process.env["E2E_BENUTZER"] ?? "admin"; const PASSWORT = process.env["E2E_PASSWORT"] ?? "proxdeploy-dev-admin"; const ZIEL = "docs/bilder"; async function anmelden(seite: Page) { await seite.goto(`${BASIS}/anmelden`); await seite.getByLabel("Benutzername").fill(BENUTZER); await seite.getByLabel("Passwort").fill(PASSWORT); await seite.getByRole("button", { name: "Anmelden" }).click(); await seite.waitForURL((url) => !url.pathname.startsWith("/anmelden")); } async function bild(seite: Page, name: string, ganz = false) { await seite.waitForLoadState("networkidle"); // Blinkender Cursor und Spinner sollen nicht zufällig halb im Bild stehen. await seite.addStyleTag({ content: "*{animation:none!important;transition:none!important}" }); await seite.screenshot({ path: `${ZIEL}/${name}.png`, fullPage: ganz }); console.log(`${ZIEL}/${name}.png`); } async function main() { const auftrag = process.argv[2]; if (!auftrag) throw new Error("Kurz-ID eines erfolgreichen Auftrags angeben (für die Live-Ansicht)."); mkdirSync(ZIEL, { recursive: true }); const browser = await chromium.launch(); try { const kontext = await browser.newContext({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1, }); await kontext.addCookies([{ name: "pd_theme", value: "dunkel", url: BASIS }]); const seite = await kontext.newPage(); await anmelden(seite); await seite.goto(`${BASIS}/`); await bild(seite, "uebersicht"); await seite.goto(`${BASIS}/auftraege/${auftrag}`); await bild(seite, "live-ansicht"); await seite.goto(`${BASIS}/auftraege`); await bild(seite, "verlauf"); await seite.goto(`${BASIS}/server/demo`); await bild(seite, "server", true); await seite.goto(`${BASIS}/katalog`); await bild(seite, "katalog"); // Assistent bis zur Zusammenfassung – mit echtem Dry-Run gegen den Server. await seite.goto(`${BASIS}/ausrollen`); await seite.waitForLoadState("networkidle"); const weiter = async (bis: number) => { await seite.getByRole("button", { name: "Weiter" }).click(); await seite.getByText(`Schritt ${bis} von 9`).waitFor(); }; for (let s = 2; s <= 4; s++) await weiter(s); await seite.getByLabel("Name (Hostname)").fill("web01"); for (let s = 5; s <= 7; s++) await weiter(s); // Ohne hinterlegten Schlüssel einen angeben (öffentlicher Beispielschlüssel). const feld = seite.locator("textarea#schluessel"); if ((await feld.inputValue()) === "") await feld.fill( "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEFTdkCuqQ6mF7I8QzmkNaQpKj7S/I2+yuxWeYasJjLd florian@laptop", ); for (let s = 8; s <= 9; s++) await weiter(s); await seite.getByText("Geplante Proxmox-Aufrufe").waitFor({ timeout: 60_000 }); await bild(seite, "assistent-zusammenfassung"); const handy = await browser.newContext({ viewport: { width: 390, height: 844 }, deviceScaleFactor: 2 }); await handy.addCookies([{ name: "pd_theme", value: "dunkel", url: BASIS }]); const klein = await handy.newPage(); await anmelden(klein); await klein.goto(`${BASIS}/auftraege/${auftrag}`); await bild(klein, "handy-live-ansicht"); } finally { await browser.close(); } } await main();