Die Abdeckung des Ausbildungsrahmenplans ist die Ansicht, die gegenueber
der IHK zaehlt. Sie unterscheidet vier Lagen statt "erledigt/offen":
vermittelt eine abgenommene Aufgabe oder ein abgeschlossener
Checklisten-Durchlauf, oder zwei gelesene Handouts
angefangen etwas erledigt, aber noch nicht genug
geplant Inhalt vorhanden, nichts davon bearbeitet
ohne Inhalt zu dieser Position gibt es im Lehrgang gar nichts
Die vierte Lage ist die wichtigste: Sie ist keine Luecke beim Azubi,
sondern eine im Lehrgang, und wird getrennt ausgewiesen. Die Regel steht
als reine Funktion in src/lib/auswertung/abdeckung.ts, mit 17 Tests.
Das Feedbackgespraech laesst beide Seiten getrennt bewerten. Wo sie um
zwei Stufen oder mehr auseinanderliegen, wird das hervorgehoben - in der
Ansicht, in der Uebersicht und im PDF, alle drei ueber dieselbe Funktion
liegtWeitAuseinander(). Nach beidseitiger Bestaetigung ist das Gespraech
unveraenderlich und wird nur noch gelesen.
Durchstich in scripts/pruefungen/baustein-9.mjs: alle fuenf Luecken
erkannt, getrennte Spalten, Sperre serverseitig, PDF nur mit Anmeldung,
ein fremder Azubi bekommt 404 - auch auf das PDF.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
224 lines
7.2 KiB
TypeScript
224 lines
7.2 KiB
TypeScript
/**
|
||
* 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');
|
||
|
||
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);
|
||
});
|