Files
dogfather-universe/pruef-verwaltung.mjs
T
DogFatherGitandClaude Opus 5 c1cb51884f Verwaltung: Übersicht als Startansicht, Reiter unter den Titel
Die Reiter standen neben dem Titel. Das las sich wie eine einzige lange
Zeile, in der der Titel nur der erste von sechs Knöpfen zu sein schien --
man sah nicht auf einen Blick, in welchem Bereich man war. Jetzt zwei
Zeilen: oben WO man ist, darunter WOHIN man kann.

Die Verwaltung öffnete bisher mit der Anfragenliste. Eine Liste ist eine
Ablage: Sie zeigt, WAS es gibt, nicht was zu TUN ist. Neu ist ein
Cockpit, das in drei Stufen antwortet -- was auf mich wartet, was beim
Kunden liegt, wie es ums Geld steht -- und dann laufende Projekte mit
Fortschritt sowie den Verlauf.

Zwei Grundsätze machen die Zahlen brauchbar: Getrennt nach 'wartet auf
mich' und 'wartet auf den Kunden' (zwölf offene Punkte sind entspannt,
wenn elf beim Kunden liegen). Und das ALTER färbt, nicht die Menge --
vier neue Anfragen sind kein Problem, eine seit sechs Tagen liegende
schon. Ein offener Widerruf ist immer rot, weil eine gesetzliche Frist
läuft.

Jede Kachel ist ein echter <button> und führt in den passenden Reiter.
Farbe ist nie der einzige Träger: Neben jedem farbigen Zustand steht der
Text ('älteste seit 8 Tagen').

Drei Fehler dabei gefunden und behoben:
- Der Titel wurde nur beim Klicken gesetzt. Frisch geladen zeigte die
  Seite das Cockpit, während darüber noch 'Projektanfragen' stand. Die
  Zuordnung Reiter->Titel liegt jetzt ausserhalb des Klick-Zuhörers.
- '.wd h2' überstimmte '.vw-ub-h': 44px Überschrift über 33px Zahl, die
  Seite las sich wie ein Plakat. Dieselbe Spezifitätsfalle wie früher
  bei '.wd a'.
- Eine Antwort mit ok:true aber ohne Inhalt riss die ganze Verwaltung
  mit. Wird jetzt abgefangen -- ein halber Server ist ein realistischer
  Fall.

Geprüft: 22 Serverprüfungen gegen eine echte Datenbank (inkl. vier
Fällen, die belegen, dass die Rechteprüfung wirklich greift), 56 im
Browser auf Computer und Handy, 69 im bestehenden Verwaltungstest,
0 Fundstellen im Design-/WCAG-Test, 5 im Farbsystemtest.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-23 19:21:12 +02:00

384 lines
20 KiB
JavaScript

