Die Grundlinie der Fingerwache sinkt von 6 auf 1.
DIE SCHWEREN FAELLE
pruef-notizen, pruef-teamlage-karten, pruef-terminregel,
pruef-werdegang, pruef-befinden.
Bei diesen fuenf stand mitten im Ablauf ein
`setViewportSize({ width: 390 })`. Das aendert nur die Groesse --
`hasTouch` gehoert zum KONTEXT und ist beim Anlegen entschieden.
Nachtragen geht nicht; es braucht ein zweites Fenster.
DAS MUSTER, DAS FUER ALLE FUENF GETRAGEN HAT
const handyKontext = await browser.newContext({
viewport: { width: 390, height: 844 }, hasTouch: true, isMobile: true });
await handyKontext.addCookies(await kontext.cookies());
const handy = await handyKontext.newPage();
await handy.goto(seite.url(), { waitUntil: "networkidle" });
Drei Entscheidungen darin:
- Die Anmeldung wandert als Keks mit, statt sie zu wiederholen.
Eine zweite Anmeldung waere eine zweite Stelle, die veralten
kann.
- Die Adresse kommt von `seite.url()`. Sie ein zweites Mal
hinzuschreiben waere eine zweite Wahrheit darueber, welche
Seite gemessen wird.
- Das fruehere Zuruecksetzen auf 1280 px faellt weg. Die breite
Seite war nie schmal -- es gibt nichts zurueckzustellen.
Bei pruef-terminregel liessen sich die vier Schritte, die den
schwebenden Hinweis erzeugen, nicht uebernehmen: Sie werden im
neuen Kontext nachgefahren (oeffnen, "Neu", Datum eintragen).
ALLE FUENF DANACH GRUEN
pruef-notizen EXIT 0
pruef-teamlage-karten EXIT 0 (39 geprueft)
pruef-terminregel EXIT 0 (35 Pruefungen)
pruef-werdegang EXIT 0
pruef-befinden EXIT 0
Diese Seiten halten die Fingerregeln also schon ein. Es hatte nur
nie jemand nachgesehen.
EINES BLEIBT: pruef-grosscheck
Die Datei zu aendern waere ein Zweizeiler. Sie zu PRUEFEN hiesse,
206 Seiten ueber vier Rollen und zwei Bildschirmgroessen laufen zu
lassen -- das braucht Filipes Zusage. Eine Aenderung, die ich nicht
pruefen darf, liefere ich nicht aus. Die Grundlinie steht deshalb
auf 1 und nicht auf 0.
GEPRUEFT
pruef-fingermass 3 / 0, neun Gegenproben, Grundlinie 1
381 lines
18 KiB
JavaScript
381 lines
18 KiB
JavaScript
/* =====================================================================
|
|
ZWEI WEGE, EINE ANTWORT (17.09.2026)
|
|
|
|
„Wann hat ein Zettel einen Termin?" wird an zwei Stellen gefragt:
|
|
|
|
Der KALENDER fragt „welche gehoeren hinein?" -- als SQL, ueber die
|
|
ganze Tabelle auf einmal.
|
|
Die BRETTKARTE fragt „stehe ICH drin?" -- als JavaScript, je Zeile.
|
|
|
|
Beide muessen dasselbe antworten. Tun sie es nicht, behauptet die
|
|
Karte „steht im Kalender" und der Kalender zeigt nichts -- oder
|
|
umgekehrt. Eine Anzeige, die etwas Falsches sagt, ist schlimmer als
|
|
gar keine: Man glaubt ihr ja.
|
|
|
|
Dass zwei Fassungen derselben Regel auseinanderlaufen, ist im Haus
|
|
keine Theorie. Es ist dreimal passiert:
|
|
11.09. eine abgeschriebene Spaltenliste warf drei Spalten samt
|
|
Inhalt weg -- ohne Fehlermeldung, bei gleicher Zeilenzahl
|
|
im Kalender eine zweite Artenliste neben ARTNAME -- ein BigMatch
|
|
liess sich anlegen und war danach unsichtbar
|
|
17.09. `.teilen` fuer zwei verschiedene Dinge
|
|
|
|
Deshalb stehen beide Fassungen in EINER Datei (workspace-termin-
|
|
regel.js), und deshalb prueft diese Datei sie GEGENEINANDER -- nicht
|
|
jede fuer sich. Zwei Pruefungen, die je eine Seite bestaetigen,
|
|
finden genau diesen Fehler nicht.
|
|
|
|
Aufrufen mit: node server/pruef-terminregel.mjs
|
|
===================================================================== */
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { request as httpAnfrage } from "node:http";
|
|
import { eigenerPort } from "./helfer-port.mjs";
|
|
import { tagLokal } from "./helfer-tag.mjs";
|
|
|
|
const PORT = await eigenerPort(import.meta, "pruef-terminregel");
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-terminregel-"));
|
|
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
|
process.env.PORT = String(PORT);
|
|
process.env.SITE_ACCESS_SECRET = "lokaler-test";
|
|
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-terminregel");
|
|
await new Promise((r) => setTimeout(r, 800));
|
|
|
|
process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); });
|
|
|
|
let fehler = 0, geprueft = 0, offen = 0;
|
|
const melde = (t) => console.log(t);
|
|
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
|
const unklar = (t) => { offen++; console.log(" -- " + t); };
|
|
|
|
const HEUTE = tagLokal(0);
|
|
const MORGEN = tagLokal(1);
|
|
const IN5 = tagLokal(5);
|
|
const GESTERN = tagLokal(-1);
|
|
|
|
const { DatabaseSync } = await import("node:sqlite");
|
|
const { scryptSync, randomBytes } = await import("node:crypto");
|
|
const regel = await import("./workspace-termin-regel.js");
|
|
|
|
/* =======================================================================
|
|
DIE FAELLE
|
|
|
|
Jede Zeile beantwortet EINE Frage, und die Haelfte davon ist die
|
|
Frage, ob etwas ZU RECHT wegbleibt. Eine Pruefung nur mit Treffern
|
|
koennte auch dann gruen sein, wenn die Regel alles hereinlaesst.
|
|
======================================================================= */
|
|
const FAELLE = [
|
|
// [name, bereich, datum, uhrzeit, geplant, ende, status, soll]
|
|
["Zusage", "wunsch", HEUTE, null, IN5, null, "angenommen", true],
|
|
["Zukunft", "content", IN5, null, null, null, "offen", true],
|
|
["Mit Uhrzeit", "live", MORGEN, "20:00", null, null, "offen", true],
|
|
["Zeitraum", "agentur", MORGEN, null, null, IN5, "offen", true],
|
|
["Uhrzeit heute", "live", HEUTE, "21:00", null, null, "offen", true],
|
|
["Nur heute", "ideen", HEUTE, null, null, null, "offen", false],
|
|
["Von gestern", "ideen", GESTERN, null, null, null, "offen", false],
|
|
["Erledigt", "content", IN5, null, null, null, "erledigt", false],
|
|
["Abgelehnt", "wunsch", HEUTE, null, IN5, null, "abgelehnt", false],
|
|
];
|
|
|
|
{
|
|
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const salt = randomBytes(16).toString("hex");
|
|
const hash = scryptSync("CODE-DOGI-0001", 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, aktiv, erstellt) VALUES (?,?,?,?,?,1,?)")
|
|
.run("Filipe", "admin", hash, salt, 32768, new Date().toISOString());
|
|
for (const [name, bereich, datum, uhrzeit, geplant, ende, status] of FAELLE) {
|
|
d.prepare(`INSERT INTO eintraege (bereich, art, titel, text, datum, uhrzeit, geplant,
|
|
event_ende, status, dringlichkeit, erstellt, erstellt_von)
|
|
VALUES (?,?,?,?,?,?,?,?,?,'mittel',?,1)`)
|
|
.run(bereich, "sonstiges", name, "Text", datum, uhrzeit, geplant, ende,
|
|
status, new Date().toISOString());
|
|
}
|
|
d.close();
|
|
}
|
|
|
|
function roh(pfad, kopf = {}) {
|
|
return new Promise((fertig, schief) => {
|
|
const a = httpAnfrage({
|
|
host: "127.0.0.1", port: PORT, path: pfad, method: "GET",
|
|
headers: { Host: "workspace.dogfather-universe.com", ...kopf },
|
|
}, (antwort) => {
|
|
let text = "";
|
|
antwort.on("data", (s) => { text += s; });
|
|
antwort.on("end", () => fertig({ code: antwort.statusCode, text, kopf: antwort.headers }));
|
|
});
|
|
a.on("error", schief);
|
|
a.end();
|
|
});
|
|
}
|
|
function post(pfad, koerper) {
|
|
return new Promise((fertig, schief) => {
|
|
const a = httpAnfrage({
|
|
host: "127.0.0.1", port: PORT, path: pfad, method: "POST",
|
|
headers: { Host: "workspace.dogfather-universe.com", "Content-Type": "application/json" },
|
|
}, (antwort) => {
|
|
let text = "";
|
|
antwort.on("data", (s) => { text += s; });
|
|
antwort.on("end", () => fertig({ code: antwort.statusCode, text, kopf: antwort.headers }));
|
|
});
|
|
a.on("error", schief);
|
|
a.write(JSON.stringify(koerper));
|
|
a.end();
|
|
});
|
|
}
|
|
const json = (a) => { try { return JSON.parse(a.text); } catch { return {}; } };
|
|
|
|
const an = await post("/workspace/api/anmelden", { rolle: "admin", code: "CODE-DOGI-0001" });
|
|
const keks = [].concat(an.kopf?.["set-cookie"] || []).map((c) => c.split(";")[0]).join("; ");
|
|
|
|
/* =======================================================================
|
|
1. DIE JS-FASSUNG FUER SICH
|
|
======================================================================= */
|
|
melde("=== 1. terminVon() ===");
|
|
for (const [name, , datum, uhrzeit, geplant, ende, status, soll] of FAELLE) {
|
|
const r = regel.terminVon({ datum, uhrzeit, geplant, event_ende: ende, status }, HEUTE);
|
|
ok(!!r === soll, `${name.padEnd(14)} -> ${r ? `${r.grund} am ${r.tag}` : "kein Termin"}`);
|
|
}
|
|
{
|
|
/* Die Rangfolge: Eine Zusage schlaegt alles andere. Wer „am 24."
|
|
sagt, meint den 24. -- auch wenn der Zettel am 3. entstand und
|
|
eine Uhrzeit traegt. */
|
|
const r = regel.terminVon(
|
|
{ datum: HEUTE, uhrzeit: "20:00", geplant: IN5, event_ende: null, status: "offen" }, HEUTE);
|
|
ok(r?.grund === "zugesagt" && r?.tag === IN5,
|
|
`die Zusage schlaegt Uhrzeit und Erfassungstag (${r?.grund} am ${r?.tag})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
2. DER KALENDER (SQL) UND DIE KARTE (JS) SAGEN DASSELBE
|
|
|
|
Das ist der eigentliche Zweck dieser Datei.
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 2. Beide Wege, verglichen ===");
|
|
|
|
const kal = json(await roh("/workspace/api/termine?tage=60", { cookie: keks }));
|
|
const imKalender = new Set((kal.brettsachen || []).map((b) => b.titel));
|
|
ok(imKalender.size > 0, `der Kalender liefert ${imKalender.size} Zettel`);
|
|
|
|
const bereiche = [...new Set(FAELLE.map((f) => f[1]))];
|
|
const mitMarke = new Set();
|
|
const tagLaut = new Map();
|
|
for (const b of bereiche) {
|
|
const r = json(await roh(`/workspace/api/bereich/${b}`, { cookie: keks }));
|
|
for (const e of r.eintraege || []) {
|
|
if (e.termin) { mitMarke.add(e.titel); tagLaut.set(e.titel, e.termin.tag); }
|
|
}
|
|
}
|
|
ok(mitMarke.size > 0, `die Bretter melden ${mitMarke.size} Zettel mit Termin`);
|
|
|
|
{
|
|
const nurKalender = [...imKalender].filter((x) => !mitMarke.has(x));
|
|
const nurKarte = [...mitMarke].filter((x) => !imKalender.has(x));
|
|
ok(nurKalender.length === 0,
|
|
nurKalender.length ? `steht im Kalender, aber die Karte sagt nichts: ${nurKalender.join(", ")}`
|
|
: "kein Zettel steht im Kalender, ohne dass seine Karte es sagt");
|
|
ok(nurKarte.length === 0,
|
|
nurKarte.length ? `die Karte behauptet einen Termin, der Kalender zeigt ihn nicht: ${nurKarte.join(", ")}`
|
|
: "und keine Karte behauptet einen Termin, den der Kalender nicht kennt");
|
|
}
|
|
|
|
/* UND DER TAG MUSS AUCH DERSELBE SEIN. Beide Seiten einig darueber,
|
|
DASS etwas einen Termin hat, und uneinig ueber WANN -- das waere die
|
|
unauffaelligste Art, falsch zu liegen. */
|
|
{
|
|
const uneinig = (kal.brettsachen || [])
|
|
.filter((b) => tagLaut.has(b.titel) && tagLaut.get(b.titel) !== b.wann)
|
|
.map((b) => `${b.titel}: Kalender ${b.wann}, Karte ${tagLaut.get(b.titel)}`);
|
|
ok(uneinig.length === 0,
|
|
uneinig.length ? `verschiedene Tage: ${uneinig.join(" | ")}`
|
|
: `beide nennen denselben Tag (${tagLaut.size} verglichen)`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
3. UND GEGEN DIE ERWARTUNG, NICHT NUR GEGENEINANDER
|
|
|
|
Zwei Wege koennen sich einig sein und beide falsch liegen. Deshalb
|
|
zusaetzlich gegen die Fallliste oben.
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 3. Gegen die Erwartung ===");
|
|
for (const [name, , , , , , , soll] of FAELLE) {
|
|
ok(imKalender.has(name) === soll,
|
|
soll ? `„${name}" steht im Kalender` : `„${name}" bleibt draussen`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
4. IM BROWSER
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 4. Was man sieht ===");
|
|
{
|
|
let chromium;
|
|
try {
|
|
({ chromium } = await import(
|
|
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"));
|
|
} catch { chromium = null; }
|
|
|
|
if (!chromium) {
|
|
unklar("kein Playwright gefunden -- die Oberflaeche wurde nicht angesehen");
|
|
} else {
|
|
const browser = await chromium.launch();
|
|
try {
|
|
const sicht = await browser.newContext({ viewport: { width: 1280, height: 900 } });
|
|
await sicht.addCookies(keks.split("; ").map((c) => {
|
|
const [name, ...rest] = c.split("=");
|
|
return { name, value: rest.join("="), domain: "127.0.0.1", path: "/" };
|
|
}));
|
|
const seite = await sicht.newPage();
|
|
const konsole = [];
|
|
seite.on("console", (m) => { if (m.type() === "error") konsole.push(m.text()); });
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/bereich.html?b=content`,
|
|
{ waitUntil: "domcontentloaded" });
|
|
await seite.waitForTimeout(1600);
|
|
|
|
/* SICHTBARKEIT, NICHT VORHANDENSEIN. `textContent` liefert auch,
|
|
was `display: none` ausblendet -- und am Bildschirmfoto fiel
|
|
auf, dass die Marke im Bild fehlte, obwohl die Zaehlung 1
|
|
ergab. Ohne `offsetHeight` waere das nie aufgefallen. */
|
|
const marken = await seite.evaluate(() =>
|
|
[...document.querySelectorAll(".im-kalender")].map((a) => ({
|
|
text: a.textContent.trim(), ziel: a.getAttribute("href"),
|
|
grund: a.dataset.grund,
|
|
sichtbar: a.offsetHeight > 0 && a.offsetWidth > 0,
|
|
wo: a.closest(".eintrag-karte")?.querySelector("h3, .eintrag-titel")?.textContent
|
|
|| a.parentElement?.className || "?",
|
|
})));
|
|
ok(marken.length > 0, `auf dem Brett steht ${marken.length}x „im Kalender"`);
|
|
ok(marken.length > 0 && marken.every((m) => m.sichtbar),
|
|
`und man sieht sie auch (${marken.map((m) => `${m.wo}: ${m.sichtbar}`).join(", ")})`);
|
|
ok(marken.every((m) => m.ziel === "kalender.html"),
|
|
"und jede fuehrt in den Kalender");
|
|
/* GEGENPROBE: NICHT an jeder Karte. „Erledigt" und „von gestern"
|
|
liegen auf demselben Brett und duerfen die Marke nicht haben --
|
|
sonst hiesse „steht im Kalender" nur, dass ueberall etwas steht. */
|
|
const karten = await seite.locator(".eintrag-karte").count();
|
|
ok(karten > marken.length,
|
|
`aber nicht an allen (${marken.length} von ${karten} Karten)`);
|
|
|
|
/* ---- Der Hinweis unter dem Datumsfeld ---- */
|
|
const hinweis = async () => seite.evaluate(() => ({
|
|
text: document.getElementById("hinweis-datum")?.textContent?.trim() || "",
|
|
marke: document.getElementById("hinweis-datum")?.dataset.imkalender || "",
|
|
}));
|
|
/* Der Knopf heisst `neu-oeffnen`, nicht `neu-auf` -- beim ersten
|
|
Lauf stand hier der falsche Name samt `.catch(() => {})`, und
|
|
der Fehlgriff verschwand lautlos. Ein `.catch`, das einen
|
|
Selektorfehler schluckt, macht aus einer kaputten Pruefung eine
|
|
gruene: Danach stand im Hinweisfeld "" und die Zeile darunter
|
|
war trotzdem in Ordnung, weil "" nun einmal nicht "ja" ist. */
|
|
await seite.click("#neu-oeffnen");
|
|
await seite.waitForTimeout(500);
|
|
const h1 = await hinweis();
|
|
ok(h1.text.length > 5, `beim Oeffnen steht ein Satz da ("${h1.text}")`);
|
|
/* Die Anzahl gehoert in die BEDINGUNG: Ohne `h1.text` waere diese
|
|
Zeile auch bei leerem Feld gruen. */
|
|
ok(h1.text.length > 5 && h1.marke !== "ja",
|
|
"und er sagt NICHT, dass es im Kalender landet -- heute ist heute");
|
|
|
|
await seite.fill("#f-datum", IN5);
|
|
await seite.waitForTimeout(300);
|
|
const h2 = await hinweis();
|
|
ok(h2.text.length > 5 && h2.marke === "ja" && /Kalender/.test(h2.text),
|
|
`mit einem Tag in der Zukunft aendert er sich ("${h2.text}")`);
|
|
|
|
await seite.fill("#f-datum", HEUTE);
|
|
await seite.waitForTimeout(300);
|
|
const h3 = await hinweis();
|
|
ok(h3.text.length > 5 && h3.marke !== "ja",
|
|
`und zurueck auf heute wieder ("${h3.text}")`);
|
|
|
|
/* ---- UND AM HANDY (17.09.2026) ----------------------------------
|
|
|
|
Der Hinweis haengt seit heute AUSSERHALB des Feldes (absolut,
|
|
damit er die Eingaben nicht aus der Linie schiebt). Genau das
|
|
kann am schmalen Bildschirm das naechste Feld ueberdecken --
|
|
dort stehen die Felder untereinander, und dann liegt der
|
|
schwebende Satz auf dem, was folgt.
|
|
|
|
Gemessen wird die Ueberschneidung, nicht geschaetzt. */
|
|
/* EIN EIGENER KONTEXT MIT FINGER (29.09.2026).
|
|
|
|
Hier stand `seite.setViewportSize({ width: 390 })`. Das
|
|
aendert nur die Groesse: `hasTouch` gehoert zum KONTEXT und
|
|
laesst sich danach nicht mehr setzen. Ohne ihn meldet der
|
|
Browser `pointer: fine`, und KEINE Regel aus
|
|
`@media (pointer: coarse)` greift -- dort stehen die
|
|
44-Pixel-Beruehrziele. Genau sie entscheiden hier mit:
|
|
Hoehere Felder stehen enger beieinander, und ob ein
|
|
schwebender Hinweis das naechste ueberdeckt, haengt daran.
|
|
|
|
Der kurze Weg zum Hinweis wird nachgefahren statt
|
|
uebernommen -- vier Schritte, dieselben wie oben. */
|
|
const handyKontext = await browser.newContext({
|
|
viewport: { width: 390, height: 844 }, hasTouch: true, isMobile: true });
|
|
await handyKontext.addCookies(await sicht.cookies());
|
|
const handy = await handyKontext.newPage();
|
|
await handy.goto(`http://127.0.0.1:${PORT}/workspace/bereich.html?b=content`,
|
|
{ waitUntil: "networkidle" });
|
|
await handy.waitForTimeout(1600);
|
|
await handy.click("#neu-oeffnen");
|
|
await handy.waitForTimeout(500);
|
|
await handy.fill("#f-datum", HEUTE);
|
|
await handy.waitForTimeout(500);
|
|
const eng = await handy.evaluate(() => {
|
|
const h = document.getElementById("hinweis-datum");
|
|
if (!h || h.offsetHeight === 0) return { da: false };
|
|
const a2 = h.getBoundingClientRect();
|
|
const treffer = [...document.querySelectorAll(".neu__raster > *")]
|
|
.filter((f) => !f.contains(h))
|
|
.map((f) => f.getBoundingClientRect())
|
|
.filter((b) => a2.left < b.right && b.left < a2.right
|
|
&& a2.top < b.bottom && b.top < a2.bottom);
|
|
return { da: true, ueberdeckt: treffer.length, hoehe: Math.round(a2.height) };
|
|
});
|
|
ok(eng.da, "am Handy steht der Hinweis auch da");
|
|
ok(eng.da && eng.ueberdeckt === 0,
|
|
`und liegt auf keinem anderen Feld (${eng.ueberdeckt} Ueberschneidungen)`);
|
|
/* Das Handyfenster war ein eigener Kontext und geht hier zu.
|
|
`seite` war nie schmal -- deshalb gibt es auch nichts
|
|
zurueckzustellen; frueher stand hier ein `setViewportSize`
|
|
auf 1280, das die Umstellung rueckgaengig machte. */
|
|
await handyKontext.close();
|
|
|
|
ok(konsole.length === 0,
|
|
konsole.length ? `Konsolenfehler: ${konsole[0]}` : "kein Konsolenfehler");
|
|
|
|
/* EIN AUSSCHNITT DER KARTE, nicht nur die ganze Seite. Auf einem
|
|
Vollbild von 2000 px Hoehe ist eine Pille von 28 px nicht zu
|
|
beurteilen -- ich habe sie beim ersten Hinsehen schlicht
|
|
uebersehen und fuer fehlend gehalten. Ein Bild, auf dem man
|
|
nichts erkennen kann, ist keine Kontrolle. */
|
|
const karteMitMarke = seite.locator(".eintrag-karte").filter(
|
|
{ has: seite.locator(".im-kalender") }).first();
|
|
await karteMitMarke.screenshot({ path: "server/pruef-terminregel-marke.png" })
|
|
.catch(() => {});
|
|
await seite.screenshot({ path: "server/pruef-terminregel.png", fullPage: true });
|
|
melde(" -- Bildschirmfoto: server/pruef-terminregel.png");
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
melde("");
|
|
melde(`${geprueft} Pruefungen, ${fehler} Fehler`
|
|
+ (offen ? `, ${offen} nicht nachsehbar` : ""));
|
|
melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
|
|
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest */ }
|
|
process.exit(fehler ? 1 : 0);
|