Files
lernbar/scripts/screenshots.ts
T
Florian WildeandClaude Opus 5.5 44e6c8bb79 Module abschaltbar machen
Merkkarten, Praxisaufgaben, Checklisten, Berichtsheft, Lernzielkontrollen
und Feedbackgespraech lassen sich unter Verwaltung -> Module fuer die ganze
Lern-Bar ab- und einschalten. Wochenplan, Handouts, Glossar, Auswertung und
Verwaltung bleiben immer an.

Gesperrt wird an drei Stellen, nicht im Layout (Next rendert Layouts beim
Seitenwechsel darunter nicht neu): Seiten mit erfordereModul, Routen fuer
PDF, Anhaenge und Lernstand mit modulAn, alle 37 Serveraktionen der Module
mit pruefeModul. Die Anmeldepruefung kommt jeweils zuerst, damit ein
Unangemeldeter nicht am 404 abliest, was abgeschaltet ist.

Ein abgeschaltetes Modul traegt nirgends bei: Wochenplan, Fortschritt,
Was-wartet, Mein Stand, Ergebnisverlauf, Rahmenplanabdeckung, Vorschlaege
fuers Berichtsheft, Karten im Handout, Kurzbefehle im Manifest. Die
Lernstand-Schnittstelle antwortet mit 410, das Geraet verwirft daraufhin
seine Offline-Warteschlange. Abschalten loescht nichts.

Gespeichert in modul_einstellung (Migration 0010); fehlt die Zeile, ist das
Modul an, eine bestehende Datenbank verhaelt sich also wie vorher.

12 Tests fuer die Regeln, Abschnitt A8 im Abnahmelauf, Anleitung Kapitel 9
mit Bildschirmfoto, README, E43.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:58:32 +02:00

