Files
dogfather-universe/server/pruef-community-sicht.mjs
T
DogFatherGitandClaude Opus 5 807d7d4a1c Das Foyer: eine Seite vor dem Saal, mit Plan, Eintritt und Vorschlägen
Filipe: „diese kachel soll nicht mehr geschlossen sein, ABER wenn man
drauf drückt geht zuerst eine seite auf mit reihen/kategorien. links
eine reihe wo die nächsten geplanten videos schon bereit stehen. wan
und umd wie viel uhr … und das soll geschlossen sein außer für vanvan
und dogfather. in der mitte sollen die leute dan wenn eine sendung
läuft drauf drücken können und der sendung beitreten. auch bei der
vorschau schon und dan rechts will ich die letzte reihe/kategorie, da
sollen die leute alle mir videos anraten können … und nur ich und
vanvan sehen sie."

Haus: Team Dogi (crew.dogfather-universe.com). Die Agentur ist nicht
berührt -- pruef-haus-trennung 107/0, pruef-workspace-seiten 37/0.

WARUM EINE EIGENE SEITE UND KEIN VIERTER ZUSTAND DES SAALS

Der Saal meldet jeden an, der ihn öffnet („dabei"), baut Verbindungen
auf und hält den Ereignisstrom offen. Das ist richtig für jemanden,
der zusieht -- und falsch für jemanden, der nur nachsehen will, ob
heute Abend etwas läuft. Wäre das Foyer der vierte Zustand derselben
Seite, zählte jeder Blick als Zuschauer, und die Zahl im Regiepult
sähe nach Publikum aus, wo niemand zusieht.

Im Foyer wird nur gelesen: Stand holen, zeichnen, alle 20 Sekunden
nachfragen -- und nicht, solange der Reiter im Hintergrund liegt. Kein
Ereignisstrom: Der hielte eine Verbindung offen, und manche Browser
geben je Adresse nur sechs her; im Saal wird jede davon gebraucht.

DREI REIHEN

  LINKS   die Warteschlange, nach TERMIN sortiert statt nach Platz.
          Was keinen hat, steht unten; was vorbei ist, bekommt
          „· überfällig". Nur admin und rechte Hand -- und für alle
          anderen steht sie gar nicht erst da, statt leer oder
          gesperrt zu sein. Ein Kasten mit Schloss erzählt, dass es
          etwas zu sehen gäbe. Entschieden wird das am Server; die
          Seite fragt nicht nach der Rolle, sondern danach, ob die
          Auskunft gekommen ist.
  MITTE   Lampe (Geschlossen / Gleich / Auf Sendung), Vorschaubild,
          Titel, Satz -- und der Knopf, der NUR dasteht, wenn er auch
          hineinführt. Er ist ein Link und kein Button: Wer ihn mit
          der mittleren Maustaste antippt, bekommt einen neuen Reiter.
  RECHTS  Video anraten. Jeder darf schicken, niemand sieht fremde,
          jeder sieht seine eigenen mit Stand. Vier Sperren, und jede
          beantwortet etwas anderes: zu schnell (45 s), zu viele offen
          (5 je Person), schon angeraten, steht schon bereit.

Am Telefon steht die MITTE oben. Dort ist „links" nur noch „vor allem
anderen", und das wäre die falsche Reihenfolge.

WAS SONST NOCH DAZUGEHÖRT

* Jede Zeile der Warteschlange im Saal hat jetzt ein Terminfeld.
* Die Kachel sagt nicht mehr „geschlossen" -- das Foyer ist immer
  offen. „gleich" und „● live" bleiben: Sie sagen etwas, das man ohne
  sie nicht weiß.
* reaktion.html steht in OHNE_KACHEL_UEBERALL. Die erlaubten Seiten
  werden aus den Kachelzielen abgeleitet; als die Kachel aufs Foyer
  zeigte, war der Saal plötzlich von keiner mehr genannt und
  antwortete mit einer Umleitung.

DREI DOPPELTE ANTWORTEN BESEITIGT

* „Wie viele Vorschläge darf einer offen haben" stand VIERMAL:
  dreimal als `je_person_max || 5` und einmal als Wort „fünf" im Satz
  (plus ein fünftes Mal in meldung.js). Kommt jetzt vom Server.
* `maxlength="200"` im Formular neben VORSCHLAG_NOTIZ_MAX im Server.
  Das Feld lernt die Grenze von der Auskunft.
* Die drei Foyer-Tafeln hatten eine eigene Fläche (Weiß mit 4,5 %) und
  eine eigene Form. Beides gibt es im Haus längst: Material in
  start.css (45 Klassen, gemessen), Form in module.css. Die eigene
  Fläche war dazu fast durchsichtig -- über einem hellen
  Hintergrundbild stand der Text ohne deckenden Grund. Gefunden hat
  das nicht eine Prüfung, sondern das Bildschirmfoto.

EINE LÜCKE IN DER PRÜFUNG GESCHLOSSEN

In module.css steht dieselbe 48er-Klassenliste ACHTMAL. Sieben trugen
die Marke MODULLISTE und wurden verglichen; die achte -- die Umrandung
beim Tabben (:focus-visible) -- nicht. Sie war damit die Einzige, die
still hätte abweichen können, ausgerechnet bei der Regel, die nur
sieht, wer mit der Tastatur bedient. pruef-css-klassen erwartet jetzt
acht. Eine steigende Prüfzahl, und sie ist begründet.

UND EINER, DEN KEIN WERKZEUG FINDEN KANN

Der Satz oben lautete „Links steht, was ansteht; rechts kannst du
etwas anraten." Für die Community gibt es „links" gar nicht, und am
Telefon stehen die Reihen untereinander. Eine Richtung im Text ist
eine Aussage über das Aussehen -- und das ändert sich mit dem
Bildschirm und mit der Rolle. Jetzt steht dort, WAS geht, nicht WO.

GEMESSEN

  mess-foyer (neu)        70 Messungen, 0 Fehler, 9 Bildschirmfotos
  pruef-reaktion          538, 0 Fehler (vorher 478)
  pruef-handy-teamdogi    263 Seitenaufrufe, 0 Befunde (vorher 244)
  pruef-sackgassen        14, 0 Fehler
  pruef-crew-adresse      173, 0 Fehler
  pruef-haus-trennung     107, 0 Fehler
  pruef-struktur          102, 0 Fehler
  pruef-neue-seiten       109, 0 Fehler
  pruef-portnummern        41, 0 Fehler
  pruef-haus-seiten        38, 0 Fehler
  pruef-workspace-seiten   37, 0 Fehler
  pruef-breiten           45 Seiten auf fünf Breiten, 0 beanstandet
  pruef-community-sicht    10, 0 Fehler
  pruef-deutsche-texte     12, 0 Fehler
  pruef-css-klassen       grün

mess-foyer klickt den ganzen Weg durch: Ein Gast schickt ein Video,
DogFather sieht es mit Namen, nimmt es, und es muss links im Plan
stehen. Zum Schluss macht es sechs seiner Messungen absichtlich kaputt
und prüft, dass sie das merken.

NICHT BEHOBEN, WEIL NICHT MEINS: pruef-meldungen ist rot (28
Kennungen ohne Satz, 3 Rohanzeigen) -- gemessen im worktree auf dem
letzten Commit schon vorher, Zeichen für Zeichen dieselbe Liste. Die
Dateien gehören überwiegend der Agentur; das gehört in einen eigenen
Durchgang.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-08 11:02:54 +02:00

366 lines
16 KiB
JavaScript

/* =====================================================================
WAS EIN MITGLIED DER COMMUNITY SIEHT -- SYSTEMATISCH (18.09.2026)
Filipe: „perfektioniere jede einzelne kategorie der community."
---------------------------------------------------------------------
WARUM ES DIESE PRÜFUNG GIBT
An einem Tag habe ich in der Community elf Dinge gefunden, die dort
nicht hingehörten: Aufgabenzähler, „gönn dir was", „0 betreut",
sieben Links auf eine Seite, die ein Mitglied gar nicht öffnen darf.
Jeder einzelne Fund kam daher, dass ich zufällig hingesehen habe.
Das ist keine Methode. Der Workspace ist für die Agentur gebaut
worden, und die Community hat ihn geerbt -- überall dort, wo
niemand hingesehen hat, steht noch die Sprache und die Logik eines
Arbeitsplatzes. Solche Reste findet man nicht durch Nachdenken,
sondern indem man ALLE Seiten durchgeht, die ein Mitglied erreichen
kann.
---------------------------------------------------------------------
ZWEI FRAGEN, BEIDE MESSBAR
1. TOTE WEGE: Führt irgendein Link, Knopf oder Formular auf eine
Seite, die diese Person nicht öffnen darf? Sie landet dann wieder
auf der Startseite -- ohne Meldung, ohne Grund. Man drückt, es
passiert nichts, und beim nächsten Knopf zweifelt man an sich.
2. FREMDE SPRACHE: Stehen dort Wörter, die es hier nicht gibt --
„Review", „betreut", „Pipeline", „Creator anlegen"? Sie sind
nicht falsch, sie gehören nur jemand anderem.
Aufrufen mit: node server/pruef-community-sicht.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";
const PORT = await eigenerPort(import.meta, "pruef-community-sicht");
const ordner = mkdtempSync(join(tmpdir(), "ws-cs-"));
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-community-sicht");
await new Promise((r) => setTimeout(r, 700));
process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); });
let fehler = 0, geprueft = 0;
const melde = (t) => console.log(t);
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
const CREW = "crew.dogfather-universe.com";
/* ---------------------------------------------------------------------
WELCHE SEITEN DARF EIN MITGLIED? -- aus der Rechtetabelle abgeleitet.
NICHT VON HAND AUFGEZÄHLT. Eine Liste hier müsste bei jeder neuen
Seite nachgezogen werden, und genau das vergisst man. Die Tabelle
in rechte.js ist ohnehin die Wahrheit -- also wird sie gefragt.
--------------------------------------------------------------------- */
const { darfSeite } = await import("./rechte.js");
const { readFileSync, readdirSync } = await import("node:fs");
const alleSeiten = readdirSync(join(import.meta.dirname, "..", "workspace"))
.filter((d) => d.endsWith(".html"));
const GAST = { rolle: "gast", haus: "crew" };
const erlaubt = alleSeiten.filter((d) => darfSeite(GAST, `/workspace/${d}`));
const verboten = alleSeiten.filter((d) => !darfSeite(GAST, `/workspace/${d}`));
melde("");
melde("=== 1. Was ueberhaupt offensteht ===");
/* ==== EINE LISTE STATT EINER SPANNE (25.09.2026) ===================
Hier stand `>= 3 && <= 8`. Eine Spanne sagt nichts darueber, WELCHE
Seiten offen sind -- und genau das ist die Frage. Sie war ausserdem
seit dem 24.09. rot: Mit dem Support sind es zwoelf geworden, und
jede einzelne davon gehoert dorthin (nachgesehen, nicht vermutet --
dieselben zwoelf stehen auf der Kachelwand eines Mitglieds).
AB JETZT WIRD DIE LISTE FESTGEHALTEN. Kommt eine Seite dazu, wird
diese Zeile rot und nennt sie beim Namen; wer sie hinzugefuegt hat,
traegt sie hier nach und hat damit BESTAETIGT, dass die Community
sie sehen soll. Verschwindet eine, faellt es genauso auf. Eine
Spanne kann beides nicht: Sie haette elf statt zwoelf stillschweigend
durchgehen lassen -- und zwar in beide Richtungen.
DAS IST DIESELBE BAUART wie die festgelegten Kachelfarben: nicht
„ungefaehr so viele", sondern „genau diese, und hier steht warum". */
const COMMUNITY_SEITEN = [
/* DIE EINWEISUNG kam am 06.10.2026 dazu (Bauplan „Willkommen &
Anleitung"). Sie ist fuer ein Mitglied die ERSTE Seite ueberhaupt:
Die Kachel „Willkommen & Anleitung" steht breit und vorne in der
Gruppe „Community" und fuehrt hierher. Was darauf steht, erklaert
genau die vierzehn Kacheln, die dieses Mitglied hat -- nichts
darueber hinaus; das misst pruef-anleitung in beide Richtungen.
Diese Zeile hat gefehlt, und die Pruefung ist deshalb rot
geworden -- richtig so: Genau dafuer gibt es sie. */
"anleitung.html", // Willkommen & Anleitung (seit 06.10.2026)
"bereich.html", // die Bretter (Highlights, Anschlag, Wunschliste ...)
"bewerben.html", // Mitmachen
"chat.html", // der Treff
"hilfe.html", // Regeln & Hilfe
"material.html", // Dogi-Media
/* DIE REACTION kam am 28.09.2026 dazu und steht im
Community-Bereich als eigene Kachel hinter Dogi-Media. Wer
zusieht, braucht die Seite -- sonst gaebe es niemanden im Saal.
WARUM DIESE LISTE TROTZDEM VON HAND GEPFLEGT WIRD: Sie ist eine
ABSICHT, keine Ableitung. Wuerde sie aus `rechte.js` gelesen,
verglichen zwei Kopien derselben Quelle miteinander -- dann
waere sie immer gruen und wuerde nichts beweisen. Der Preis ist
dieser Eintrag hier; die Pruefung sagt beim Anschlagen selbst,
welche Seite fehlt oder zu viel ist. */
"reaktion.html", // zusammen schauen (seit 28.09.2026)
/* DAS FOYER davor (08.10.2026): die Seite, auf die die Kachel
seither zeigt. Sie ist IMMER offen -- dort steht, was ansteht,
und dort kann jeder ein Video anraten. Was darin nur die zwei
sehen duerfen, entscheidet der Server bei den Daten und nicht
diese Liste. */
"reaktion-foyer.html", // das Foyer davor (seit 08.10.2026)
"start.html", // die Wand
"steckbrief.html", // der eigene Steckbrief
"support.html", // etwas ist kaputt (seit 24.09. fuer alle)
"treff-regeln.html", // die Regeln des Rudels
"unsere-seiten.html", // Draussen
"unterstuetzen.html", // Unterstuetzen
/* Die alte Adresse bleibt offen -- sie leitet seit dem 06.10.2026
auf anleitung.html weiter. Ein Lesezeichen soll nicht an einer
Schranke enden; die Weiterleitung selbst misst
pruef-willkommen. */
"willkommen.html", // leitet auf die Anleitung weiter
];
const zuViel = erlaubt.filter((s) => !COMMUNITY_SEITEN.includes(s));
const fehltDa = COMMUNITY_SEITEN.filter((s) => !erlaubt.includes(s));
ok(zuViel.length === 0 && fehltDa.length === 0,
`ein Mitglied darf genau die ${COMMUNITY_SEITEN.length} vorgesehenen Seiten`
+ (zuViel.length ? ` -- ZU VIEL: ${zuViel.join(", ")}` : "")
+ (fehltDa.length ? ` -- ES FEHLT: ${fehltDa.join(", ")}` : ""));
ok(verboten.length >= 15,
`und ${verboten.length} nicht -- so viele Wege gibt es, ins Leere zu zeigen`);
/* ---------------------------------------------------------------------
DIE MESSUNG IM BROWSER
--------------------------------------------------------------------- */
function roh(pfad, host, kopf = {}, koerper = null) {
return new Promise((f, s) => {
const a = httpAnfrage({
host: "127.0.0.1", port: PORT, path: pfad,
method: koerper ? "POST" : "GET",
headers: { Host: host, ...(koerper ? { "Content-Type": "application/json" } : {}), ...kopf },
}, (an) => {
let t = "";
an.on("data", (c) => { t += c; });
an.on("end", () => f({ code: an.statusCode, text: t, kopf: an.headers }));
});
a.on("error", s);
if (koerper) a.write(JSON.stringify(koerper));
a.end();
});
}
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes } = await import("node:crypto");
{
const d = new DatabaseSync(process.env.WORKSPACE_DB);
for (const [name, rolle, code] of [
["Filipe", "admin", "CODE-DOGI-0001"],
["Mara", "gast", "CODE-GAST-0001"],
]) {
const salz = randomBytes(16).toString("hex");
const hash = scryptSync(code, salz, 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(name, rolle, hash, salz, 32768, new Date().toISOString());
}
d.close();
}
async function anmelden(rolle, code) {
let a = await roh("/workspace/api/anmelden", CREW, {}, { rolle, code });
if (a.code === 400 && a.text.includes("alter_offen")) {
a = await roh("/workspace/api/anmelden", CREW, {}, { rolle, code, alter_ok: true });
}
return [].concat(a.kopf["set-cookie"] || []).map((z) => z.split(";")[0]).join("; ");
}
const kGast = await anmelden("gast", "CODE-GAST-0001");
const kDogi = await anmelden("admin", "CODE-DOGI-0001");
/* Damit die Bretter nicht leer sind -- auf einer leeren Seite findet
man keine Wege, die ins Leere fuehren. */
for (const b of ["treff", "anschlag", "ansteht", "wunsch", "highlight", "regeln", "mitmachen"]) {
await roh(`/workspace/api/bereich/${b}/start`, CREW,
{ cookie: kDogi, origin: `http://${CREW}:${PORT}` }, {});
}
let pw = null;
try {
pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
} catch { /* dritter Ausgang unten */ }
if (!pw) {
melde("");
melde("KANN NICHT NACHSEHEN: Playwright fehlt -- die Seiten wurden nicht geoeffnet.");
melde(`${geprueft} Pruefungen, ${fehler} Fehler`);
process.exit(3);
}
/* WÖRTER, DIE JEMAND ANDEREM GEHÖREN.
Keine Rollennamen -- die stehen bewusst nirgends in ausgelieferten
Dateien. Gesucht wird nach der SPRACHE eines Arbeitsplatzes. */
const FREMDE_WOERTER = [
"Vorarbeiten", "gönn dir was", "betreut", "Review",
"Pipeline", "überfällig", "In Arbeit", "Abgebrochen",
];
/* ZWEI BREITEN, NICHT EINE.
Die Community ist ueberwiegend mit dem Telefon unterwegs. Eine
Messung auf 1400 px beantwortet fuer sie die falsche Frage -- und
genau auf dem Handy sind heute die haesslichsten Sachen aufgefallen
(drei verschiedene Kartenanordnungen, ein Hinweis quer ueber der
naechsten Beschriftung). Was auf einer Breite in Ordnung ist, muss
es auf der anderen nicht sein. */
const BREITEN = [
{ name: "Rechner", breite: 1400, hoehe: 1000 },
{ name: "Handy", breite: 412, hoehe: 915 },
];
const browser = await pw.chromium.launch();
try {
for (const geraet of BREITEN) {
const ctx = await browser.newContext({
viewport: { width: geraet.breite, height: geraet.hoehe },
isMobile: geraet.breite < 700, hasTouch: geraet.breite < 700 });
await ctx.addCookies(kGast.split("; ").filter(Boolean).map((z) => {
const [name, ...rest] = z.split("=");
return { name, value: rest.join("="), domain: "127.0.0.1", path: "/" };
}));
const seite = await ctx.newPage();
const skriptfehler = [];
seite.on("pageerror", (e) => skriptfehler.push(String(e).slice(0, 100)));
/* Jede erlaubte Seite -- und bei bereich.html jedes der sieben
Bretter, denn sie sehen völlig verschieden aus. */
const ziele = [];
for (const d of erlaubt) {
if (d === "bereich.html") {
for (const b of ["treff", "anschlag", "ansteht", "wunsch", "highlight", "regeln", "mitmachen"]) {
ziele.push(`${d}?b=${b}`);
}
} else ziele.push(d);
}
melde("");
melde(`=== 2. Wege, die ins Leere fuehren (${geraet.name}) ===`);
let toteWege = 0, geprueftLinks = 0;
const gefunden = [];
for (const ziel of ziele) {
await seite.goto(`http://127.0.0.1:${PORT}/workspace/${ziel}`, { waitUntil: "networkidle" });
await seite.waitForTimeout(900);
const wege = await seite.evaluate(() =>
[...document.querySelectorAll("a[href]")]
.filter((a) => a.offsetParent !== null)
.map((a) => ({ ziel: a.getAttribute("href"), text: a.textContent.trim().slice(0, 30) })));
for (const w of wege) {
const datei = String(w.ziel).split("?")[0].split("#")[0].replace(/^\.?\//, "");
if (!datei.endsWith(".html")) continue;
geprueftLinks++;
if (verboten.includes(datei)) {
toteWege++;
gefunden.push(`${ziel}: „${w.text}" -> ${datei}`);
}
}
}
/* DIE ANZAHL GEHÖRT IN DIE BEDINGUNG. Ohne sie wäre „0 tote Wege"
auch dann wahr, wenn gar keine Seite geladen hätte. */
/* DIE UNTERGRENZE WIRD ABGELEITET, NICHT GESETZT.
Hier stand `>= 20`. Auf dem Rechner sind es 27 Wege, auf dem Handy
18 -- dort klappt die Kopfleiste um, und ein Teil der Links ist
nicht sichtbar. Die feste Zahl machte die Pruefung auf der
zweiten Breite rot, ohne dass irgendetwas falsch war.
Sie einfach auf 15 zu senken waere derselbe Fehler mit einer
anderen Zahl. Verlangt wird jetzt, was die Sache selbst hergibt:
mindestens ein sichtbarer Weg je Seite. Findet sich weniger, hat
etwas nicht geladen -- und dann ist „0 tote Wege" keine Auskunft,
sondern ein Messfehler. */
ok(geprueftLinks >= ziele.length && toteWege === 0,
`${geprueftLinks} sichtbare Wege auf ${ziele.length} Seiten, davon ${toteWege} ins Leere`);
for (const g of gefunden.slice(0, 10)) console.log(` -> ${g}`);
melde("");
melde(`=== 3. Sprache, die jemand anderem gehoert (${geraet.name}) ===`);
let fremd = 0;
const wo = [];
for (const ziel of ziele) {
await seite.goto(`http://127.0.0.1:${PORT}/workspace/${ziel}`, { waitUntil: "networkidle" });
await seite.waitForTimeout(700);
const text = await seite.evaluate(() => document.body.innerText);
for (const w of FREMDE_WOERTER) {
if (text.includes(w)) { fremd++; wo.push(`${ziel}: „${w}"`); }
}
}
ok(fremd === 0, `keine fremde Arbeitssprache auf ${ziele.length} Seiten (${fremd} Stellen)`);
for (const g of wo.slice(0, 10)) console.log(` -> ${g}`);
/* =====================================================================
4. DIE GEGENPROBE: BEHAELT DAS TEAM SEINE WEGE?
Was hier weggenommen wird, weil es fuer ein Mitglied ins Leere
fuehrt, muss fuer die anderen stehen bleiben. Ohne diesen
Abschnitt saehe eine abgeschaffte Funktion genauso aus wie eine,
die richtig entscheidet -- und zwar in beiden Faellen gruen.
===================================================================== */
melde("");
melde("=== 4. Und behaelt das Team seine Wege? ===");
if (geraet.breite > 700) {
const ctx2 = await browser.newContext({ viewport: { width: 1400, height: 1000 } });
await ctx2.addCookies(kDogi.split("; ").filter(Boolean).map((z) => {
const [name, ...rest] = z.split("=");
return { name, value: rest.join("="), domain: "127.0.0.1", path: "/" };
}));
const seite2 = await ctx2.newPage();
await seite2.goto(`http://127.0.0.1:${PORT}/workspace/start.html`, { waitUntil: "networkidle" });
await seite2.waitForTimeout(1200);
const z = await seite2.evaluate(() => ({
chat: !!document.querySelector(".chat-knopf"),
wege: [...document.querySelectorAll("a[href$='.html']")].length,
}));
ok(z.chat, "DogFather hat seinen Chat-Knopf weiterhin");
ok(z.wege >= 5, `und weiterhin Wege auf andere Seiten (${z.wege})`);
await ctx2.close();
}
melde("");
melde(`=== 5. Und laeuft ueberhaupt alles? (${geraet.name}) ===`);
ok(skriptfehler.length === 0,
`kein Skriptfehler auf ${ziele.length} Seiten (${geraet.name})`
+ (skriptfehler.length ? ` -- ${skriptfehler[0]}` : ""));
await ctx.close();
}
} finally {
await browser.close();
}
melde("");
melde(`${geprueft} Pruefungen, ${fehler} Fehler`);
melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Rest */ }
process.exit(fehler ? 1 : 0);