Files
dogfather-universe/server/pruef-entwicklung-kacheln.mjs
T
DogFatherGitandClaude Opus 5 5b244ab7ec Die Personenkachel wird eine Standkarte -- mit den Aufgaben darauf
Filipe: "mach das bitte viel krasser und viel geiler. die aufgaben die
wir verteilen sollen da auch zu sehen sein und so. viel
uebersichtlicher, viel moderner. nicht so banal und einfach."

VORHER: Name, Rolle, ein flacher Balken und drei Zahlenkaesten. Auf
seinem Bildschirmfoto standen in fuenf von sechs Kacheln exakt
dieselben drei Zahlen (0 / 68 / 0) -- achtzehn Kaesten fuer eine
einzige Auskunft. Und die Aufgaben, die er eine Handbreit darunter
verteilt, kamen auf der Kachel derselben Person gar nicht vor.

JETZT drei Zonen in der Reihenfolge, in der man fragt:
  KOPF     Ring mit dem Anteil in der Mitte, Name, Rolle.
  AUFTRAG  Wie viele offen, wie viele ueberfaellig, und die naechste
           mit Namen und Frist ("seit gestern ueberfaellig").
  FUSS     "46 von 68 angesehen" -- und "verschieden gesehen" nur
           dann, wenn es dort etwas gibt.

Der Ring rechnet mit stroke-dasharray auf einem Kreis vom Umfang 100:
der Anteil in Prozent ist damit buchstaeblich die Strichlaenge. Keine
Ampelfarbe -- was "genug" ist, haengt davon ab, wie lange jemand dabei
ist. Gewarnt wird nur an einem Massstab, der nicht geraten ist: einer
ueberschrittenen Frist.

Die Aufgabenzeile wird aus derselben Liste gerechnet wie das
Verteil-Band und der Katalog (alleAufgabenV) und in aufgabenHolen()
nachgezogen -- an EINER Stelle, damit es keine gibt, die es vergisst.

GITTER STATT FLIESSREIHE: Bei sieben Leuten stand die letzte Kachel
allein in ihrer Zeile und wuchs auf 1160 px neben 379 px der anderen.
Eine Person sah dreimal so wichtig aus, weil die Teamgroesse ungerade
ist.

Drei Funde nebenbei, alle durch die neuen Messungen:

  - pruef-schritt verlangte seit dem 23.09. einen Sprung, der an dem
    Tag ABSICHTLICH entfernt wurde. Sie war seither rot, ohne dass
    etwas kaputt war. Neu gefasst auf den Sprung, den es noch gibt:
    den Weg von aussen ueber "?zeigen=person-...".
  - Und der war kaputt. Gesprungen wurde, waehrend in der Karte nur
    "wird geladen ..." stand -- die Seite war zu kurz zum Scrollen,
    der Browser klemmte bei 0 ab. Wer der Talentseite folgte, landete
    oben auf der Liste. Gesprungen wird jetzt, wenn die Karte steht.
  - Auf demselben Weg rief karte() das Vorlagenbrett, bevor es
    eingerichtet war ("zeigen() vor einrichten()"). Das Einrichten ist
    vorgezogen. Ueber "?zeigen=" ging vorher KEINE Pruefung.

Gemessen: pruef-entwicklung-kacheln 31 (vorher 16), pruef-schritt 68
(vorher 65), pruef-entwicklung 48, pruef-modi-katalog 133,
pruef-bewerbung-aufgaben 101, pruef-zuteilung, pruef-css-klassen --
alle gruen. Schriftgroessen unter 11,5 px: 40 statt 42.

Neu: server/mess-entwicklung-kacheln.mjs. Die Pruefung braucht einen
kargen Bestand und zeigt deshalb den Sonderfall (alles auf null); diese
Datei legt sieben Leute mit verschiedenen Staenden und Aufgaben an und
macht Bilder fuer 1280 und 390 px. Sie prueft nichts.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-24 13:30:17 +02:00

404 lines
19 KiB
JavaScript

