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
1059 lines
54 KiB
JavaScript
1059 lines
54 KiB
JavaScript
/* =====================================================================
|
|
Wie geht's dir? (15.09.2026)
|
|
|
|
Filipe, mit dem Bildschirmfoto der Kachel: „ich will dass du diese
|
|
seite perfektionnierst, denn gerade wenn ich drauf drücke geht die
|
|
entwicklungsseite auf."
|
|
|
|
---------------------------------------------------------------------
|
|
DER FEHLER WAR SCHLIMMER ALS EIN FALSCHER VERWEIS
|
|
|
|
Auf der Kachel steht „die Antworten sieht nur du" — und sie öffnete
|
|
eine Seite voller Namen und Beobachtungen über ANDERE Menschen. Die
|
|
erste Prüfung hier ist deshalb: Führt die Kachel dorthin, wo sie
|
|
hinführt zu führen verspricht?
|
|
|
|
---------------------------------------------------------------------
|
|
UND DIE EIGENTLICHE FRAGE: BLEIBT ES PRIVAT?
|
|
|
|
Ein Verlauf über das Befinden eines Menschen ist das Empfindlichste,
|
|
was dieses Haus speichert. Er existiert nur, weil er IHM hilft. Also:
|
|
|
|
Sieht DogFather ihn? (darf nicht)
|
|
Steht etwas davon im Protokoll? (nur DASS, nie WAS)
|
|
Ändert er die Ampel? (darf nicht)
|
|
|
|
Aufrufen mit: node server/pruef-befinden.mjs
|
|
===================================================================== */
|
|
|
|
import { mkdtempSync, rmSync, readFileSync } 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";
|
|
|
|
const PORT = await eigenerPort(import.meta, "pruef-befinden");
|
|
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-bef-"));
|
|
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-befinden");
|
|
await new Promise((r) => setTimeout(r, 700));
|
|
|
|
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);
|
|
/* WIE VIELE FRAGEN ES GIBT -- aus dem Katalog, nicht abgeschrieben.
|
|
|
|
Vier Zeilen dieser Datei nannten frueher feste Zahlen ("2 Runden x 6",
|
|
"zwoelf Antworten", "alle sechs Fragen"). Am 17.09.2026 wurden aus
|
|
sechs Fragen zwoelf, und sie waren ab da rot, ohne dass irgendetwas
|
|
kaputt war. Eine feste Zahl ist eine Rechnung von gestern. */
|
|
const anzahlFragen = (await import("./workspace-entwicklungs-punkte.js"))
|
|
.ENTWICKLUNG_PUNKTE.befinden.length;
|
|
/* Und wie viele davon der Kern sind -- aus demselben Grund abgeleitet. */
|
|
const kernAnzahl = (await import("./workspace-entwicklungs-punkte.js"))
|
|
.ENTWICKLUNG_PUNKTE.befinden.filter((p) => p.kern).length;
|
|
|
|
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
|
|
|
const CREW = "crew.dogfather-universe.com";
|
|
const HAUS = "workspace.dogfather-universe.com";
|
|
|
|
const { DatabaseSync } = await import("node:sqlite");
|
|
const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
|
|
const modul = await import("./workspace-befinden.js");
|
|
const kat = await import("./workspace-entwicklungs-punkte.js");
|
|
const rechte = await import("./rechte.js");
|
|
|
|
const oeffne = () => new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const jetzt = new Date().toISOString();
|
|
|
|
let idDogi, idFrida;
|
|
{
|
|
const d = oeffne();
|
|
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);
|
|
const 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;
|
|
};
|
|
idDogi = anlegen("Filipe", "admin", "CODE-DOGI-0001");
|
|
idFrida = anlegen("Frida", "modi", "CODE-MODI-0001");
|
|
d.close();
|
|
}
|
|
|
|
function ruf(pfad, host, methode, kopf = {}, koerper = null) {
|
|
return new Promise((fertig, schief) => {
|
|
const a = httpAnfrage({ host: "127.0.0.1", port: PORT, path: pfad, method: methode,
|
|
headers: { Host: host, ...(koerper ? { "Content-Type": "application/json" } : {}), ...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);
|
|
if (koerper) a.write(JSON.stringify(koerper));
|
|
a.end();
|
|
});
|
|
}
|
|
const json = (a) => { try { return JSON.parse(a.text); } catch { return {}; } };
|
|
async function anmelden(rolle, code, host) {
|
|
const r = await ruf("/workspace/api/anmelden", host, "POST", {}, { rolle, code });
|
|
return [].concat(r.kopf?.["set-cookie"] || []).map((c) => c.split(";")[0]).join("; ");
|
|
}
|
|
/* DOGFATHER AUF DER TEAM-ADRESSE.
|
|
|
|
Nicht aus Bequemlichkeit: Auf workspace. liefert der Server ihm
|
|
`bereiche: null` -- der Browser nimmt dort seine eigene Liste, und
|
|
die kennt diese Kachel gar nicht. Die Kachel, die Filipe im
|
|
Bildschirmfoto angetippt hat, steht auf crew. Wer sie auf der
|
|
falschen Adresse sucht, prueft an ihr vorbei. */
|
|
const kDogi = await anmelden("admin", "CODE-DOGI-0001", CREW);
|
|
const kFrida = await anmelden("modi", "CODE-MODI-0001", CREW);
|
|
ok(!!kDogi && !!kFrida, "DogFather und ein Modi sind angemeldet");
|
|
|
|
/* =======================================================================
|
|
1. DIE KACHEL HÄLT, WAS SIE VERSPRICHT
|
|
|
|
Der gemeldete Fehler. Er war schlimmer als ein falscher Verweis: Auf
|
|
der Kachel steht „die Antworten sieht nur du" -- und sie oeffnete
|
|
eine Seite voller Namen anderer Menschen.
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 1. Die Kachel ===");
|
|
{
|
|
const ich = json(await ruf("/workspace/api/ich", CREW, "GET", { cookie: kDogi }));
|
|
const alle = [...(ich.bereiche || []), ...(ich.bereiche_zusatz || [])];
|
|
const kachel = alle.find((k) => /Wie geht/i.test(k.name || ""));
|
|
ok(!!kachel, `die Kachel „${kachel?.name}“ steht bei DogFather`);
|
|
ok(kachel?.ziel === "befinden.html",
|
|
`und zeigt auf die eigene Seite (${kachel?.ziel})`);
|
|
/* GEGENPROBE -- ohne sie beweist die Zeile nichts: Die Kachel
|
|
„Entwicklung" zeigt weiterhin auf die Entwicklungsseite. */
|
|
/* Der Kachelname wurde am 19.09.2026 von "Entwicklung" zu "Eure
|
|
Aufgaben" geaendert (sie fuehrt auf den Aufgabenkatalog). Gesucht
|
|
wird deshalb ueber das ZIEL -- das aendert sich nicht, wenn jemand
|
|
den Namen nachschaerft. */
|
|
const andere = alle.find((k) => k.ziel === "entwicklung.html");
|
|
ok(andere?.ziel === "entwicklung.html",
|
|
`die Kachel „Entwicklung“ zeigt weiterhin dorthin (${andere?.ziel})`);
|
|
|
|
ok((rechte.tafelJetzt()["/workspace/befinden.html"] || []).includes("modi"),
|
|
"die Seite steht in der Rechtetafel und ein Modi darf sie öffnen");
|
|
ok(!(rechte.tafelJetzt()["/workspace/befinden.html"] || []).includes("creator"),
|
|
"die Agentur nicht — sie hat auch keine Entwicklungskarte");
|
|
|
|
const beiFrida = json(await ruf("/workspace/api/ich", CREW, "GET", { cookie: kFrida }));
|
|
const ihre = [...(beiFrida.bereiche || []), ...(beiFrida.bereiche_zusatz || [])]
|
|
.find((k) => /Wie geht/i.test(k.name || ""));
|
|
ok(ihre?.ziel === "befinden.html", "und bei einem Modi führt sie ebenfalls dorthin");
|
|
}
|
|
|
|
/* =======================================================================
|
|
2. SECHS FRAGEN, EIN RHYTHMUS
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 2. Sechs Fragen, ein Rhythmus ===");
|
|
{
|
|
const d = json(await ruf("/workspace/api/befinden", CREW, "GET", { cookie: kFrida }));
|
|
|
|
/* ==== KERN UND ROTATION (19.09.2026) ==============================
|
|
|
|
Filipe wollte "viel mehr Fragen" UND "kurz und knapp". Geloest
|
|
ueber fuenf feste Fragen plus drei wechselnde: Jede Runde bleibt
|
|
kurz, ueber die Monate wird alles abgedeckt -- und der Katalog
|
|
kann wachsen, ohne dass eine Runde laenger wird.
|
|
============================================================== */
|
|
const kern = kat.ENTWICKLUNG_PUNKTE.befinden.filter((p) => p.kern);
|
|
const rest = kat.ENTWICKLUNG_PUNKTE.befinden.filter((p) => !p.kern);
|
|
ok(kern.length > 0, `${kern.length} Fragen sind als Kern markiert`);
|
|
ok(rest.length > modul.ZUSATZ_PRO_RUNDE,
|
|
`${rest.length} wechseln sich ab — mehr als die ${modul.ZUSATZ_PRO_RUNDE} Plätze je Runde`);
|
|
ok(d.punkte?.length === kern.length + modul.ZUSATZ_PRO_RUNDE,
|
|
`${d.punkte?.length} Fragen in dieser Runde (${kern.length} feste + ${modul.ZUSATZ_PRO_RUNDE})`);
|
|
ok(d.punkte.length < kat.ENTWICKLUNG_SELBST.length,
|
|
`weniger als die ${kat.ENTWICKLUNG_SELBST.length} des Katalogs — genau darum geht es`);
|
|
ok(d.fragen_gesamt === kat.ENTWICKLUNG_SELBST.length,
|
|
"und die Seite sagt, wie viele es insgesamt gibt");
|
|
/* JEDE KERNFRAGE IST DABEI. Der Riegel: Waere eine davon in der
|
|
Rotation, erfuehre man die wichtigste Auskunft im Schnitt sechs
|
|
Wochen zu spaet. */
|
|
ok(kern.every((k) => d.punkte.some((p) => p.schluessel === k.schluessel)),
|
|
"jede Kernfrage ist in dieser Runde dabei");
|
|
/* GEGENPROBE: Es sind NICHT alle -- sonst waere die Rotation nur
|
|
behauptet. */
|
|
ok(rest.some((x) => !d.punkte.some((p) => p.schluessel === x.schluessel)),
|
|
"und mindestens eine wechselnde Frage ist diesmal NICHT dran");
|
|
/* IN KATALOGREIHENFOLGE. Wer sie jede Runde anders sortiert sieht,
|
|
liest sie jedes Mal neu statt sie wiederzuerkennen. */
|
|
const ordnung = kat.ENTWICKLUNG_PUNKTE.befinden.map((p) => p.schluessel);
|
|
const gezeigt = d.punkte.map((p) => p.schluessel);
|
|
ok(gezeigt.join() === [...gezeigt].sort((a, b) => ordnung.indexOf(a) - ordnung.indexOf(b)).join(),
|
|
"die Fragen stehen in der Reihenfolge des Katalogs");
|
|
|
|
/* ÜBER VIER RUNDEN KOMMT JEDE FRAGE DRAN. Eine Frage, die nie
|
|
drankommt, koennte man ebenso gut loeschen -- und genau das
|
|
passiert, wenn der Versatz und die Zahl der Fragen einen
|
|
gemeinsamen Teiler haben. */
|
|
{
|
|
/* DIE GRENZE WIRD GERECHNET, NICHT EINGETRAGEN (20.09.2026).
|
|
|
|
Hier stand fest „6 Runden". Das war richtig, solange es sieben
|
|
wechselnde Fragen bei drei je Runde gab. Mit dreizehn Fragen ist
|
|
es nur noch Zufall, ob die Zahl passt -- und eine Pruefung, die
|
|
bei jedem Katalogzuwachs von Hand nachgestellt werden muss, wird
|
|
irgendwann nachgestellt, statt den Fehler zu zeigen.
|
|
|
|
Der Anspruch ist stattdessen: Es darf nicht laenger dauern, als
|
|
es MUSS, plus eine Runde Luft. Weniger als ceil(wechselnde /
|
|
Plaetze) geht rechnerisch nicht; mehr als eine Runde darueber
|
|
heisst, dass die Auswahl sich im Kreis dreht.
|
|
|
|
GENAU SO IST DER FEHLER AUFGEFALLEN, den diese Zeile gefunden
|
|
hat: Eine erste Fassung der feldbewussten Rotation brauchte
|
|
ZWOELF Runden statt vier -- fast ein halbes Jahr, bis jede Frage
|
|
einmal gestellt war. */
|
|
const wechselnde = kat.ENTWICKLUNG_SELBST.filter((p) => !p.kern).length;
|
|
const mindestens = Math.ceil(wechselnde / modul.ZUSATZ_PRO_RUNDE);
|
|
const grenze = mindestens + 1;
|
|
|
|
const gesehen = new Set();
|
|
let gebraucht = null;
|
|
for (let r = 0; r < grenze * 4; r++) {
|
|
for (const p of modul.fragenFuer(idFrida, r).alle) gesehen.add(p.schluessel);
|
|
if (gebraucht === null && gesehen.size === kat.ENTWICKLUNG_SELBST.length) {
|
|
gebraucht = r + 1;
|
|
}
|
|
}
|
|
ok(gesehen.size === kat.ENTWICKLUNG_SELBST.length,
|
|
`jede der ${kat.ENTWICKLUNG_SELBST.length} Fragen kommt überhaupt dran (${gesehen.size})`);
|
|
ok(gebraucht !== null && gebraucht <= grenze,
|
|
`und zwar in ${gebraucht} Runden — rechnerisch nötig sind ${mindestens}, erlaubt ${grenze}`);
|
|
|
|
/* UND JEDE RUNDE STREUT ÜBER DIE FELDER. Vier Zusatzfragen aus
|
|
demselben Feld waeren kein Befinden mehr, sondern ein Interview
|
|
zu einem Thema -- genau das passiert, wenn der Ring dem nach
|
|
Themen sortierten Katalog folgt. */
|
|
let engste = 99;
|
|
for (let r = 0; r < 12; r++) {
|
|
const felder = new Set(modul.fragenFuer(idFrida, r).zusatz.map((p) => p.feld));
|
|
engste = Math.min(engste, felder.size);
|
|
}
|
|
ok(engste >= Math.min(3, modul.ZUSATZ_PRO_RUNDE),
|
|
`auch die engste Runde streut über mindestens 3 Felder (${engste})`);
|
|
/* Und die Auswahl ist STABIL: derselbe Stand, dieselbe Liste.
|
|
Zufall hiesse, dass bei jedem Neuladen etwas anderes dasteht. */
|
|
ok(modul.fragenFuer(idFrida, 2).alle.map((p) => p.schluessel).join()
|
|
=== modul.fragenFuer(idFrida, 2).alle.map((p) => p.schluessel).join(),
|
|
"zweimal dieselbe Runde ergibt zweimal dieselben Fragen");
|
|
ok(modul.fragenFuer(idFrida, 2).alle.map((p) => p.schluessel).join()
|
|
!== modul.fragenFuer(idFrida, 3).alle.map((p) => p.schluessel).join(),
|
|
"die nächste Runde ergibt andere — sonst wäre es keine Rotation");
|
|
}
|
|
|
|
/* Die Recherche: fuenf bis fuenfzehn, unter zwei Minuten. */
|
|
ok(d.punkte.length >= 5 && d.punkte.length <= 15,
|
|
"zwischen fünf und fünfzehn — kurz genug, dass man sie wirklich beantwortet");
|
|
ok(d.rhythmus_tage === modul.RHYTHMUS_TAGE,
|
|
`alle ${d.rhythmus_tage} Tage wird wieder gefragt`);
|
|
ok(d.rhythmus?.faellig === true, "am Anfang ist sofort etwas fällig");
|
|
ok(d.rhythmus?.letzte === null, "und es gab noch keine Runde");
|
|
ok(Array.isArray(d.verlauf) && d.verlauf.length === 0, "der Verlauf ist leer");
|
|
|
|
/* EINE HALBE RUNDE IST KEINE RUNDE. */
|
|
const halb = await ruf("/workspace/api/befinden/runde", CREW, "POST", { cookie: kFrida }, {});
|
|
ok(halb.code === 400, `ohne Antworten lässt sich nichts festhalten (${halb.code})`);
|
|
ok(json(halb).fehler === "unvollstaendig"
|
|
&& json(halb).offen === d.punkte.length,
|
|
`und die Absage sagt, wie viele fehlen (${json(halb).offen} von ${json(halb).gesamt})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
3. EINE RUNDE, EIN VERLAUF
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 3. Der Verlauf entsteht ===");
|
|
{
|
|
const d = json(await ruf("/workspace/api/befinden", CREW, "GET", { cookie: kFrida }));
|
|
for (const p of d.punkte) {
|
|
await ruf(`/workspace/api/entwicklung/mich/${p.schluessel}`, CREW, "PUT",
|
|
{ cookie: kFrida }, { stufe: "laeuft" });
|
|
}
|
|
const erste = await ruf("/workspace/api/befinden/runde", CREW, "POST", { cookie: kFrida }, {});
|
|
ok(erste.code === 201, `mit allen Antworten geht es (${erste.code})`);
|
|
ok(json(erste).anzahl === d.punkte.length,
|
|
`${json(erste).anzahl} Antworten sind in den Verlauf gewandert`);
|
|
|
|
const nach = json(await ruf("/workspace/api/befinden", CREW, "GET", { cookie: kFrida }));
|
|
ok(nach.verlauf.length === 1, `der Verlauf hat ${nach.verlauf.length} Runde`);
|
|
ok(nach.rhythmus.faellig === false,
|
|
"und es ist erst einmal nichts mehr fällig — die Seite fragt nicht jeden Tag");
|
|
ok(nach.rhythmus.vollstaendig === true, "die Runde gilt als vollständig");
|
|
|
|
/* ==== DER FEHLER, DEN DIESE PRUEFUNG SELBST FESTGESCHRIEBEN HAT ===
|
|
|
|
Hier stand bis zum 19.09.2026:
|
|
|
|
eine Antwort aendern
|
|
POST /runde
|
|
ok(zweite.code === 201, "eine zweite Runde geht auch sofort")
|
|
|
|
Gruen -- und ein Beleg fuer genau das Gegenteil dessen, was sie
|
|
pruefen sollte. Eine Runde galt naemlich als vollstaendig, sobald
|
|
zu jeder Frage IRGENDEIN Stand vorlag. Nach der ersten Runde ist
|
|
das immer der Fall: Die Antworten bleiben stehen. Ab da liess sich
|
|
jede weitere Runde abschliessen, OHNE eine einzige Frage
|
|
anzufassen -- die alten Antworten wanderten mit neuem Datum ein
|
|
zweites Mal in den Verlauf.
|
|
|
|
Der Verlauf zeigte dann eine gerade Linie, und die bedeutete nicht
|
|
"es ist gleich geblieben", sondern "niemand hat gefragt". Genau
|
|
die Sorte Zahl, die dieses Haus verbietet: eine, die etwas
|
|
Falsches behauptet, und der man glaubt.
|
|
|
|
Ab jetzt zaehlt nur, was NACH der letzten Runde gesetzt wurde.
|
|
============================================================== */
|
|
const leer = await ruf("/workspace/api/befinden/runde", CREW, "POST", { cookie: kFrida }, {});
|
|
ok(leer.code === 400,
|
|
`eine zweite Runde ohne neue Antwort geht NICHT (${leer.code})`);
|
|
ok(json(leer).offen === d.punkte.length,
|
|
`und zwar sind alle ${json(leer).offen} Fragen wieder offen — `
|
|
+ "die alten Antworten zaehlen nicht als neue");
|
|
|
|
const zwischen = json(await ruf("/workspace/api/befinden", CREW, "GET", { cookie: kFrida }));
|
|
/* KEINE EINZIGE gilt als frisch. Bei den Fragen, die schon beim
|
|
letzten Mal dran waren, steht die alte Antwort sichtbar da; bei den
|
|
neu hereinrotierten gibt es noch gar keine. Beides ist "offen". */
|
|
ok(zwischen.punkte.every((p) => !p.frisch),
|
|
"keine Antwort gilt als frisch — auch die vom letzten Mal nicht");
|
|
ok(zwischen.punkte.some((p) => p.meins && !p.frisch),
|
|
"die alten stehen trotzdem sichtbar da („vom letzten Mal“)");
|
|
|
|
/* GEGENPROBE: Mit frischen Antworten geht es sehr wohl. Ohne sie
|
|
waere die Zeile oben auch dann gruen, wenn ueberhaupt keine Runde
|
|
mehr moeglich waere. */
|
|
const zweiteFragen = zwischen.punkte;
|
|
for (const p of zweiteFragen) {
|
|
await ruf(`/workspace/api/entwicklung/mich/${p.schluessel}`, CREW, "PUT",
|
|
{ cookie: kFrida }, { stufe: p.schluessel === d.punkte[0].schluessel ? "hakt" : "laeuft" });
|
|
}
|
|
const zweite = await ruf("/workspace/api/befinden/runde", CREW, "POST", { cookie: kFrida }, {});
|
|
ok(zweite.code === 201, `mit frischen Antworten geht die zweite Runde (${zweite.code})`);
|
|
|
|
const nach2 = json(await ruf("/workspace/api/befinden", CREW, "GET", { cookie: kFrida }));
|
|
ok(nach2.verlauf.length === 2, `jetzt ${nach2.verlauf.length} Runden`);
|
|
const neu = nach2.verlauf[0].antworten[d.punkte[0].schluessel];
|
|
const alt = nach2.verlauf[1].antworten[d.punkte[0].schluessel];
|
|
ok(neu === "hakt" && alt === "laeuft",
|
|
`und man sieht die Veränderung (${alt} → ${neu})`);
|
|
/* DIE ANTWORTEN DER ALTEN RUNDE BLEIBEN, WIE SIE WAREN. Ein Verlauf,
|
|
der sich rueckwirkend aendert, ist kein Verlauf. */
|
|
ok(Object.keys(nach2.verlauf[1].antworten).length === d.punkte.length,
|
|
"die alte Runde steht unverändert da");
|
|
/* UND DIE ANALYSE SIEHT ALLE FRAGEN, nicht nur die dieser Runde --
|
|
der Verlauf hoert nicht auf, wenn eine Frage gerade nicht dran ist. */
|
|
ok(nach2.analyse?.zeilen?.length === kat.ENTWICKLUNG_SELBST.length,
|
|
`die Auswertung deckt alle ${kat.ENTWICKLUNG_SELBST.length} Fragen ab `
|
|
+ `(${nach2.analyse?.zeilen?.length}), nicht nur die ${nach2.punkte.length} dieser Runde`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
4. ES BLEIBT PRIVAT
|
|
|
|
Die eigentliche Frage. Ein Verlauf ueber das Befinden eines Menschen
|
|
ist das Empfindlichste, was dieses Haus speichert.
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 4. Privat ===");
|
|
{
|
|
/* DogFather bekommt SEINEN Verlauf, nicht ihren. */
|
|
const seiner = json(await ruf("/workspace/api/befinden", CREW, "GET", { cookie: kDogi }));
|
|
ok(seiner.verlauf.length === 0,
|
|
`DogFather hat einen eigenen, leeren Verlauf (${seiner.verlauf.length})`);
|
|
|
|
/* Es gibt gar keinen Weg zu einem fremden. */
|
|
const fremd = await ruf(`/workspace/api/befinden?person=${idFrida}`, CREW, "GET", { cookie: kDogi });
|
|
ok(json(fremd).verlauf?.length === 0,
|
|
"auch mit einer fremden Nummer in der Adresse bekommt er nur seinen eigenen");
|
|
|
|
/* Im Protokoll steht nur, DASS eine Runde war -- nie was darin stand. */
|
|
const d = oeffne();
|
|
const spur = d.prepare(
|
|
"SELECT detail FROM protokoll WHERE aktion = 'befinden_runde' ORDER BY id DESC").all();
|
|
const verlaufZeilen = d.prepare("SELECT COUNT(*) AS n FROM befinden_verlauf").get().n;
|
|
const runProRunde = d.prepare(
|
|
"SELECT COUNT(*) AS n FROM befinden_verlauf GROUP BY runde ORDER BY runde")
|
|
.all().map((z) => z.n);
|
|
d.close();
|
|
ok(spur.length === 2, `${spur.length} Runden stehen im Protokoll`);
|
|
ok(spur.every((z) => !/laeuft|hakt|waechst|unklar/.test(z.detail || "")),
|
|
`aber keine einzige Antwort darin (${spur[0]?.detail})`);
|
|
/* DIE ZAHL WIRD GERECHNET, NICHT ABGESCHRIEBEN.
|
|
|
|
Hier stand `=== 12` mit dem Kommentar "2 Runden x 6". Am
|
|
17.09.2026 wurden aus sechs Fragen zwoelf, und drei Zeilen in
|
|
dieser Datei waren ab da rot, ohne dass irgendetwas kaputt war.
|
|
Eine feste Zahl ist eine Rechnung von gestern. */
|
|
/* ABGELEITET, NICHT GERECHNET (19.09.2026, mit der Rotation).
|
|
|
|
Hier stand `2 * anzahlFragen` -- zwei Runden mal ALLE Fragen. Seit
|
|
eine Runde nur Kern plus drei Zusatzfragen enthaelt, stimmt das
|
|
nicht mehr. Und die naechste feste Zahl waere derselbe Fehler noch
|
|
einmal: Wer ZUSATZ_PRO_RUNDE aendert, haette sie wieder rot.
|
|
|
|
Gezaehlt wird deshalb, was WIRKLICH in den Runden steht. */
|
|
ok(verlaufZeilen === runProRunde.reduce((x, y) => x + y, 0),
|
|
`im Verlauf liegen ${verlaufZeilen} Antworten (${runProRunde.join(" + ")})`);
|
|
ok(runProRunde.length === 2 && runProRunde.every((n) => n === runProRunde[0]),
|
|
`zwei gleich grosse Runden zu je ${runProRunde[0]} Fragen`);
|
|
|
|
/* UND DIE AMPEL ZAEHLT WEITER NUR DEN AKTUELLEN STAND, ohne Namen. */
|
|
const a = await ruf("/workspace/api/entwicklung/ampel", CREW, "GET", { cookie: kDogi });
|
|
ok(a.code === 200, `die Ampel antwortet (${a.code})`);
|
|
ok(!/Frida/.test(a.text), "und nennt keinen Namen");
|
|
}
|
|
|
|
/* =======================================================================
|
|
5. DIE ENTWICKLUNGSSEITE TUT NUR NOCH EINE SACHE
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 5. Eine Seite, ein Zweck ===");
|
|
{
|
|
const e = readFileSync(new URL("../workspace/entwicklung.html", import.meta.url), "utf8");
|
|
const b = readFileSync(new URL("../workspace/befinden.html", import.meta.url), "utf8");
|
|
|
|
ok(/href="befinden\.html"/.test(e),
|
|
"die Entwicklungsseite verweist auf die sechs Fragen");
|
|
ok(!/id="meins"/.test(e),
|
|
"und zeichnet sie nicht mehr selbst — zwei Stände wären zwei Wahrheiten");
|
|
ok(/id="fragen"/.test(b) && /id="verlauf"/.test(b),
|
|
"die neue Seite hat die Fragen und den Verlauf");
|
|
/* AUF DER EIGENEN SEITE STEHT KEIN FREMDER NAME -- nicht einmal ein
|
|
Platz dafuer. */
|
|
ok(!/personen-block|meine-karte|abweichung/.test(b),
|
|
"und keinen einzigen Abschnitt über andere Menschen");
|
|
|
|
/* DIE SKRIPT-QUELLEN, NICHT DER ROHE TEXT (korrigiert 20.09.2026).
|
|
|
|
Hier stand `!/entwicklung\.js/.test(b)` -- eine Textsuche ueber die
|
|
GANZE Datei. Die schlug an einem KOMMENTAR an, der voellig zu
|
|
Recht die Serverdatei `workspace-entwicklung.js` benennt. Die
|
|
Pruefung meldete damit einen Fehler an einer Stelle, an der nichts
|
|
kaputt war, und stand der eigentlichen Frage im Weg.
|
|
|
|
Die eigentliche Frage lautet: Welche Dateien LAEDT die Seite? Die
|
|
steht in den `src`-Angaben und nirgends sonst. Dieselbe Lehre wie
|
|
bei pruef-schranke am 19.09.: Wer Text sucht, misst Text -- nicht
|
|
die Sache. */
|
|
const quellen = [...b.matchAll(/<script[^>]+src="([^"]+)"/g)]
|
|
/* Nur der Dateiname -- ohne Pfad und ohne Versionsstempel. Damit
|
|
braucht es hier kein Muster, das man falsch schreiben kann: Ein
|
|
Vergleich auf Gleichheit hat keine Randfaelle. (Der erste Anlauf
|
|
benutzte eine Wortgrenze in der Zeichenkette und bekam dadurch
|
|
ein echtes Steuerzeichen in die Datei -- die Pruefung verglich
|
|
danach gegen etwas, das es nirgends gibt, und die Gegenprobe war
|
|
still gruen. Genau die Sorte Pruefung, die nichts prueft.) */
|
|
.map((m) => m[1].split("?")[0].split("/").pop());
|
|
ok(quellen.length > 0, `die Seite laedt ueberhaupt Skripte (${quellen.length})`);
|
|
ok(quellen.includes("befinden.js"),
|
|
`sie laedt ihr eigenes Skript (${quellen.join(", ")})`);
|
|
ok(!quellen.includes("entwicklung.js"),
|
|
"und nicht das der Entwicklungsseite — zwei Stände wären zwei Wahrheiten");
|
|
}
|
|
|
|
/* =======================================================================
|
|
6. IM BROWSER
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 6. Im Browser ===");
|
|
{
|
|
const { chromium } = await import(
|
|
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
|
|
const browser = await chromium.launch();
|
|
const kontext = await browser.newContext({ viewport: { width: 1280, height: 1000 } });
|
|
const seite = await kontext.newPage();
|
|
const konsole = [];
|
|
seite.on("console", (m) => { if (m.type() === "error" && !/favicon|ERR_/i.test(m.text())) konsole.push(m.text()); });
|
|
seite.on("pageerror", (e) => konsole.push("Absturz: " + e.message));
|
|
|
|
/* ALS MODI, NICHT ALS DOGFATHER: Die Seite gehoert dem Menschen, der
|
|
sie oeffnet -- und der haeufigste Fall ist ein Modi. Der Keks kommt
|
|
von der Anmeldung oben, weil Playwright die Host-Kopfzeile beim
|
|
Seitenaufruf nicht setzen kann. */
|
|
await kontext.addCookies(kFrida.split("; ").filter(Boolean).map((teil) => {
|
|
const i = teil.indexOf("=");
|
|
return { name: teil.slice(0, i), value: teil.slice(i + 1), domain: "127.0.0.1", path: "/" };
|
|
}));
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/start.html`, { waitUntil: "networkidle" });
|
|
await seite.waitForTimeout(700);
|
|
|
|
/* DER GEMELDETE FEHLER, IM BROWSER NACHGESTELLT: auf die Kachel
|
|
druecken und nachsehen, wo man landet. */
|
|
const ziel = await seite.evaluate(() => {
|
|
const k = [...document.querySelectorAll('a')]
|
|
.find((a) => /Wie geht/i.test(a.textContent || ''));
|
|
return k ? k.getAttribute('href') : null;
|
|
});
|
|
ok(/befinden\.html/.test(ziel || ""),
|
|
`die Kachel auf der Startseite führt zu ${ziel}`);
|
|
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/befinden.html`, { waitUntil: "networkidle" });
|
|
await seite.waitForTimeout(900);
|
|
|
|
const stand = await seite.evaluate(() => ({
|
|
fragen: document.querySelectorAll('.e-punkt--meins').length,
|
|
knoepfe: document.querySelectorAll('.e-wahl__knopf').length,
|
|
takt: document.getElementById('takt')?.hidden === false,
|
|
taktText: document.getElementById('takt-satz')?.textContent.trim() || '',
|
|
verlauf: document.getElementById('verlauf-block')?.hidden === false,
|
|
spalten: document.querySelectorAll('.b-verlauf__zeile--kopf .b-verlauf__datum').length,
|
|
punkte: document.querySelectorAll('.b-verlauf__punkt').length,
|
|
/* Die Zeilen OHNE die Kopfzeile -- sie traegt die Datumsangaben,
|
|
keine Antworten. */
|
|
zeilen: document.querySelectorAll('.b-verlauf__zeile:not(.b-verlauf__zeile--kopf)').length,
|
|
text: document.querySelector('main')?.innerText || '',
|
|
}));
|
|
if (process.env.SCHIRM) {
|
|
await seite.screenshot({
|
|
path: "C:/Users/qciga/Documents/Obelix/DogiHompage/blick-befinden.png",
|
|
fullPage: true });
|
|
}
|
|
ok(stand.fragen >= 5, `${stand.fragen} Fragen stehen auf der Seite`);
|
|
ok(stand.knoepfe === stand.fragen * 4, `mit je vier Knöpfen (${stand.knoepfe})`);
|
|
ok(stand.takt === true, `der Rhythmus steht oben: „${stand.taktText.slice(0, 70)}“`);
|
|
/* FRIDA HAT ZWEI RUNDEN -- bei ihr MUSS er dastehen. */
|
|
ok(stand.verlauf === true, "ihr Verlauf steht auf der Seite");
|
|
ok(stand.spalten === 2, `mit zwei Ständen nebeneinander (${stand.spalten})`);
|
|
/* Zeilen x Spalten des Gitters. Die Zeilen sind die Fragen MIT
|
|
GESCHICHTE -- nach zwei Runden mit Rotation sind das mehr als die
|
|
acht dieser Runde und weniger als alle zwoelf. Gerechnet, nicht
|
|
angenommen: Eine feste Zahl waere hier beim naechsten Umbau still
|
|
falsch. */
|
|
ok(stand.punkte === stand.zeilen * stand.spalten && stand.punkte > 0,
|
|
`${stand.punkte} Felder im Gitter (${stand.zeilen} Fragen x ${stand.spalten} Stände)`);
|
|
ok(stand.zeilen > kernAnzahl && stand.zeilen <= anzahlFragen,
|
|
`${stand.zeilen} Fragen haben schon eine Geschichte — mehr als der Kern (${kernAnzahl}), `
|
|
+ `weniger als der ganze Katalog (${anzahlFragen})`);
|
|
/* KEIN FREMDER NAME AUF DEM BILDSCHIRM. Ihr eigener darf oben in der
|
|
Kopfleiste stehen -- gemessen wird deshalb nur der Inhalt, und
|
|
gesucht wird nach DogFather, also nach dem, der hier nichts zu
|
|
suchen hat. */
|
|
ok(!/Filipe/.test(stand.text), "und kein anderer Mensch steht darauf");
|
|
|
|
/* ==== ZWEI DINGE, DIE KEIN ZAHLENVERGLEICH FINDET ================
|
|
|
|
Beide sind beim Ansehen des Bildschirmfotos aufgefallen, nicht
|
|
beim Lesen der Ausgabe -- und beide waren wahr und trotzdem
|
|
irrefuehrend.
|
|
|
|
1. Der Satz "diese Runde sind 8 von 12 dran" stand nur, solange
|
|
etwas offen oder faellig war. Im RUHEZUSTAND -- dem, den man am
|
|
haeufigsten sieht -- fehlte er. Wer dann acht Fragen zaehlt, wo
|
|
zwoelf im Katalog stehen, haelt vier fuer verschwunden.
|
|
|
|
2. "Das war deine Antwort beim letzten Mal" stand an JEDER Frage,
|
|
unmittelbar nachdem man sie beantwortet und die Runde
|
|
abgeschlossen hatte. Technisch richtig (die Antwort ist vor dem
|
|
Rundenabschluss gesetzt), als Auskunft Unsinn.
|
|
============================================================== */
|
|
ok(/von \d+ Fragen dran/.test(stand.taktText),
|
|
`der Satz erklaert die kurze Runde auch im Ruhezustand: `
|
|
+ `„…${(stand.taktText.match(/Diese Runde[^.]*\./) || [""])[0].slice(0, 60)}…“`);
|
|
const ruhig = await seite.evaluate(() => ({
|
|
alt: document.querySelectorAll('.e-punkt[data-alt="ja"]').length,
|
|
hinweise: document.querySelectorAll('.e-punkt__alt').length,
|
|
kernmarken: document.querySelectorAll('.e-punkt__marke').length,
|
|
}));
|
|
ok(ruhig.alt === 0 && ruhig.hinweise === 0,
|
|
`direkt nach einer Runde steht nirgends „vom letzten Mal“ (${ruhig.hinweise})`);
|
|
ok(ruhig.kernmarken === kernAnzahl,
|
|
`die ${kernAnzahl} festen Fragen sind als solche gekennzeichnet (${ruhig.kernmarken})`);
|
|
|
|
/* GEGENPROBE: Ist eine Runde wirklich faellig, MUSS der Hinweis da
|
|
sein -- sonst hiesse die Zeile oben nur, dass er nie erscheint.
|
|
Die Runde wird dafuer um mehr als den Rhythmus zurueckdatiert;
|
|
die Uhr laesst sich nicht vorstellen. */
|
|
{
|
|
/* ALLE Runden verschieben, nicht nur die letzte.
|
|
|
|
Erster Anlauf datierte nur die juengste zurueck -- danach war
|
|
die ZWEITGROESSTE die juengste, und die lag weiterhin auf heute.
|
|
Die Seite meldete folgerichtig "ruhig", und die Gegenprobe war
|
|
rot, ohne dass an der Sache etwas falsch war.
|
|
|
|
In JavaScript gerechnet und nicht in SQL: `datetime(runde, '-15
|
|
days')` gibt ein anderes Format zurueck (Leerzeichen statt T,
|
|
ohne Z) -- Date.parse liest das als ORTSZEIT, und die Rechnung
|
|
waere danach um zwei Stunden daneben. */
|
|
const versatz = (modul.RHYTHMUS_TAGE + 1) * 86400000;
|
|
const dd = oeffne();
|
|
const runden = dd.prepare(
|
|
"SELECT DISTINCT runde FROM befinden_verlauf WHERE person_id = ?").all(idFrida)
|
|
.map((z) => z.runde);
|
|
const setzen = dd.prepare(
|
|
"UPDATE befinden_verlauf SET runde = ?, am = ? WHERE person_id = ? AND runde = ?");
|
|
const alteStaende = runden.map((r) => [r, new Date(Date.parse(r) - versatz).toISOString()]);
|
|
for (const [vorher, nachher] of alteStaende) setzen.run(nachher, nachher, idFrida, vorher);
|
|
|
|
/* UND DIE ANTWORTEN GLEICH MIT. Im Betrieb werden sie gesetzt und
|
|
kurz darauf die Runde abgeschlossen -- sie sind also IMMER etwas
|
|
aelter als die Runde. Nur die Runden zurueckzudatieren erzeugte
|
|
einen Zustand, den es nie gibt: Antworten von heute unter einer
|
|
Runde von vor zwei Wochen. Die Seite hielt sie folgerichtig fuer
|
|
frisch, und die Gegenprobe war rot an der falschen Stelle. */
|
|
const antworten = dd.prepare(
|
|
`SELECT schluessel, am FROM entwicklung_stand
|
|
WHERE person_id = ? AND von_id = ?`).all(idFrida, idFrida);
|
|
const setzenA = dd.prepare(
|
|
`UPDATE entwicklung_stand SET am = ?
|
|
WHERE person_id = ? AND von_id = ? AND schluessel = ?`);
|
|
const alteAntworten = antworten.map((z) =>
|
|
[z.schluessel, z.am, new Date(Date.parse(z.am) - versatz).toISOString()]);
|
|
for (const [k, , nachher] of alteAntworten) setzenA.run(nachher, idFrida, idFrida, k);
|
|
dd.close();
|
|
|
|
await seite.reload({ waitUntil: "networkidle" });
|
|
await seite.waitForTimeout(900);
|
|
const faellig = await seite.evaluate(() => ({
|
|
lage: document.getElementById('takt')?.dataset.lage || '',
|
|
alt: document.querySelectorAll('.e-punkt[data-alt="ja"]').length,
|
|
hinweise: document.querySelectorAll('.e-punkt__alt').length,
|
|
fragen: document.querySelectorAll('.e-punkt--meins').length,
|
|
knopf: !document.getElementById('takt-knopf')?.hidden,
|
|
}));
|
|
ok(faellig.lage === "faellig", `jetzt ist eine Runde fällig (${faellig.lage})`);
|
|
ok(faellig.hinweise > 0 && faellig.alt > 0,
|
|
`und die alten Antworten sind als solche gekennzeichnet (${faellig.hinweise} von ${faellig.fragen})`);
|
|
/* UND DER KNOPF IST WEG, solange nichts bestaetigt ist. Genau das
|
|
war der Fehler: Er stand da und schloss eine leere Runde ab. */
|
|
ok(faellig.knopf === false,
|
|
"der Knopf zum Abschließen ist weg, solange nichts bestätigt ist");
|
|
|
|
/* ZURUECKDREHEN. Ohne das liefe der Rest dieser Datei auf einem
|
|
Stand, den die Pruefung selbst verbogen hat -- Abschnitt 7 fand
|
|
danach ein "besser" statt eines Musters und meldete fuenf
|
|
Fehler, die keine waren. Genau die Sorte Befund, die man
|
|
stundenlang im falschen Code sucht. */
|
|
const dz = oeffne();
|
|
const zurueckSetzen = dz.prepare(
|
|
"UPDATE befinden_verlauf SET runde = ?, am = ? WHERE person_id = ? AND runde = ?");
|
|
for (const [vorher, nachher] of alteStaende) zurueckSetzen.run(vorher, vorher, idFrida, nachher);
|
|
const zurueckA = dz.prepare(
|
|
`UPDATE entwicklung_stand SET am = ?
|
|
WHERE person_id = ? AND von_id = ? AND schluessel = ?`);
|
|
for (const [k, vorher] of alteAntworten) zurueckA.run(vorher, idFrida, idFrida, k);
|
|
dz.close();
|
|
const zurueck = json(await ruf("/workspace/api/befinden", CREW, "GET", { cookie: kFrida }));
|
|
ok(zurueck.rhythmus?.letzte === runden.sort().at(-1)
|
|
&& zurueck.rhythmus?.faellig === false,
|
|
"der Stand ist danach wieder so, wie er war");
|
|
}
|
|
|
|
/* GEGENPROBE mit einem zweiten Menschen: DogFather hat keine einzige
|
|
Runde. Bei ihm muss derselbe Block WEG sein -- sonst hiesse „er
|
|
erscheint ab der zweiten Runde" nur, dass er immer erscheint. */
|
|
const zweiter = await browser.newContext({ viewport: { width: 1280, height: 1000 } });
|
|
await zweiter.addCookies(kDogi.split("; ").filter(Boolean).map((teil) => {
|
|
const i = teil.indexOf("=");
|
|
return { name: teil.slice(0, i), value: teil.slice(i + 1), domain: "127.0.0.1", path: "/" };
|
|
}));
|
|
const seite2 = await zweiter.newPage();
|
|
await seite2.goto(`http://127.0.0.1:${PORT}/workspace/befinden.html`, { waitUntil: "networkidle" });
|
|
await seite2.waitForTimeout(800);
|
|
const ohne = await seite2.evaluate(() => ({
|
|
fragen: document.querySelectorAll('.e-punkt--meins').length,
|
|
verlauf: document.getElementById('verlauf-block')?.hidden === false,
|
|
knopf: document.getElementById('takt-knopf')?.hidden === false,
|
|
}));
|
|
ok(ohne.fragen >= 5, `auch bei ihm stehen die Fragen da (${ohne.fragen})`);
|
|
ok(ohne.verlauf === false, "aber kein Verlauf — er hat noch keine Runde");
|
|
ok(ohne.knopf === false,
|
|
"und kein Knopf zum Abschließen, solange nichts beantwortet ist");
|
|
|
|
ok(konsole.length === 0,
|
|
konsole.length ? "Fehler im Browser: " + konsole[0].slice(0, 90) : "keine Fehler im Browser");
|
|
await browser.close();
|
|
}
|
|
|
|
/* =======================================================================
|
|
7. WAS DEIN VERLAUF SAGT
|
|
|
|
Filipe: „mach mir auch eine analyse, mach eine perfektion draus. eine
|
|
professionelle auch mit infos und so aus aller welt was tiktok
|
|
angeht."
|
|
|
|
Eine Auswertung über das eigene Befinden ist das Empfindlichste, was
|
|
dieses Haus rechnet. Also dieselben drei Fragen wie beim Verlauf:
|
|
Sagt sie das Richtige? Sagt sie es nur IHR? Und sagt sie gar nichts,
|
|
wenn sie noch nichts sagen kann?
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 7. Die Analyse ===");
|
|
{
|
|
const holen = async (keks, host) =>
|
|
json(await ruf("/workspace/api/befinden", host, "GET", { cookie: keks }));
|
|
|
|
/* Stand nach Abschnitt 3: zwei Runden. In der zweiten hakt der erste
|
|
Punkt, vorher lief er -- das ist „neu", noch kein Muster. */
|
|
const d = await holen(kFrida, CREW);
|
|
const erste = d.punkte[0].schluessel;
|
|
ok(d.analyse?.moeglich === true, `mit ${d.analyse?.runden} Runden gibt es eine Analyse`);
|
|
ok(d.analyse.zeilen.length === anzahlFragen,
|
|
`sie geht alle ${d.analyse.zeilen.length} Fragen des Katalogs durch, `
|
|
+ `nicht nur die ${d.punkte.length} dieser Runde und nicht nur die schlechten`);
|
|
const z1 = d.analyse.zeilen.find((z) => z.schluessel === erste);
|
|
ok(z1.art === "neu", `der frisch hakende Punkt gilt als neu, nicht als Muster (${z1.art})`);
|
|
ok(/noch kein Muster/i.test(d.analyse.satz),
|
|
`und der Satz oben sagt genau das: „${d.analyse.satz.slice(0, 60)}…“`);
|
|
|
|
/* DIE EINORDNUNG IST DER TEIL, DER „AUS ALLER WELT" HEISST. Sie steht
|
|
bei JEDER Zeile -- auch bei denen, die laufen. */
|
|
ok(d.analyse.zeilen.every((z) => z.welt && z.welt.length > 40),
|
|
`zu jeder der ${anzahlFragen} Fragen steht ein belegter Befund`);
|
|
ok(new Set(d.analyse.zeilen.map((z) => z.welt)).size === anzahlFragen,
|
|
"und jeder ist ein anderer — kein Satz, der zu allen passen würde");
|
|
/* „Was hilft" NUR dort, wo es etwas zu tun gibt. */
|
|
ok(d.analyse.zeilen.filter((z) => z.hilft).length === 1,
|
|
`ein Rat steht nur an der Stelle, an der es hakt (${d.analyse.zeilen.filter((z) => z.hilft).length})`);
|
|
|
|
/* ---- Und jetzt dasselbe ein zweites Mal: daraus wird ein Muster ---- */
|
|
for (const p of d.punkte) {
|
|
await ruf(`/workspace/api/entwicklung/mich/${p.schluessel}`, CREW, "PUT",
|
|
{ cookie: kFrida }, p.schluessel === erste
|
|
? { stufe: "hakt", anlass: "Immer noch dasselbe." } : { stufe: "laeuft" });
|
|
}
|
|
const dritte = await ruf("/workspace/api/befinden/runde", CREW, "POST", { cookie: kFrida }, {});
|
|
ok(dritte.code === 201, `eine dritte Runde (${dritte.code})`);
|
|
|
|
const n = await holen(kFrida, CREW);
|
|
const z2 = n.analyse.zeilen.find((z) => z.schluessel === erste);
|
|
ok(z2.art === "dauerhaft", `jetzt ist es ein Muster (${z2.art})`);
|
|
/* "MALE", NICHT "RUNDEN": Mit der Rotation ist eine Frage nicht in
|
|
jeder Runde dran -- gezaehlt wird, wie oft sie gestellt wurde. */
|
|
ok(z2.male === modul.MUSTER_AB, `über ${z2.male} Male hintereinander gleich`);
|
|
ok(z2.gefragt >= z2.male,
|
|
`und sie war insgesamt ${z2.gefragt}x dran — die Zahl gibt es wirklich`);
|
|
ok(/kein schlechter Tag/i.test(n.analyse.satz),
|
|
`und der Satz sagt, worauf es ankommt: „${n.analyse.satz.slice(0, 64)}…“`);
|
|
/* DIE REIHENFOLGE MACHT DIE AUSSAGE -- das Muster gehört nach oben.
|
|
Sortiert wird im Browser; hier wird geprüft, dass die Angabe dafür
|
|
überhaupt mitkommt. */
|
|
ok(n.analyse.zahlen.dauerhaft === 1 && n.analyse.zahlen.neu === 0,
|
|
`die Zahlen daneben stimmen (${JSON.stringify(n.analyse.zahlen)})`);
|
|
|
|
/* ---- Sie gehört ihr allein ---- */
|
|
const seine = await holen(kDogi, CREW);
|
|
ok(seine.analyse?.moeglich === false,
|
|
"DogFather bekommt seine eigene — und die ist leer");
|
|
ok(!JSON.stringify(seine).includes("Immer noch dasselbe"),
|
|
"nichts von ihr steht darin");
|
|
|
|
/* ---- Und ohne zweite Runde gibt es sie gar nicht ---- */
|
|
ok(seine.analyse.ab === modul.MUSTER_AB,
|
|
`sie sagt, ab wann es sie gibt (ab ${seine.analyse.ab} Runden)`);
|
|
ok(Array.isArray(seine.analyse.zeilen) && seine.analyse.zeilen.length === 0,
|
|
"und liefert bis dahin keine einzige Zeile");
|
|
|
|
/* ---- Im Protokoll steht weiterhin nichts davon ---- */
|
|
const db = oeffne();
|
|
const spuren = db.prepare("SELECT aktion, detail FROM protokoll").all();
|
|
db.close();
|
|
ok(!spuren.some((x) => /analyse/i.test(x.aktion || "")),
|
|
"eine Analyse ist kein Vorgang — sie steht in keinem Protokoll");
|
|
ok(!spuren.some((x) => /Immer noch dasselbe/.test(x.detail || "")),
|
|
"und ihr Anlass erst recht nicht");
|
|
}
|
|
|
|
/* =======================================================================
|
|
8. DIE ANALYSE AUF DEM BILDSCHIRM
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 8. Die Analyse im Browser ===");
|
|
{
|
|
const { chromium } = await import(
|
|
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
|
|
const browser = await chromium.launch();
|
|
const kontext = await browser.newContext({ viewport: { width: 1280, height: 1000 } });
|
|
await kontext.addCookies(kFrida.split("; ").filter(Boolean).map((teil) => {
|
|
const i = teil.indexOf("=");
|
|
return { name: teil.slice(0, i), value: teil.slice(i + 1), domain: "127.0.0.1", path: "/" };
|
|
}));
|
|
const seite = await kontext.newPage();
|
|
const konsole = [];
|
|
seite.on("console", (m) => { if (m.type() === "error" && !/favicon|ERR_/i.test(m.text())) konsole.push(m.text()); });
|
|
seite.on("pageerror", (e) => konsole.push("Absturz: " + e.message));
|
|
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/befinden.html`, { waitUntil: "networkidle" });
|
|
await seite.waitForTimeout(1000);
|
|
|
|
/* EIN RESUEMEE, NICHT NEUN (umgedreht am 22.09.2026).
|
|
================================================================
|
|
Bis hierher verlangte diese Stelle das GEGENTEIL: eine Zeile je
|
|
Frage, zu jeder eine Einordnung. Genau das hat Filipe mehrfach
|
|
abgeschafft ("das resume unten auf jeder frage hab ich auch schon
|
|
mehrmals gesagt muss weg ich will ein ganzes gesamtresume").
|
|
|
|
Die Pruefung ist beim Umbau ROT geworden -- richtig so, sie hat
|
|
die Aenderung bemerkt. Und sie hat dabei etwas gerettet: Die
|
|
TikTok-Quelle war beim Umzug ins Fazit verlorengegangen, und die
|
|
hatte Filipe ausdruecklich bestellt. */
|
|
const a = await seite.evaluate(() => ({
|
|
alterBlock: !!document.getElementById("analyse-block"),
|
|
alteZeilen: document.querySelectorAll(".b-analyse__zeile").length,
|
|
fazitDa: document.getElementById("fazit-block")?.hidden === false,
|
|
schrittDa: document.getElementById("fazit-schritt")?.hidden === false,
|
|
schrittTitel: document.getElementById("fazit-schritt-titel")?.textContent.trim() || "",
|
|
schrittWas: document.getElementById("fazit-schritt-was")?.textContent.trim() || "",
|
|
schrittWelt: document.getElementById("fazit-schritt-welt")?.textContent.trim() || "",
|
|
team: document.getElementById("fazit-team")?.textContent.trim() || "",
|
|
quellen: [...document.querySelectorAll(".b-fazit__quelle-hin")].map((x) => x.href),
|
|
blank: [...document.querySelectorAll(".b-fazit__quelle-hin")]
|
|
.every((x) => x.rel.includes("noopener")),
|
|
}));
|
|
|
|
ok(!a.alterBlock && a.alteZeilen === 0,
|
|
`unter den Fragen steht KEIN Resuemee mehr (${a.alteZeilen} Zeilen)`);
|
|
ok(a.fazitDa, "stattdessen EIN Gesamtresuemee");
|
|
|
|
/* DER SCHRITT: genau einer, mit Inhalt. Eine Pruefung, die nur
|
|
fragt "ist der Kasten da", waere auch bei leerem Text gruen. */
|
|
ok(a.schrittDa, "es nennt einen naechsten Schritt");
|
|
ok(a.schrittWas.length > 40,
|
|
`und der sagt konkret, was hilft (${a.schrittWas.length} Zeichen)`);
|
|
ok(a.schrittWelt.length > 40,
|
|
`mit dem Grund daneben, warum es dieser ist (${a.schrittWelt.length} Zeichen)`);
|
|
|
|
/* GENAU EINER. "Mindestens einer" waere auch bei neun gruen -- also
|
|
genau bei dem Zustand, den Filipe abgeschafft hat. */
|
|
const wieVieleSchritte = await seite.evaluate(
|
|
() => document.querySelectorAll(".b-fazit__schritt").length);
|
|
ok(wieVieleSchritte === 1, `und zwar genau einen (${wieVieleSchritte})`);
|
|
|
|
ok(a.team.length > 30, `das Team kommt vor (${a.team.length} Zeichen)`);
|
|
/* DER TEAM-SATZ IST ABGELEITET, NICHT IMMER DERSELBE. Ein Spruch,
|
|
der bei jeder Lage gleich lautet, wird beim dritten Mal
|
|
ueberlesen. Geprueft wird das hier ueber den Server (Abschnitt
|
|
zum Resuemee), hier nur, dass er ueberhaupt zur Lage passt. */
|
|
ok(!/gemeinsam schaffen wir/i.test(a.team),
|
|
"und ist kein Spruch, der immer passt");
|
|
|
|
ok(a.quellen.length === 3, `${a.quellen.length} Quellen stehen darunter`);
|
|
ok(a.quellen.some((x) => /tiktok/i.test(x)),
|
|
"darunter eine zu TikTok — danach hatte Filipe ausdrücklich gefragt");
|
|
ok(a.blank, "die Verweise öffnen ohne Zugriff aufs eigene Fenster (noopener)");
|
|
|
|
/* ---- DIE FELDER UND DIE EINE ZUSAMMENFASSUNG (20.09.2026) --------
|
|
|
|
Filipe: „viel mehr fragen aber kategorisiert … und am ende EINE
|
|
gesamte zusammenfassung."
|
|
|
|
Gemessen wird hier NICHT, ob ein Text dasteht, sondern:
|
|
* dass die Fragen unter Überschriften stehen -- und zwar unter
|
|
MEHREREN, sonst ist es eine Überschrift über allem und keine
|
|
Gliederung;
|
|
* dass jede Frage unter einer steht und keine darüber hängt;
|
|
* dass die Zusammenfassung GENAU EINMAL vorkommt. Eine je
|
|
Kategorie war ausdrücklich nicht gewünscht, und eine Prüfung,
|
|
die nur "mindestens eine" verlangt, würde sechs durchlassen. */
|
|
const g = await seite.evaluate(() => {
|
|
const felder = [...document.querySelectorAll("#fragen .e-feld")];
|
|
const fragen = [...document.querySelectorAll("#fragen .e-punkt")];
|
|
/* Wie viele Fragen stehen VOR der ersten Überschrift? Jede davon
|
|
hängt ohne Thema in der Luft. */
|
|
const ersteFeldPos = felder.length
|
|
? [...document.getElementById("fragen").children].indexOf(felder[0]) : -1;
|
|
const kinder = [...document.getElementById("fragen").children];
|
|
const heimatlos = kinder.slice(0, ersteFeldPos < 0 ? kinder.length : ersteFeldPos)
|
|
.filter((k) => k.classList.contains("e-punkt")).length;
|
|
const fazit = document.getElementById("fazit-block");
|
|
return {
|
|
felder: felder.length,
|
|
feldNamen: felder.map((f) => f.querySelector(".e-feld__wort")?.textContent.trim()),
|
|
fragen: fragen.length,
|
|
heimatlos,
|
|
fazitDa: fazit && fazit.hidden === false,
|
|
fazitAnzahl: document.querySelectorAll(".b-fazit").length,
|
|
kopf: document.getElementById("fazit-kopf")?.textContent.trim() || "",
|
|
traegt: document.querySelectorAll("#fazit-traegt-liste li").length,
|
|
schluss: document.getElementById("fazit-schluss")?.textContent.trim() || "",
|
|
zahlen: document.getElementById("fazit-zahlen")?.textContent.trim() || "",
|
|
};
|
|
});
|
|
ok(g.felder >= 3,
|
|
`die Fragen stehen unter ${g.felder} Feldüberschriften — nicht in einer Reihe`);
|
|
ok(g.heimatlos === 0,
|
|
`keine Frage hängt über der ersten Überschrift (${g.heimatlos})`);
|
|
ok(g.feldNamen.every((x) => x && x.length > 3),
|
|
`jede Überschrift hat einen Namen (${g.feldNamen.join(", ")})`);
|
|
ok(g.fazitDa === true, "die Zusammenfassung steht am Ende der Fragen");
|
|
ok(g.fazitAnzahl === 1,
|
|
`und zwar GENAU EINMAL (${g.fazitAnzahl}) — eine je Kategorie war ausdrücklich nicht gewünscht`);
|
|
ok(g.kopf.length > 30, `mit einem ganzen Satz („${g.kopf.slice(0, 52)}…")`);
|
|
ok(g.schluss.length > 20, `und einem Schlusssatz („${g.schluss.slice(0, 46)}…")`);
|
|
ok(/\d+ Fragen aus \d+ Feldern/.test(g.zahlen),
|
|
`die Belegzahlen stehen darunter („${g.zahlen}“)`);
|
|
|
|
/* ---- EINE FLAECHE, UND JEDER KOPF UEBER SEINEN EIGENEN FRAGEN
|
|
(23.09.2026) ----------------------------------------------
|
|
|
|
Filipe: „ich will dass diese seite viel übersichtlicher aussieht,
|
|
es soll eine riesßen kachel sein wo alles viel besser und geiler
|
|
aussieht.“
|
|
|
|
Die Fragen liegen jetzt in EINER Flaeche, darin je Gruppe ein
|
|
Abschnitt mit Kopf und eigenem Raster.
|
|
|
|
WARUM HIER GEMESSEN WIRD UND NICHT NACHGESEHEN, OB ES IM HTML
|
|
RICHTIG VERSCHACHTELT IST: Genau das war es die ganze Zeit. Der
|
|
Kopf stand im richtigen Abschnitt -- und trotzdem oben rechts
|
|
neben den Fragen der naechsten Gruppe, weil `columns: 2` auf dem
|
|
Behaelter den Abschnitt mitten durchgeschnitten hat. Eine Pruefung
|
|
auf die Verschachtelung waere dabei gruen geblieben; nachgemessen
|
|
war sie es auch (`kopfInGruppe: true`), waehrend das Bild falsch war.
|
|
|
|
Gemessen wird deshalb, WO die Kaesten liegen:
|
|
1. jede Frage unterhalb des Kopfes IHRER Gruppe,
|
|
2. keine Gruppe ragt in die naechste hinein.
|
|
Gegenprobe gefahren: mit dem Spaltensatz zurueck faellt (1) auf
|
|
5 von 6 und (2) auf 3 von 5. Die Pruefung kann also auch
|
|
„nicht in Ordnung“ sagen. */
|
|
const lage = await seite.evaluate(() => {
|
|
const gruppen = [...document.querySelectorAll(".b-gruppe")];
|
|
const kasten = gruppen.map((x) => x.getBoundingClientRect());
|
|
let gestapelt = 0;
|
|
for (let i = 1; i < kasten.length; i++) {
|
|
if (kasten[i].top >= kasten[i - 1].bottom - 2) gestapelt++;
|
|
}
|
|
return {
|
|
flaechen: document.querySelectorAll(".b-flaeche").length,
|
|
gruppen: gruppen.length,
|
|
fragen: document.querySelectorAll("#fragen .e-punkt").length,
|
|
/* Keine Frage darf ausserhalb einer Gruppe haengen. */
|
|
ausserhalb: [...document.querySelectorAll("#fragen .e-punkt")]
|
|
.filter((f) => !f.closest(".b-gruppe")).length,
|
|
kopfUeberFragen: gruppen.filter((gr) => {
|
|
const k = gr.querySelector(".e-feld");
|
|
if (!k) return true;
|
|
const unten = k.getBoundingClientRect().bottom;
|
|
return [...gr.querySelectorAll(".e-punkt")]
|
|
.every((f) => f.getBoundingClientRect().top >= unten - 2);
|
|
}).length,
|
|
gestapelt,
|
|
paare: Math.max(0, kasten.length - 1),
|
|
/* Kein seitliches Wegrutschen -- eine Flaeche, die breiter ist
|
|
als das Fenster, schiebt die ganze Seite. */
|
|
breiter: document.documentElement.scrollWidth > innerWidth + 1,
|
|
};
|
|
});
|
|
ok(lage.flaechen === 1,
|
|
`die Fragen liegen in EINER Flaeche (${lage.flaechen})`);
|
|
ok(lage.gruppen >= 3 && lage.fragen > 0 && lage.ausserhalb === 0,
|
|
`alle ${lage.fragen} Fragen stecken in ihren ${lage.gruppen} Gruppen (${lage.ausserhalb} lose)`);
|
|
/* DIE ZAHL GEHOERT IN DIE BEDINGUNG -- sonst waere das hier bei null
|
|
Gruppen gruen, ohne etwas gesehen zu haben. */
|
|
ok(lage.gruppen > 0 && lage.kopfUeberFragen === lage.gruppen,
|
|
`jede Ueberschrift steht ueber IHREN Fragen (${lage.kopfUeberFragen} von ${lage.gruppen})`);
|
|
ok(lage.paare > 0 && lage.gestapelt === lage.paare,
|
|
`keine Gruppe ragt in die naechste (${lage.gestapelt} von ${lage.paare} Uebergaengen sauber)`);
|
|
ok(lage.breiter === false, "und nichts steht seitlich ueber den Rand");
|
|
|
|
/* ---- KEIN TEXT AUF TEXT, AUCH AM HANDY (20.09.2026) --------------
|
|
|
|
Gefunden hat das ein Bildschirmfoto bei 430 px: Die Unterzeile
|
|
„Deine Antworten liest niemand ausser dir" lag 18 px UNTER dem
|
|
Absatz darunter -- Buchstaben in Buchstaben, ausgerechnet in dem
|
|
Satz, in dem Vertrauen entsteht.
|
|
|
|
Ursache war eine feste Zahl: `-18px` Abstand, gerechnet gegen die
|
|
26 px, die das Element darueber mitbrachte. In der Kopfzeile sind
|
|
es aber 0 px. Zwei fuer sich richtige Regeln, und dazwischen ein
|
|
Schaden, den keine von beiden verursacht.
|
|
|
|
Warum das hier stehen bleibt: Es ist kein Fehler, den man beim
|
|
Lesen sieht -- beide Regeln wirken einzeln vernuenftig. Nur eine
|
|
Messung findet ihn wieder, wenn jemand an einem der beiden
|
|
Abstaende dreht. */
|
|
/* EIN EIGENER KONTEXT MIT FINGER (29.09.2026).
|
|
|
|
Hier stand `seite.setViewportSize({ width: 430 })`. 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. Gerade hier zaehlt das: Hoehere Beruehrziele stehen
|
|
enger beieinander, und ob Text auf Text liegt, haengt daran. */
|
|
const engKontext = await browser.newContext({
|
|
viewport: { width: 430, height: 900 }, hasTouch: true, isMobile: true });
|
|
await engKontext.addCookies(await kontext.cookies());
|
|
const engSeite = await engKontext.newPage();
|
|
const enge = await engSeite.goto(seite.url(), { waitUntil: "networkidle" })
|
|
.then(() => engSeite.waitForTimeout(500))
|
|
.then(() => engSeite.evaluate(() => {
|
|
const kandidaten = [...document.querySelectorAll("body *")].filter((e) => {
|
|
const r = e.getBoundingClientRect();
|
|
return r.width > 20 && r.height > 6
|
|
&& /P|H1|H2|H3|LI/.test(e.tagName)
|
|
&& e.textContent.trim().length > 12;
|
|
});
|
|
const treffer = [];
|
|
for (let i = 0; i < kandidaten.length; i++) {
|
|
for (let j = i + 1; j < kandidaten.length; j++) {
|
|
const x = kandidaten[i], y = kandidaten[j];
|
|
if (x.contains(y) || y.contains(x)) continue;
|
|
const a2 = x.getBoundingClientRect(), b2 = y.getBoundingClientRect();
|
|
const ux = Math.min(a2.right, b2.right) - Math.max(a2.left, b2.left);
|
|
const uy = Math.min(a2.bottom, b2.bottom) - Math.max(a2.top, b2.top);
|
|
if (ux > 8 && uy > 6) {
|
|
treffer.push(`${x.className.split(" ")[0]} über ${y.className.split(" ")[0]} (${Math.round(uy)} px)`);
|
|
}
|
|
}
|
|
}
|
|
return { treffer, geprueft: kandidaten.length };
|
|
}));
|
|
/* DIE ZAHL GEHOERT IN DIE BEDINGUNG. Faende der Wahlausdruck nichts,
|
|
waere `treffer` leer und die Pruefung gruen, ohne etwas angesehen
|
|
zu haben -- genau der Fehler vom 01.09. mit `every()` auf einer
|
|
leeren Liste. */
|
|
ok(enge.geprueft > 20 && enge.treffer.length === 0,
|
|
enge.treffer.length
|
|
? `Text liegt auf Text: ${enge.treffer.slice(0, 2).join("; ")}`
|
|
: `bei 430 px liegt kein Text auf Text (${enge.geprueft} Absätze verglichen)`);
|
|
/* Das enge Fenster war ein eigener Kontext und geht hier zu.
|
|
`seite` war nie schmal -- das fruehere Zurueckstellen auf 1280
|
|
ist damit hinfaellig. */
|
|
await engKontext.close();
|
|
if (process.env.SCHIRM) {
|
|
/* Zielt seit dem 22.09.2026 auf das Gesamtresuemee -- den Block
|
|
darunter gibt es nicht mehr. Ohne diese Zeile brach der Lauf
|
|
mit SCHIRM=1 ab, und zwar erst ganz am Ende. */
|
|
await seite.locator("#fazit-block").screenshot(
|
|
{ path: "C:/Users/qciga/Documents/Obelix/DogiHompage/blick-fazit.png" });
|
|
}
|
|
ok(konsole.length === 0,
|
|
konsole.length ? "Fehler im Browser: " + konsole[0].slice(0, 90) : "keine Fehler im Browser");
|
|
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);
|