236 lines
7.7 KiB
TypeScript
Raw Permalink 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.
/**
* Nimmt die Bildschirmfotos für die Anleitung auf.
*
* npm run build && npm run start (in einer zweiten Konsole)
* npm run screenshots
*
* Die Bilder landen unter docs/bilder/ und werden bei jedem Lauf überschrieben.
* So veralten sie nicht – wer die Oberfläche ändert, lässt das Skript neu laufen.
*
* Umgebungsvariablen:
* LERNBAR_BASIS Adresse der laufenden Anwendung (Vorgabe http://localhost:3000)
* LERNBAR_AUSBILDER E-Mail des Ausbilderkontos
* LERNBAR_AZUBI E-Mail des Azubikontos
* LERNBAR_PASSWORT_* zugehörige Passwörter
*/
import fs from 'node:fs';
import path from 'node:path';
import { chromium, type Page } from '@playwright/test';
const BASIS = process.env.LERNBAR_BASIS ?? 'http://localhost:3000';
const ZIEL = path.join(process.cwd(), 'docs', 'bilder');
const AUSBILDER = {
email: process.env.LERNBAR_AUSBILDER ?? 'ausbilder@betrieb.de',
passwort: process.env.LERNBAR_PASSWORT_AUSBILDER ?? 'ausbilder-start',
};
const AZUBI = {
email: process.env.LERNBAR_AZUBI ?? 'azubi@betrieb.de',
passwort: process.env.LERNBAR_PASSWORT_AZUBI ?? 'azubi-start-2026',
};
const SCHREIBTISCH = { width: 1280, height: 900 };
const HANDY = { width: 390, height: 844 };
async function anmelden(seite: Page, konto: { email: string; passwort: string }) {
await seite.goto(`${BASIS}/anmelden`);
await seite.waitForSelector('#email');
await seite.fill('#email', konto.email);
await seite.fill('#passwort', konto.passwort);
await seite.click('button[type=submit]');
await seite.waitForURL(`${BASIS}/`);
}
async function abmelden(seite: Page) {
await seite.click('button:has-text("Abmelden")');
await seite.waitForURL(/anmelden/);
}
async function bild(seite: Page, name: string, ganzeSeite = true) {
const datei = path.join(ZIEL, `${name}.png`);
await seite.screenshot({ path: datei, fullPage: ganzeSeite });
console.log(` ${path.relative(process.cwd(), datei)}`);
}
async function main() {
fs.mkdirSync(ZIEL, { recursive: true });
const browser = await chromium.launch();
const umgebung = await browser.newContext({ viewport: SCHREIBTISCH, locale: 'de-DE', colorScheme: 'light' });
const seite = await umgebung.newPage();
console.log('Bildschirmfotos werden aufgenommen …');
// --- Anmeldung ---
await seite.goto(`${BASIS}/anmelden`);
await seite.waitForSelector('#email');
await bild(seite, '01-anmelden');
// --- Ausbilder ---
await anmelden(seite, AUSBILDER);
await bild(seite, '02-ausbilder-ueberblick');
await seite.goto(`${BASIS}/verwaltung`);
await seite.waitForSelector('#neu-name');
await bild(seite, '03-verwaltung');
// Module: für das Bild ein Modul abschalten, danach den alten Stand herstellen.
const umschalten = async (modul: string) => {
await seite.goto(`${BASIS}/verwaltung/module`);
await seite.click(`form:has(input[name="modul"][value="${modul}"]) button[type=submit]`);
await seite.waitForLoadState('networkidle');
};
await umschalten('berichtsheft');
await seite.goto(`${BASIS}/verwaltung/module`);
await seite.waitForSelector('#modul-karten');
await bild(seite, '25-module');
await umschalten('berichtsheft');
await seite.goto(`${BASIS}/wochenplan`);
await seite.waitForSelector('h1');
await bild(seite, '04-wochenplan');
await seite.goto(`${BASIS}/aufgaben`);
await seite.waitForSelector('h1');
const abnahme = await seite.$('a[href*="/aufgaben/1.1?azubi="]');
if (abnahme) {
await abnahme.click();
await seite.waitForSelector('#abnahme');
await bild(seite, '05-aufgabe-abnahme');
}
// --- Auswertung und Feedbackgespräch (Ausbildersicht) ---
await seite.goto(`${BASIS}/auswertung`);
await seite.waitForSelector('h1');
await bild(seite, '06-auswertung');
const luecke = await seite.$('[data-stufe="ohne_inhalt"]');
if (luecke) {
await luecke.scrollIntoViewIfNeeded();
await bild(seite, '07-rahmenplanabdeckung', false);
}
await seite.goto(`${BASIS}/feedback`);
await seite.waitForSelector('h1');
await bild(seite, '08-feedback-liste');
const gespraech = await seite.$('.gespraechszeile');
if (gespraech) {
await gespraech.click();
await seite.waitForSelector('#bogen');
await bild(seite, '09-feedbackgespraech');
}
await abmelden(seite);
// --- Azubi ---
await anmelden(seite, AZUBI);
await bild(seite, '10-azubi-woche');
await seite.goto(`${BASIS}/handouts`);
await seite.waitForSelector('h1');
await bild(seite, '11-handouts-liste');
await seite.goto(`${BASIS}/handouts?suche=subnetting`);
await seite.waitForSelector('h2');
await bild(seite, '12-handouts-suche');
await seite.goto(`${BASIS}/handouts/N3`);
await seite.waitForSelector('h1');
await seite.click('.pruefungsfrage > summary');
await bild(seite, '13-handout-n3');
await seite.goto(`${BASIS}/karten`);
await seite.waitForSelector('h1');
await bild(seite, '14-merkkarten');
await seite.goto(`${BASIS}/aufgaben`);
await seite.waitForSelector('h1');
await bild(seite, '16-aufgaben-liste');
await seite.goto(`${BASIS}/aufgaben/1.1`);
await seite.waitForSelector('h1');
await bild(seite, '17-aufgabe-azubi');
await seite.goto(`${BASIS}/checklisten`);
await seite.waitForSelector('h1');
await bild(seite, '18-checklisten');
await seite.goto(`${BASIS}/berichtsheft`);
await seite.waitForSelector('h1');
await bild(seite, '21-berichtsheft');
await seite.goto(`${BASIS}/kontrollen`);
await seite.waitForSelector('h1');
await bild(seite, '23-kontrollen');
await seite.goto(`${BASIS}/kontrollen/woche-03`);
await seite.waitForSelector('h1');
await bild(seite, '24-kontrolle-ergebnis');
// Einzelblatt und Checklisten-Durchlauf jeweils von ihrer eigenen Liste aus –
// vorher wurde hier auf der Kontrollseite gesucht, wo es sie gar nicht gibt.
await seite.goto(`${BASIS}/berichtsheft`);
await seite.waitForSelector('h1');
const blatt = await seite.$('.blattzeile-link');
if (blatt) {
await blatt.click();
await seite.waitForSelector('h1');
await bild(seite, '22-wochenblatt');
}
await seite.goto(`${BASIS}/checklisten`);
await seite.waitForSelector('h1');
const lauf = await seite.$('.laufzeile-haupt');
if (lauf) {
await lauf.click();
await seite.waitForSelector('.schrittliste');
await bild(seite, '19-checklisten-lauf');
}
await seite.goto(`${BASIS}/glossar`);
await seite.waitForSelector('h1');
await bild(seite, '15-glossar');
// --- Dunkle Darstellung ---
await seite.goto(`${BASIS}/handouts/N3`);
await seite.waitForSelector('button[aria-pressed]');
await seite.click('button[aria-pressed="false"]');
await seite.waitForTimeout(200);
await bild(seite, '20-dunkel');
await seite.click('button[aria-pressed="true"]');
// --- Handy ---
const handy = await browser.newContext({
viewport: HANDY,
locale: 'de-DE',
isMobile: true,
hasTouch: true,
deviceScaleFactor: 2,
});
const handySeite = await handy.newPage();
await anmelden(handySeite, AZUBI);
await handySeite.goto(`${BASIS}/handouts/N3`);
await handySeite.waitForSelector('h1');
await bild(handySeite, '30-handy-handout');
await handySeite.goto(`${BASIS}/karten/lernen`);
await handySeite.waitForSelector('.lernkarte, h1');
if (await handySeite.$('.lernkarte')) {
await bild(handySeite, '31-handy-lernmodus-frage', false);
await handySeite.tap('.lernkarte');
await handySeite.waitForSelector('.lernkarte-antwort');
await bild(handySeite, '32-handy-lernmodus-antwort', false);
}
await browser.close();
console.log('Fertig.');
}
main().catch((fehler) => {
console.error('Die Bildschirmfotos konnten nicht aufgenommen werden.');
console.error(fehler instanceof Error ? fehler.message : fehler);
console.error(`Läuft die Anwendung unter ${BASIS}? Stimmen LERNBAR_AUSBILDER und LERNBAR_AZUBI?`);
process.exit(1);
});