/* =====================================================================
DIE PERSONENKACHELN AUF DER ENTWICKLUNGSSEITE (17.09.2026)
Filipe, mit zwei Bildschirmfotos nebeneinander: "wieso ist diese
seite noch nicht im stil und perfektioniert wie die auf screen2 ...
aber so dass ich die leute in einer kachel aussuchen kann alle
einzel."
Der Unterschied zur Checkliste lag nicht im Aufbau -- der stimmte --
sondern an zwei Stellen:
1. Die Zahlen standen als graue Textzeilen da, drueben als
abgesetzte Kaesten mit grosser Ziffer (`bilanz-zahl`).
2. Eine angeklickte Person sah aus wie eine nicht angeklickte. Wer
scrollte, wusste nicht mehr, wen er offen hat.
---------------------------------------------------------------------
WAS HIER GEPRUEFT WIRD -- UND WARUM NICHT DAS AUSSEHEN
Ob etwas "gleich aussieht", laesst sich nicht messen. Die drei Fragen
darunter schon, und sie sind das, was das Aussehen traegt:
- Benutzt die Kachel den HAUSBAUSTEIN (`bilanz-zahl`) oder einen
Nachbau? Ein Nachbau laeuft auseinander, sobald jemand eins von
beiden anfasst.
- Stimmen die Zahlen? "angesehen" plus "noch offen" MUSS die
Gesamtzahl ergeben. Zwei Zahlen, die nebeneinander stehen und
nicht zusammenpassen, sind schlimmer als eine.
- Sieht man, wer gewaehlt ist? Und zwar so, dass es auch ein
Vorleseprogramm sagen kann.
MIT GEGENPROBE: Vor dem ersten Klick darf KEINE Kachel gewaehlt sein.
Sonst hiesse "eine ist gewaehlt" nur, dass immer eine gewaehlt ist.
Aufrufen mit: node server/pruef-entwicklung-kacheln.mjs
Bild: SCHIRM=1 node server/pruef-entwicklung-kacheln.mjs
===================================================================== */
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { eigenerPort } from "./helfer-port.mjs";
const PORT = await eigenerPort(import.meta, "pruef-entwicklung-kacheln");
const ordner = mkdtempSync(join(tmpdir(), "ws-ekach-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
process.env.PORT = String(PORT);
process.env.SITE_ACCESS_SECRET = "lokaler-test";
process.env.SITE_ACCESS_CODE_DOGI = "DOGI-TEST-1";
process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
notbremse(180_000, "pruef-entwicklung-kacheln");
await new Promise((r) => setTimeout(r, 700));
const BASIS = `http://127.0.0.1:${PORT}`;
process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); });
process.on("unhandledRejection", (f) => { console.error("ABSTURZ (Versprechen):", f); process.exit(7); });
let fehler = 0, geprueft = 0;
const melde = (t) => console.log(t);
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
const d = new DatabaseSync(process.env.WORKSPACE_DB);
const jetzt = new Date().toISOString();
const schluessel = randomBytes(32).toString("hex");
d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)"
+ " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING")
.run("code_kennung_schluessel", schluessel, jetzt);
function anlegen(name, rolle, code) {
const salt = randomBytes(16).toString("hex");
const hash = scryptSync(code, salt, 64,
{ N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
d.prepare("INSERT INTO personen"
+ " (name, rolle, code_hash, code_salt, code_n, code_kennung, aktiv, erstellt)"
+ " VALUES (?,?,?,?,?,?,1,?)")
.run(name, rolle, hash, salt, 32768,
createHmac("sha256", schluessel).update(code).digest("hex"), jetzt);
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
}
const idFilipe = anlegen("Filipe", "admin", "CODE-DOGI-0001");
anlegen("VanVan", "hand", "CODE-HAND-0001");
const idDiene = anlegen("Diene", "modi", "CODE-MODI-0001");
/* =====================================================================
AUFGABEN FUER DEN AUFTRAGSSTREIFEN (24.09.2026)
=====================================================================
Seit dem 24.09. traegt jede Kachel, was bei der Person liegt --
Filipe: "die aufgaben die wir verteilen sollen da auch zu sehen sein
und so."
Das laesst sich nur messen, wenn es etwas zu zeigen gibt. Drei
Aufgaben fuer Diene, KEINE offene fuer VanVan -- so wird beides
geprueft: die Zahl UND der Fall "nichts offen", der auf der Kachel
ausdruecklich stehenbleibt.
DIE ERLEDIGTE IST DIE GEGENPROBE. Zaehlte der Streifen sie mit,
stuende bei VanVan "1 offene Aufgabe" ueber einer abgehakten -- und
niemand wuerde es merken, weil die Zahl ja plausibel aussieht.
GERECHNET WIRD RELATIV ZU HEUTE, nie mit einem festen Datum: Eine
Frist, die beim Schreiben in der Zukunft lag, holt der Kalender
sonst ein, und die Pruefung wird rot, ohne dass etwas kaputt ist.
Mittags gesetzt, damit die Zeitumstellung den Tag nicht kippt. */
const tagPlus = (n) => {
const t = new Date(); t.setHours(12, 0, 0, 0); t.setDate(t.getDate() + n);
return `${t.getFullYear()}-${String(t.getMonth() + 1).padStart(2, "0")}-${String(t.getDate()).padStart(2, "0")}`;
};
const GESTERN = tagPlus(-1), IN_DREI = tagPlus(3);
const neueAufgabe = (titel, wer, frist, status) => d.prepare(
"INSERT INTO aufgaben (titel, status, prioritaet, creator_id, verantwortlich_id, frist,"
+ " erstellt, erstellt_von) VALUES (?,?,'mittel',?,?,?,?,?)")
.run(titel, status, idFilipe, wer, frist, jetzt, idFilipe);
neueAufgabe("Clips vom Samstag schneiden", idDiene, GESTERN, "offen");
neueAufgabe("Plan für nächste Woche", idDiene, IN_DREI, "arbeit");
neueAufgabe("Ideen sammeln", idDiene, null, "offen");
neueAufgabe("Längst abgehakt", idDiene, GESTERN, "erledigt");
d.close();
const an = await fetch(BASIS + "/workspace/api/anmelden", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ rolle: "admin", code: "CODE-DOGI-0001" }),
});
const keks = (an.headers.getSetCookie?.() || []).map((z) => z.split(";")[0]).join("; ");
const { chromium } = await import(
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
const browser = await chromium.launch();
try {
const kontext = await browser.newContext({ viewport: { width: 1280, height: 900 } });
await kontext.addCookies(keks.split("; ").map((k) => {
const [name, ...rest] = k.split("=");
return { name, value: rest.join("="), domain: "127.0.0.1", path: "/" };
}));
const seite = await kontext.newPage();
const konsole = [];
seite.on("console", (e) => { if (e.type() === "error") konsole.push(e.text()); });
await seite.goto(`${BASIS}/workspace/entwicklung.html`, { waitUntil: "networkidle" });
await seite.waitForSelector("#personen .e-person", { timeout: 15_000 });
await seite.waitForTimeout(400);
/* =====================================================================
1. DIE REIHE UND IHRE KACHELN
===================================================================== */
melde("");
melde("=== 1. Die Reihe ===");
{
const b = await seite.evaluate(() => {
const reihe = document.getElementById("personen");
const kacheln = [...reihe.querySelectorAll(".e-person")];
return {
schild: reihe.getAttribute("aria-label") || "",
anzahl: kacheln.length,
namen: kacheln.map((k) => k.querySelector(".e-person__name")?.textContent || ""),
ringe: kacheln.map((k) => k.querySelectorAll(".e-ring").length),
boegen: kacheln.map((k) => k.querySelectorAll(".e-ring__bogen").length),
streifen: kacheln.map((k) => k.querySelectorAll(".e-person__auftrag").length),
/* GEGENPROBE AUF DEN UMBAU: Die drei Bilanz-Kaesten sind am
24.09.2026 gewichen. Blieben irgendwo Reste stehen, stuenden
zwei Aufbauten nebeneinander -- genau das, was hier seit dem
17.09. verhindert werden soll. */
reste: kacheln.map((k) => k.querySelectorAll(
".bilanz-zahl, .e-fortschritt, .e-person__bilanz").length),
/* Die Kachel ist ein <button>. Ein <div> darin ist ungueltiges
HTML -- es wird angezeigt und faellt trotzdem irgendwann auf. */
divs: kacheln.map((k) => k.querySelectorAll("div").length),
};
});
ok(b.schild === "Person wählen", `die Reihe sagt, was sie ist ("${b.schild}")`);
ok(b.anzahl === 2, `zwei Kacheln (${b.namen.join(", ")})`);
ok(b.ringe.length > 0 && b.ringe.every((n) => n === 1),
`jede Kachel traegt genau einen Ring (${b.ringe.join(", ")})`);
ok(b.boegen.every((n) => n === 1),
`und in jedem Ring genau einen Bogen (${b.boegen.join(", ")})`);
ok(b.streifen.length > 0 && b.streifen.every((n) => n === 1),
`jede Kachel traegt ihren Auftragsstreifen (${b.streifen.join(", ")})`);
ok(b.reste.every((n) => n === 0),
`kein Rest der alten Kachel (Balken/Bilanz-Kaesten: ${b.reste.join(", ")})`);
ok(b.divs.every((n) => n === 0),
`nichts Ungueltiges im Knopf (divs: ${b.divs.join(", ")})`);
}
/* =====================================================================
2. STIMMT DER RING MIT DEM, WAS DANEBEN STEHT?
=====================================================================
Bis zum 24.09.2026 stand hier die Rechnung "angesehen + noch
offen = alle Punkte". Die beiden Kaesten gibt es nicht mehr; die
Frage dahinter bleibt aber dieselbe und ist jetzt sogar schaerfer
zu stellen, weil DREI Darstellungen derselben Zahl nebeneinander
liegen:
- der Bogen des Rings (stroke-dasharray, "68 100"),
- die Ziffer in seiner Mitte,
- die Zeile darunter ("46 von 68 angesehen"),
- und das aria-label, das ein Vorleseprogramm vorliest.
Laufen die auseinander, sieht man es NICHT: Ein Ring, der zu drei
Vierteln gefuellt ist, waehrend "12 %" darin steht, faellt auf --
ein Ring, dessen aria-label etwas anderes sagt als das Bild, nie.
Deshalb wird hier alles gegen dieselbe Quelle geprueft: die Zahlen
aus /entwicklung/lage. */
melde("");
melde("=== 2. Der Ring und seine Zahlen ===");
{
const lage = await seite.evaluate(async () => {
const a = await fetch("/workspace/api/entwicklung/lage", { credentials: "same-origin" });
return a.ok ? await a.json() : null;
});
ok(lage && lage.gesamt_fremd > 0,
`die Seite kennt ${lage?.gesamt_fremd ?? "?"} Punkte insgesamt`);
const z = await seite.evaluate(() => [...document.querySelectorAll("#personen .e-person")]
.map((k) => {
const ring = k.querySelector(".e-ring");
return {
name: k.querySelector(".e-person__name")?.textContent || "",
nr: Number(k.dataset.person),
ringZahl: k.querySelector(".e-ring__zahl")?.textContent || "",
bogen: k.querySelector(".e-ring__bogen")?.getAttribute("stroke-dasharray") || "",
schild: ring?.getAttribute("aria-label") || "",
rolle: ring?.getAttribute("role") || "",
standZahl: k.querySelector(".e-person__stand-zahl")?.textContent || "",
standRest: k.querySelector(".e-person__stand-rest")?.textContent || "",
abw: k.querySelector(".e-person__abw")?.textContent || "",
};
}));
const gesamt = lage?.gesamt_fremd || 0;
const sollAnteil = (p) => (gesamt > 0
? Math.round((Number(p?.gesetzt) || 0) / gesamt * 100) : 0);
const nachNr = new Map((lage?.personen || []).map((p) => [Number(p.id), p]));
ok(z.length > 0 && z.every((k) => nachNr.has(k.nr)),
`jede Kachel gehoert zu einer Person aus der Auskunft (${z.map((k) => k.nr).join(", ")})`);
const ringOk = z.every((k) => k.ringZahl === String(sollAnteil(nachNr.get(k.nr))));
ok(ringOk, `die Ziffer im Ring ist der gerechnete Anteil `
+ `(${z.map((k) => `${k.name}: ${k.ringZahl}%`).join(", ")})`);
/* Der BOGEN ist das, was man sieht. Umfang 100 -- die erste Zahl
im stroke-dasharray ist damit buchstaeblich der Prozentwert. */
const bogenOk = z.every((k) =>
Number(String(k.bogen).split(" ")[0]) === sollAnteil(nachNr.get(k.nr))
&& String(k.bogen).split(" ")[1] === "100");
ok(bogenOk, `und der Bogen zeigt denselben Anteil (${z.map((k) => k.bogen).join(" | ")})`);
/* DIE ZEILE DARUNTER nennt beide Zahlen, aus denen der Anteil
entsteht -- ohne sie waere "68 %" eine Behauptung ohne Beleg. */
const standOk = z.every((k) => {
const p = nachNr.get(k.nr);
return k.standZahl === String(Number(p?.gesetzt) || 0)
&& k.standRest === ` von ${gesamt} angesehen`;
});
ok(standOk, `"x von ${gesamt} angesehen" stimmt bei jeder Kachel `
+ `(${z.map((k) => k.standZahl + k.standRest).join(", ")})`);
/* UND DAS VORLESEPROGRAMM HOERT DASSELBE. Ein Ring ist ein Bild;
ohne Schild ist er fuer ein Vorleseprogramm gar nicht da. */
ok(z.every((k) => k.rolle === "img"), "der Ring gibt sich als Bild zu erkennen");
const schildOk = z.every((k) => {
const p = nachNr.get(k.nr);
const soll = `${Number(p?.gesetzt) || 0} von ${gesamt} angesehen `
+ `(${sollAnteil(p)} Prozent)`;
return k.schild === soll;
});
ok(schildOk, `und sagt dasselbe wie das Bild ("${z[0]?.schild}")`);
/* EINE NULL IST KEINE WARNUNG: "verschieden gesehen" steht nur da,
wenn es etwas gibt. Im Testbestand hat niemand etwas anders
gesehen -- also darf die Angabe auf KEINER Kachel stehen. */
ok(z.every((k) => k.abw === ""),
"ohne Abweichung steht dort auch nichts");
}
/* =====================================================================
2b. WAS LIEGT BEI DER PERSON? -- der Auftragsstreifen
=====================================================================
Filipe, 24.09.2026: "die aufgaben die wir verteilen sollen da auch
zu sehen sein und so."
Angelegt sind (siehe oben): fuer Diene drei offene -- eine
ueberfaellig, eine in drei Tagen, eine ohne Frist -- und eine
erledigte. Fuer VanVan nichts.
GEPRUEFT WIRD BEIDES: die Zahl UND der Fall "nichts offen". Der
zweite ist der, den man beim Bauen vergisst, und er ist auf dieser
Seite der haeufigste: Wer verteilt, sucht genau danach. */
melde("");
melde("=== 2b. Der Auftragsstreifen ===");
{
const a = await seite.evaluate(() => Object.fromEntries(
[...document.querySelectorAll("#personen .e-person")].map((k) => {
const s2 = k.querySelector(".e-person__auftrag");
return [k.querySelector(".e-person__name")?.textContent || "?", {
sichtbar: !!s2 && !s2.hidden,
leer: s2?.dataset.leer || "",
spaet: s2?.dataset.spaet || "",
zahl: k.querySelector(".e-person__auftrag-zahl")?.textContent || "",
wort: k.querySelector(".e-person__auftrag-wort")?.textContent || "",
marke: k.querySelector(".e-person__auftrag-spaet")?.textContent || "",
titel: k.querySelector(".e-person__auftrag-titel")?.textContent || "",
frist: k.querySelector(".e-person__auftrag-frist")?.textContent || "",
fristSpaet: k.querySelector(".e-person__auftrag-frist")?.dataset.spaet || "",
leerText: k.querySelector(".e-person__auftrag-leer")?.textContent || "",
}];
})));
const dn = a.Diene || {}, vv = a.VanVan || {};
ok(dn.sichtbar === true && vv.sichtbar === true,
"der Streifen steht auf beiden Kacheln");
/* DREI, NICHT VIER: Die erledigte zaehlt nicht mit. Ohne diese
Gegenprobe wuerde eine Zahl, die alles zaehlt, nie auffallen. */
ok(dn.zahl === "3", `Diene hat drei offene Aufgaben (steht da: "${dn.zahl}")`);
ok(dn.wort === "offene Aufgaben", `und das Wort steht im Plural ("${dn.wort}")`);
ok(dn.marke === "1 überfällig", `eine davon ist ueberfaellig ("${dn.marke}")`);
ok(dn.spaet === "ja", "und der Streifen zeigt es an seiner Kante");
/* DIE NAECHSTE MIT NAMEN -- und zwar die ueberfaellige, nicht
irgendeine. Sortiert wird nach Frist; ohne Frist steht hinten. */
ok(dn.titel === "Clips vom Samstag schneiden",
`die ueberfaellige steht mit Namen da ("${dn.titel}")`);
ok(dn.frist === "seit gestern überfällig",
`mit einem Satz statt einem Datum ("${dn.frist}")`);
ok(dn.fristSpaet === "ja", "und sie ist als ueberfaellig ausgezeichnet");
ok(vv.leer === "ja" && vv.leerText === "keine offene Aufgabe",
`bei VanVan steht "nichts offen" statt einer Null ("${vv.leerText}")`);
ok(vv.zahl === "" && vv.marke === "",
"und keine erfundene Zahl daneben");
}
/* =====================================================================
3. EINZELN AUSWAEHLBAR -- UND MAN SIEHT ES
===================================================================== */
melde("");
melde("=== 3. Auswaehlen ===");
{
/* GEGENPROBE ZUERST: Vorher darf keine gewaehlt sein. */
const vorher = await seite.evaluate(() => [...document.querySelectorAll("#personen .e-person")]
.map((k) => k.getAttribute("aria-pressed")));
ok(vorher.length > 0 && vorher.every((w) => w === "false"),
`vor dem Klick ist keine gewaehlt (${vorher.join(", ")})`);
await seite.click("#personen .e-person:nth-of-type(2)");
await seite.waitForTimeout(900);
const nachher = await seite.evaluate(() => ({
gedrueckt: [...document.querySelectorAll("#personen .e-person")]
.map((k) => k.getAttribute("aria-pressed")),
wer: document.querySelector('#personen .e-person[aria-pressed="true"] .e-person__name')
?.textContent || "",
karteOffen: !document.getElementById("karte")?.hidden,
}));
ok(nachher.gedrueckt.filter((w) => w === "true").length === 1,
`genau eine ist gewaehlt (${nachher.gedrueckt.join(", ")})`);
ok(nachher.wer === "Diene", `und zwar die angeklickte (${nachher.wer})`);
ok(nachher.karteOffen, "ihre Karte steht offen");
/* UND DIE AUSWAHL WANDERT, statt sich zu sammeln. */
await seite.click("#personen .e-person:nth-of-type(1)");
await seite.waitForTimeout(900);
const um = await seite.evaluate(() =>
document.querySelector('#personen .e-person[aria-pressed="true"] .e-person__name')
?.textContent || "");
const wieViele = await seite.evaluate(() =>
document.querySelectorAll('#personen .e-person[aria-pressed="true"]').length);
ok(um === "VanVan" && wieViele === 1,
`ein Klick weiter ist nur noch sie gewaehlt (${um}, ${wieViele} gewaehlt)`);
}
/* =====================================================================
4. NICHTS KAPUTT
===================================================================== */
melde("");
melde("=== 4. Die Konsole ===");
ok(konsole.length === 0,
konsole.length ? `Fehler in der Konsole: ${konsole[0]}` : "keine Fehler in der Konsole");
if (process.env.SCHIRM) {
const pfad = fileURLToPath(new URL("pruef-entwicklung-kacheln.png", import.meta.url));
await seite.screenshot({ path: pfad, fullPage: false });
melde(" -- Bildschirmfoto: server/pruef-entwicklung-kacheln.png");
}
await kontext.close();
} finally {
await browser.close();
}
melde("");
melde(`${geprueft} Pruefungen, ${fehler} Fehler`);
melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest */ }
process.exit(fehler ? 1 : 0);