/* Prüft die neuen Bereiche der Verwaltung: Projekte mit Aufgabenliste
und das Postfach. Wieder mit abgefangenen Netzanfragen, aber mit
Daten, die den heiklen Faellen entsprechen:
- ein Punkt, der auf den KUNDEN wartet (muss auffallen)
- ein Punkt "nicht enthalten" (muss als solcher erkennbar sein)
- eine INTERNE Notiz im Postfach (darf nicht wie eine gesendete
Nachricht aussehen)
*/
import { chromium } from "playwright";
import { createServer } from "node:http";
import { readFileSync, existsSync } from "node:fs";
import { join, extname } from "node:path";
const W = process.cwd();
const T = { ".html": "text/html", ".css": "text/css", ".js": "text/javascript",
".png": "image/png", ".jpg": "image/jpeg", ".svg": "image/svg+xml",
".webp": "image/webp", ".ico": "image/x-icon", ".json": "application/json" };
const srv = createServer((q, r) => {
const p = join(W, decodeURIComponent(q.url.split("?")[0]));
if (existsSync(p) && !p.endsWith("/")) {
r.writeHead(200, { "content-type": T[extname(p)] || "text/plain" });
return r.end(readFileSync(p));
}
r.writeHead(404).end("");
});
await new Promise((r) => srv.listen(4325, r));
const PROJEKT = {
id: "p1", nummer: "P-0001", titel: "Website für Musterbau", paket: "website",
status: "design", kunde_id: "k1", kunde_name: "Anna Muster", kunde_firma: "Musterbau",
preis_cent: 180000, waehrung: "EUR", richttermin: null, archiviert: 0,
anzahlung_bezahlt: 1, rest_bezahlt: 0, erstellt_am: new Date().toISOString(),
aufg_gesamt: 6, aufg_erledigt: 2, aufg_kunde: 2, ungelesen: 1,
naechster_schritt: "Deine Rueckmeldung zum Entwurf.", wartet_auf: "kunde",
kunde_email: "[email protected]", portfolio_freigabe: 0,
};
const AENDERUNGEN = [
{ id: "ae1", titel: "Noch eine Unterseite", beschreibung: "Eine Seite fuer Referenzen waere schoen.",
status: "eingereicht", zusatzpreis_cent: null, zusatzdauer_text: null,
erstellt_am: new Date(Date.now() - 86400000).toISOString() },
{ id: "ae2", titel: "Anderes Logo", beschreibung: "Wir haben ein neues.",
status: "angenommen", zusatzpreis_cent: 12000, zusatzdauer_text: "2 Tage",
erstellt_am: new Date(Date.now() - 172800000).toISOString() },
];
const PNACHRICHTEN = [
{ id: "pn1", autor: "kunde", text: "Wann kommt der naechste Entwurf?",
erstellt_am: new Date(Date.now() - 7200000).toISOString() },
{ id: "pn2", autor: "dogfather", text: "Morgen Abend.",
erstellt_am: new Date(Date.now() - 3600000).toISOString() },
];
const AUFGABEN = [
{ id: "a1", titel: "Briefing-Gespräch führen", beschreibung: "Ziel und Umfang klären.",
kategorie: "start", status: "erledigt", wer_dran: "dogfather", nicht_enthalten: 0, hinweis: null, reihenfolge: 10 },
{ id: "a2", titel: "Texte und Bilder zusammenstellen", beschreibung: "Lieber früh als vollständig.",
kategorie: "start", status: "offen", wer_dran: "kunde", nicht_enthalten: 0, hinweis: null, reihenfolge: 20 },
{ id: "a3", titel: "Entwurf der Startseite", beschreibung: null,
kategorie: "gestaltung", status: "erledigt", wer_dran: "dogfather", nicht_enthalten: 0, hinweis: null, reihenfolge: 110 },
{ id: "a4", titel: "Entwurf ansehen und Rückmeldung geben", beschreibung: null,
kategorie: "gestaltung", status: "offen", wer_dran: "kunde", nicht_enthalten: 0, hinweis: null, reihenfolge: 130 },
{ id: "a5", titel: "Alle Unterseiten bauen", beschreibung: null,
kategorie: "umsetzung", status: "in_arbeit", wer_dran: "dogfather", nicht_enthalten: 0, hinweis: null, reihenfolge: 210 },
{ id: "a6", titel: "Mehrsprachigkeit", beschreibung: null,
kategorie: "umsetzung", status: "offen", wer_dran: "dogfather", nicht_enthalten: 1,
hinweis: "Nicht im Paket enthalten — auf Wunsch als Zusatz.", reihenfolge: 260 },
{ id: "a7", titel: "Seite live schalten", beschreibung: null,
kategorie: "abschluss", status: "offen", wer_dran: "dogfather", nicht_enthalten: 0, hinweis: null, reihenfolge: 860 },
];
const FORTSCHRITT = {
gesamt: 6, erledigt: 2, inArbeit: 1, beimKunden: 2, prozent: 33,
jeKategorie: {
start: { gesamt: 2, erledigt: 1, inArbeit: 0 },
gestaltung: { gesamt: 2, erledigt: 1, inArbeit: 0 },
umsetzung: { gesamt: 1, erledigt: 0, inArbeit: 1 },
abschluss: { gesamt: 1, erledigt: 0, inArbeit: 0 },
},
};
const VERLAUF = [
{ id: "n1", autor: "kunde", text: "Hallo! Können wir das Logo noch etwas grösser machen?",
intern: 0, projekt_titel: null, erstellt_am: new Date(Date.now() - 7200000).toISOString() },
{ id: "n2", autor: "dogfather", text: "Klar, mache ich heute noch.",
intern: 0, projekt_titel: "Website für Musterbau", erstellt_am: new Date(Date.now() - 3600000).toISOString() },
{ id: "n3", autor: "dogfather", text: "Nicht vergessen: Rechnung erst nach Abnahme.",
intern: 1, projekt_titel: null, erstellt_am: new Date(Date.now() - 1800000).toISOString() },
];
const ergebnisse = [];
function pruefe(name, ok, zusatz) {
ergebnisse.push({ name, ok });
console.log((ok ? " ok " : " FEHL ") + name + (zusatz ? " -> " + zusatz : ""));
}
const browser = await chromium.launch();
for (const [wo, breite, hoehe] of [["Computer", 1440, 950], ["Handy", 390, 844]]) {
const seite = await browser.newPage({ viewport: { width: breite, height: hoehe } });
const fehlerImProtokoll = [];
seite.on("pageerror", (e) => fehlerImProtokoll.push(e.message));
/* Mitschrift dessen, was die Seite tatsächlich an den Server schickt.
Nur so lässt sich prüfen, ob ein Klick auch WIRKLICH das Richtige
speichert — dass sich ein Knopf einfärbt, beweist gar nichts. */
const gesendet = [];
let beziffert = null;
let projektNachricht = null;
// NUR die API abfangen. Ein Muster, das den ganzen Ordner /webdesign
// umfasst, trifft auch die Seite SELBST -- dann bekommt der Browser
// statt der HTML-Datei ein JSON serviert. Genau das ist beim ersten
// Lauf passiert: Die Seite blieb leer, und der Test meldete
// "Abzeichen fehlt" statt "Seite gar nicht geladen".
// (Und der Hinweis steht in Zeilenkommentaren, weil das Glob-Muster
// ein Sternchen-Schrägstrich enthält und einen Blockkommentar
// vorzeitig beenden würde -- auch das ist hier schon passiert.)
await seite.route("**/webdesign/admin/**", (route) => {
const u = route.request().url();
if (u.includes("/admin/projekte/p1/alles")) {
return route.fulfill({ json: { ok: true, projekt: PROJEKT, aufgaben: AUFGABEN,
fortschritt: FORTSCHRITT, aenderungen: AENDERUNGEN, nachrichten: PNACHRICHTEN } });
}
if (u.includes("/admin/projekte/p1/aufgaben")) {
return route.fulfill({ json: { ok: true, projekt: PROJEKT, aufgaben: AUFGABEN, fortschritt: FORTSCHRITT } });
}
if (u.match(/\/admin\/projekte\/p1$/)) {
gesendet.push(route.request().postDataJSON());
return route.fulfill({ json: { ok: true } });
}
if (u.includes("/admin/aenderungen/")) {
beziffert = route.request().postDataJSON();
return route.fulfill({ json: { ok: true, preisText: "250,00 €" } });
}
if (u.includes("/admin/projekt-nachricht")) {
projektNachricht = route.request().postDataJSON();
return route.fulfill({ json: { ok: true } });
}
if (u.includes("/admin/projekte")) return route.fulfill({ json: { ok: true, projekte: [PROJEKT] } });
if (u.includes("/admin/postfach-zaehler")) return route.fulfill({ json: { ok: true, ungelesen: 1 } });
if (u.includes("/admin/postfach/k1")) {
return route.fulfill({ json: { ok: true,
kunde: { id: "k1", name: "Anna Muster", email: "[email protected]", sprache: "de" },
nachrichten: VERLAUF, projekte: [{ id: "p1", titel: "Website für Musterbau" }] } });
}
if (u.includes("/admin/postfach")) {
return route.fulfill({ json: { ok: true, ungelesen: 1, verlaeufe: [{
kunde_id: "k1", name: "Anna Muster", firma: "Musterbau", email: "[email protected]",
anzahl: 3, ungelesen: 1, letzte: new Date(Date.now() - 1800000).toISOString(),
letzter_text: "Nicht vergessen: Rechnung erst nach Abnahme.", letzter_autor: "dogfather" }] } });
}
/* Die Uebersicht ist seit dem 23.08.2026 die Startansicht und wird
deshalb bei JEDEM Laden abgerufen. Ohne eigene Ersatzantwort fiel
sie in den Auffang unten und bekam ein blankes {ok:true} -- was
einen Fehler ausloeste, der als "keine Fehler im Protokoll"
fehlschlug, ohne zu verraten, woher er kam. */
if (u.includes("/admin/uebersicht")) return route.fulfill({ json: { ok: true,
beiMir: { anfragen: { n: 0 }, nachrichten: { n: 0 }, wuensche: { n: 0 },
widerrufe: { n: 0 }, projekte: 0 },
beiKunde: { projekte: 0, aufgaben: 0, zahlungen: { n: 0, summe: 0 }, zahlungenText: "0,00 €" },
geld: { monat: { n: 0, brutto: 0, netto: 0, bruttoText: "0,00 €", nettoText: "0,00 €" },
gesamt: { n: 0, bruttoText: "0,00 €" }, gebuehrenText: "0,00 €" },
projekte: [], verlauf: [], bestand: {} } });
if (u.includes("/admin/anfragen")) return route.fulfill({ json: { ok: true, anfragen: [] } });
if (u.includes("/admin/kunden")) return route.fulfill({ json: { ok: true, kunden: [] } });
return route.fulfill({ json: { ok: true } });
});
await seite.route("**/auth/**", (r) => r.fulfill({ json: { ok: true, user: { name: "Test" } } }));
await seite.addInitScript(() => {
sessionStorage.setItem("wd-vw-token", "t");
sessionStorage.setItem("wd-sitzung-aktiv", "1");
});
await seite.goto("http://127.0.0.1:4325/webdesign/verwaltung.html");
await seite.waitForTimeout(900);
console.log("\n=== " + wo + " ===");
// Abzeichen am Reiter
const abz = await seite.locator("#vw-pf-zaehler").innerText().catch(() => "");
const abzSichtbar = await seite.locator("#vw-pf-zaehler").isVisible().catch(() => false);
pruefe(wo + ": Postfach-Abzeichen zeigt die ungelesene Nachricht", abzSichtbar && abz === "1", abz);
// ---- Projekte ----
await seite.click('[data-tab="projekte"]');
await seite.waitForTimeout(500);
pruefe(wo + ": Projektliste zeigt das Projekt", await seite.locator("[data-projekt]").count() === 1);
pruefe(wo + ": „wartet auf Kunde“ ist sichtbar", await seite.locator(".vw-wartet").count() === 1);
await seite.click("[data-projekt]");
await seite.waitForTimeout(600);
const m = await seite.evaluate(() => {
const gruppen = Array.from(document.querySelectorAll(".vw-aufg-gruppe")).map((g) => g.dataset.k);
const farben = Array.from(document.querySelectorAll(".vw-aufg-gruppe")).map((g) =>
getComputedStyle(g.querySelector(".vw-aufg-kopf")).color);
const balken = Array.from(document.querySelectorAll(".vw-fs-teil > i")).map((i) =>
getComputedStyle(i).backgroundColor);
const zeile = document.querySelector('[data-aufgabe="a2"]');
const normal = document.querySelector('[data-aufgabe="a5"]');
return {
gruppen, farben, balkenFarben: [...new Set(balken)].length,
kundenZeileAnders: zeile && normal
? getComputedStyle(zeile).backgroundColor !== getComputedStyle(normal).backgroundColor : false,
nichtEnthalten: document.querySelectorAll(".vw-aufg-nicht").length,
haken: document.querySelectorAll(".vw-aufg-haken").length,
erledigtDurchgestrichen: getComputedStyle(
document.querySelector('[data-aufgabe="a1"] .vw-aufg-titel')).textDecorationLine,
blickFelder: document.querySelectorAll(".vw-blick-feld").length,
/* Die REIHENFOLGE im Bild, nicht im Quelltext.
Diese Pruefung fehlte im ersten Anlauf, und deshalb meldete der
Test 37 von 37 gruen, waehrend jede Zeile sichtbar falsch herum
gebaut war: Kaestchen, dann die kleinen Knoepfe, und der Titel
ganz rechts. Alle inhaltlichen Pruefungen (Farben, Anzahl,
Durchgestrichenes) trafen zu -- keine einzige schaute darauf, WO
die Dinge liegen. Aufgefallen ist es erst am Screenshot.
Lehre: Wer eine Anordnung baut, muss die Anordnung pruefen, nicht
nur ihre Bestandteile. */
anordnung: (() => {
const z = document.querySelector('[data-aufgabe="a2"]');
if (!z) return null;
const k = z.querySelector(".vw-aufg-haken").getBoundingClientRect();
const t = z.querySelector(".vw-aufg-titel").getBoundingClientRect();
const r = z.querySelector(".vw-aufg-rechts").getBoundingClientRect();
const b = z.querySelector(".vw-aufg-text");
return {
reihenfolgeStimmt: k.right <= t.left + 1 && t.right <= r.left + 1,
textUnterTitel: b ? b.getBoundingClientRect().top >= t.bottom - 2 : true,
textNichtUnterKaestchen: b ? b.getBoundingClientRect().left >= k.right - 1 : true,
};
})(),
};
});
pruefe(wo + ": alle vier Abschnitte erscheinen", m.gruppen.join(",") === "start,gestaltung,umsetzung,abschluss", m.gruppen.join(","));
pruefe(wo + ": jeder Abschnitt hat eine EIGENE Farbe", new Set(m.farben).size === 4, m.farben.join(" | "));
pruefe(wo + ": Punkt für den Kunden hebt sich ab", m.kundenZeileAnders);
pruefe(wo + ": „nicht enthalten“ ist gekennzeichnet", m.nichtEnthalten === 1);
pruefe(wo + ": jeder Punkt hat ein Kästchen zum Abhaken", m.haken === 7, "n=" + m.haken);
pruefe(wo + ": Erledigtes ist durchgestrichen", m.erledigtDurchgestrichen.includes("line-through"), m.erledigtDurchgestrichen);
pruefe(wo + ": Zusammenfassung mit vier Zahlen", m.blickFelder === 4);
pruefe(wo + ": Kästchen — Titel — Knöpfe stehen in dieser Reihenfolge", m.anordnung?.reihenfolgeStimmt);
pruefe(wo + ": die Erklärung steht UNTER dem Titel", m.anordnung?.textUnterTitel);
pruefe(wo + ": die Erklärung ist am Titel ausgerichtet, nicht am Rand", m.anordnung?.textNichtUnterKaestchen);
/* ---- Stand ändern ----
Dafür gab es bis zu dieser Erweiterung KEINE Oberfläche. Der Kunde
sah Phase und "nächster Schritt" ganz oben in seinem Portal, und
beides liess sich nirgends ändern — ein Projekt blieb für immer im
Briefing stehen, egal wie weit es wirklich war. */
pruefe(wo + ": alle Phasen sind wählbar",
await seite.locator("[data-phase]").count() === 9, "7 Phasen + 2 Sonderfälle");
pruefe(wo + ": die aktuelle Phase ist hervorgehoben",
await seite.locator('[data-phase="design"].vw-an').count() === 1);
pruefe(wo + ": „wer ist am Zug“ zeigt den Kunden",
await seite.locator('[data-wartet="kunde"].vw-an').count() === 1);
gesendet.length = 0;
await seite.click('[data-phase="entwicklung"]');
await seite.waitForTimeout(400);
pruefe(wo + ": Phasenklick speichert sofort",
gesendet.some((g) => g && g.status === "entwicklung"), JSON.stringify(gesendet));
await seite.click('[data-wartet="dogfather"]');
await seite.waitForTimeout(400);
pruefe(wo + ": „wer ist am Zug“ speichert sofort",
gesendet.some((g) => g && g.wartetAuf === "dogfather"));
await seite.fill("#vw-p-schritt", "Entwurf ansehen und Rückmeldung geben");
await seite.fill("#vw-p-termin", "Mitte Oktober");
gesendet.length = 0;
await seite.click("#vw-p-speichern");
await seite.waitForTimeout(500);
pruefe(wo + ": „nächster Schritt“ und Termin werden gespeichert",
gesendet.some((g) => g && g.naechsterSchritt === "Entwurf ansehen und Rückmeldung geben"
&& g.richttermin === "Mitte Oktober"), JSON.stringify(gesendet));
gesendet.length = 0;
await seite.check("#vw-p-rest");
await seite.waitForTimeout(400);
pruefe(wo + ": Zahlungsstand wird sofort gespeichert",
gesendet.some((g) => g && g.restBezahlt === true), JSON.stringify(gesendet));
/* ---- Änderungswunsch beziffern ----
Der Kunde konnte seit jeher Ideen einreichen — beziffern ging nur
serverseitig. Der Kreis war offen: Er schickt etwas los und hört nie
wieder davon. */
pruefe(wo + ": beide Änderungswünsche erscheinen", await seite.locator("[data-ae]").count() === 2);
pruefe(wo + ": nur der OFFENE hat ein Bezifferungsfeld",
await seite.locator("[data-beziffern]").count() === 1);
// Ohne Preis darf nichts rausgehen — der Kunde könnte sonst nicht entscheiden.
beziffert = null;
await seite.click("[data-beziffern]");
await seite.waitForTimeout(400);
pruefe(wo + ": ohne Preis wird NICHT gesendet", beziffert === null, JSON.stringify(beziffert));
await seite.fill(".vw-ae-preis", "250");
await seite.fill(".vw-ae-dauer", "3 Tage");
await seite.click("[data-beziffern]");
await seite.waitForTimeout(600);
pruefe(wo + ": mit Preis geht das Angebot raus",
beziffert && beziffert.preisEuro === 250 && beziffert.dauer === "3 Tage", JSON.stringify(beziffert));
/* ---- Nachricht zum Projekt ----
Ohne sie hiess es: Der Kunde schreibt im Projekt, und ich kann ihm
nur woanders antworten. */
pruefe(wo + ": Projektnachrichten erscheinen",
await seite.locator("#vw-p-blasen .vw-blase").count() === 2);
await seite.fill("#vw-p-nachricht", "Der Entwurf kommt morgen.");
await seite.click("#vw-p-senden");
await seite.waitForTimeout(500);
pruefe(wo + ": Antwort zum Projekt geht raus",
projektNachricht && projektNachricht.text === "Der Entwurf kommt morgen."
&& projektNachricht.projektId === "p1", JSON.stringify(projektNachricht));
// Touch-Ziele auf dem Handy
if (breite < 900) {
const zuKlein = await seite.evaluate(() =>
Array.from(document.querySelectorAll(".vw-aufg-haken, .vw-mini"))
.filter((b) => { const r = b.getBoundingClientRect(); return r.width < 24 || r.height < 24; }).length);
pruefe(wo + ": alle Knöpfe sind gross genug zum Antippen", zuKlein === 0, "zu klein: " + zuKlein);
}
await seite.screenshot({ path: `pruef-aufgaben-${wo.toLowerCase()}.png` });
await seite.keyboard.press("Escape");
await seite.waitForTimeout(300);
// ---- Postfach ----
await seite.click('[data-tab="postfach"]');
await seite.waitForTimeout(500);
pruefe(wo + ": Postfach zeigt den Verlauf", await seite.locator("[data-verlauf]").count() === 1);
await seite.click("[data-verlauf]");
await seite.waitForTimeout(600);
const pf = await seite.evaluate(() => {
const intern = document.querySelector(".vw-blase--intern");
const wir = document.querySelector(".vw-blase--wir");
const kunde = document.querySelector(".vw-blase--kunde");
const lauf = document.getElementById("vw-blasen");
return {
/* Auf den Postfach-Verlauf eingegrenzt. Ein document-weites
".vw-blase" zaehlt auch die Nachrichten der Projektansicht mit --
die ist zwar geschlossen, steht aber weiter im Dokument. */
anzahl: document.querySelectorAll("#vw-blasen .vw-blase").length,
internDa: !!intern,
internAnders: intern && wir
? getComputedStyle(intern).borderStyle !== getComputedStyle(wir).borderStyle : false,
kundeLinks: kunde && wir
? kunde.getBoundingClientRect().left < wir.getBoundingClientRect().left : false,
amEnde: lauf ? lauf.scrollTop + lauf.clientHeight >= lauf.scrollHeight - 5 : false,
schalterDa: !!document.getElementById("vw-pf-intern"),
};
});
pruefe(wo + ": alle drei Nachrichten erscheinen", pf.anzahl === 3, "n=" + pf.anzahl);
pruefe(wo + ": die interne Notiz ist da", pf.internDa);
pruefe(wo + ": interne Notiz sieht ANDERS aus als eine gesendete", pf.internAnders);
pruefe(wo + ": Kunde links, eigene Nachrichten rechts", pf.kundeLinks);
pruefe(wo + ": Verlauf springt ans Ende", pf.amEnde);
// Der Schalter muss das Schreibfeld sichtbar umfaerben
const vorher = await seite.evaluate(() => getComputedStyle(document.getElementById("vw-schreibfeld")).borderColor);
await seite.check("#vw-pf-intern");
await seite.waitForTimeout(250);
const nachher = await seite.evaluate(() => getComputedStyle(document.getElementById("vw-schreibfeld")).borderColor);
pruefe(wo + ": „interne Notiz“ färbt das Schreibfeld sichtbar um", vorher !== nachher, vorher + " -> " + nachher);
pruefe(wo + ": keine Fehler im Protokoll", fehlerImProtokoll.length === 0, fehlerImProtokoll.join(" | "));
await seite.screenshot({ path: `pruef-postfach-${wo.toLowerCase()}.png` });
await seite.close();
}
await browser.close();
srv.close();
const fehler = ergebnisse.filter((e) => !e.ok).length;
console.log(`\n===== ${ergebnisse.length - fehler} bestanden, ${fehler} fehlgeschlagen =====`);
process.exitCode = fehler ? 1 : 0;