Der Bauplan ist damit vollstaendig umgesetzt.
ETAPPE 3 -- SCOUT, MANAGER UND SPICY MEDIA.
Woertlich aus Kapitel 07 bis 09, zusammen 84 Karten. Die Zahlen des
Bauplans stimmen auf die Karte genau: 18/21/22/23, verteilt 5/6/7,
5/7/9, 6/9/7, 5/5/13.
EINE ZWEIDEUTIGKEIT IM PDF, GEMESSEN STATT GERATEN. Beim Manager
nennt die Merke-Spalte "61, 58, 51 feste Punkte", und Layout- und
Raw-Auszug ordnen sie verschiedenen Kacheln zu. Nachgezaehlt im Code:
LIVE 69, Content-Ideen 61, Community 58, Technik 51. Damit ist die
zeilenweise Lesart bewiesen richtig -- die Layout-Lesart haette "61"
der LIVE-Analyse gegeben, die 69 hat.
Die Zuordnungspruefung laeuft jetzt fuer alle vier Rollen, und die
erwartete Kartenzahl wird AUS DER KACHELLISTE abgeleitet, nicht aus
der Abnahmeliste abgeschrieben. Dazu eine Gegenprobe, dass sich die
vier Fassungen wirklich unterscheiden -- sonst waeren alle Zeilen
gruen, solange die Kachellisten zufaellig passen.
ETAPPE 4 -- DIE PFLEGE, fuer DogFather UND Spicy.
Bearbeitet wird an Ort und Stelle: Stufe und die drei Zeilen je Karte,
Titel und Text je Listenzeile, Leitgedanke je Rolle. Fehlende Karten
werden benannt und lassen sich mit einem Griff anlegen -- das ist der
Fall "Fuer diese Kachel fehlt ein Eintrag" aus Kapitel 11. Dazu "Wer
ist wie weit": eine Arbeitslage, keine Ueberwachung.
KEINE ZWEITE ROLLENAUSWAHL. Die Vorschau gibt es schon -- sie heisst
"Meine Sicht" und steht in der Kopfleiste. Geprueft wird fuers Pflegen
`req.person` und nicht `req.sicht`: Wer durch fremde Augen sieht, soll
nicht aus Versehen in fremdem Namen aendern.
DIE MARKE "NEU" HAT EIN ENDE: 14 Tage ODER bis die Person die Seite
nach der Aenderung geoeffnet hat. Der Bauplan sagt dazu nichts, und
ohne Ende waere nach einem halben Jahr alles neu.
ETAPPE 5 -- DIE SUCHE.
Die Karten stehen in der Kopfleisten-Suche, aber nur die der EIGENEN
Rolle: Faende ein Creator die Manager-Karte, stuende dort "Zuteilen,
freigeben" neben einer Kachel, die er nicht hat. Der Treffer traegt
den Zusatz hinter dem Fragezeichen, sonst landen fuenf Karten auf
derselben Seite.
ETAPPE 6 -- BEGRUESSUNG UND RUNDGANG.
Fuenf Schritte ueber die Zentrale, im letzten leuchten nur die
Muss-Kacheln. EIGENE DATEI (`rundgang.js`) und nicht ein Stueck
start.js: Er laeuft auf der meistbenutzten Seite des Hauses, und wenn
hier etwas schiefgeht, darf davon nichts anderes betroffen sein.
Keine gerechnete Koordinate, kein Loch in einer Abdeckung -- die Seite
wird leiser, das Ziel bleibt hell. Faellt das Skript aus, ist beim
naechsten Laden alles normal.
DER ROLLENWECHSEL IST KEINE SONDERREGEL, sondern der Schluessel:
`(Person, Rolle)`. Die neue Rolle hat schlicht noch keine Zeile, also
startet die Einweisung von selbst noch einmal. Eine Spalte
"zuruecksetzen", an die jemand denken muesste, waere die Stelle, an
der es vergessen wird.
EIN FUND AUS DER MESSUNG: Gemerkt wurde zuerst erst am ENDE des
Rundgangs. Wer "Los geht's" drueckte und dann wegging, bekam die
Begruessung bei jedem Aufruf wieder -- fuer immer. Jetzt wird beim
ZEIGEN gemerkt; das ist die Tatsache, um die es geht.
ETAPPE 7 -- DIE ABNAHME.
Dabei fiel auf, dass der Rollen-Rundgang SPICY MEDIA gar nicht kannte:
fuenf Durchgaenge, aber zweimal Manager, zweimal Scout und Spicy nie.
Ihr gehoert die Gruppe "Rund um das Team" samt Team-Lage -- diese
Kachel ist in keinem Durchgang je geoeffnet worden. Jetzt laeuft sie
mit: 452 statt 406 Pruefungen.
MITGENOMMEN, WEIL ES ROT WAR: `pruef-eventkarte` und
`pruef-haus-luecke` bildeten ihr Tagesdatum aus UTC. Zwischen 00:00
und 02:00 deutscher Zeit liegt UTC im Vortag -- ein naechtlicher Lauf
haette an einem Datum gesucht, das er selbst nicht geschrieben hat.
Beide benutzen jetzt `helfer-zeit`. `pruef-struktur` ist damit gruen.
Gemessen (alle gruen, kein einziger Befund)
pruef-anleitung 125 Pruefungen (vorher 70)
pruef-rollen 452 Pruefungen (vorher 406) -- mit Spicy Media
pruef-struktur gruen (vorher rot)
pruef-rechtetafel, pruef-css-klassen, pruef-kachel-universum,
pruef-suchfeld, pruef-kachelraster, pruef-start-ansicht,
pruef-sicht, pruef-eventkarte alle gruen
mess-anleitung Rundgang fuenf Schritte, im letzten genau 5
Muss-Kacheln hervorgehoben; beim zweiten
Aufruf keine Begruessung mehr; kein
Querschieben bei 412 und 1440 px
Sicherung: ~/sicherungen/workspace-vor-anleitung-e37-20261006-1536.db
OFFEN, NICHT VON MIR UND NICHT AUS DIESEM BAUPLAN: pruef-haus-luecke
endet mit Rueckgabewert 3 ("konnte nicht nachsehen") -- eine der
beiden Adressen liefert das Anschlagbrett nicht. Nachgemessen: Das war
vor dieser Arbeit genauso.
Co-Authored-By: Claude Opus 5 <[email protected]>
345 lines
16 KiB
JavaScript
345 lines
16 KiB
JavaScript
/* =====================================================================
|
|
WIE DIE ANLEITUNG AUSSIEHT (06.10.2026, Etappe 1)
|
|
|
|
SIE PRUEFT NICHTS und meldet keinen Fehlschlag -- sie macht Bilder,
|
|
auf denen man nachsehen kann, und misst vier Dinge, die man auf
|
|
einem Bild schlecht sieht: waagerechtes Schieben, Beruehrziele unter
|
|
40 px, Fehler in der Browserkonsole und ob der Filter wirklich etwas
|
|
tut.
|
|
|
|
Deshalb `mess-` und nicht `pruef-`.
|
|
|
|
ZWEI BREITEN, und die kleinere ist die wichtigere: 412 px ist die
|
|
haeufigste Android-Breite. Am 06.09.2026 hat genau diese Breite im
|
|
Creator Workspace eine Ueberdeckung aufgedeckt, bei der ein Knopf
|
|
ueber einem anderen lag -- auf dem Computer war nichts zu sehen.
|
|
|
|
EIGENE WEGWERF-DATENBANK in einem mktemp-Ordner. In einem laufenden
|
|
System waeren das Testdaten, und die sind verboten.
|
|
|
|
Aufrufen mit: node server/mess-anleitung.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, "mess-anleitung");
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-anl-bild-"));
|
|
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(240_000, "mess-anleitung");
|
|
await new Promise((r) => setTimeout(r, 900));
|
|
process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); });
|
|
process.on("unhandledRejection", (f) => { console.error("ABSTURZ (Versprechen):", f); process.exit(7); });
|
|
|
|
/* ---------- Leute ----------------------------------------------------- */
|
|
const { DatabaseSync } = await import("node:sqlite");
|
|
const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
|
|
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const jetztIso = new Date().toISOString();
|
|
const kenn = randomBytes(32).toString("hex");
|
|
d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)"
|
|
+ " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING")
|
|
.run("code_kennung_schluessel", kenn, jetztIso);
|
|
function anlegen(name, rolle, code) {
|
|
const salt = randomBytes(16).toString("hex");
|
|
const hash = scryptSync(code, salt, 64,
|
|
{ N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
|
|
d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n,"
|
|
+ " code_kennung, aktiv, erstellt) VALUES (?,?,?,?,?,?,1,?)")
|
|
.run(name, rolle, hash, salt, 32768,
|
|
createHmac("sha256", kenn).update(code).digest("hex"), jetztIso);
|
|
}
|
|
anlegen("Luna", "creator", "CODE-LUNA-0001");
|
|
anlegen("Filipe", "admin", "CODE-DOGI-0001");
|
|
anlegen("Cigdem", "spicy", "CODE-SPICY-0001");
|
|
anlegen("Neu Hier", "creator", "CODE-NEU-0001");
|
|
d.close();
|
|
|
|
function anfrage(pfad, methode, koerper, keks) {
|
|
return new Promise((fertig, schief) => {
|
|
const daten = koerper ? JSON.stringify(koerper) : null;
|
|
const a = httpAnfrage({
|
|
host: "127.0.0.1", port: PORT, path: pfad, method: methode,
|
|
headers: {
|
|
Origin: `http://127.0.0.1:${PORT}`,
|
|
...(keks ? { Cookie: keks } : {}),
|
|
...(daten ? { "Content-Type": "application/json",
|
|
"Content-Length": Buffer.byteLength(daten) } : {}),
|
|
},
|
|
}, (antwort) => {
|
|
let text = "";
|
|
antwort.on("data", (t) => { text += t; });
|
|
antwort.on("end", () => fertig({ code: antwort.statusCode, kopf: antwort.headers, text }));
|
|
});
|
|
a.on("error", schief);
|
|
if (daten) a.write(daten);
|
|
a.end();
|
|
});
|
|
}
|
|
|
|
async function keksFuer(rolle, code) {
|
|
const an = await anfrage("/workspace/api/anmelden", "POST", { rolle, code });
|
|
if (an.code !== 200) { console.log(`ABBRUCH: Anmeldung ${rolle} ${an.code}`); process.exit(2); }
|
|
return (an.kopf["set-cookie"] || []).map((x) => x.split(";")[0]).join("; ");
|
|
}
|
|
const keksCreator = await keksFuer("creator", "CODE-LUNA-0001");
|
|
const keksDogi = await keksFuer("admin", "CODE-DOGI-0001");
|
|
const keksSpicy = await keksFuer("spicy", "CODE-SPICY-0001");
|
|
const keksNeu = await keksFuer("creator", "CODE-NEU-0001");
|
|
|
|
/* ---------- Bilder ---------------------------------------------------- */
|
|
const pw = await import(
|
|
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
|
|
const browser = await pw.chromium.launch();
|
|
|
|
const nachmessen = () => ({
|
|
schiebtQuer: document.documentElement.scrollWidth > window.innerWidth + 2,
|
|
zuKlein: [...document.querySelectorAll("button, a[href], summary")]
|
|
.filter((e) => {
|
|
const k = e.getBoundingClientRect();
|
|
return k.width > 0 && k.height > 0 && k.height < 40;
|
|
})
|
|
.map((e) => `${e.tagName.toLowerCase()}.${(e.className || "-")}: ${Math.round(e.getBoundingClientRect().height)}px`),
|
|
karten: document.querySelectorAll(".an-karte").length,
|
|
/* Je Stufe: wie viele Karten, und ist der Kasten offen? */
|
|
stufen: [...document.querySelectorAll(".an-stufe")].map((s) => ({
|
|
stufe: s.dataset.stufe,
|
|
zahl: s.querySelector(".an-stufe__zahl")?.textContent,
|
|
offen: !!s.querySelector(".an-stufe__falte")?.open,
|
|
sichtbar: !s.hidden,
|
|
})),
|
|
/* Tragen alle Karten den Ton ihrer Kachel? Eine ohne waere grau. */
|
|
ohneTon: [...document.querySelectorAll(".an-karte")]
|
|
.filter((k) => !k.dataset.ton)
|
|
.map((k) => k.querySelector(".an-karte__name")?.textContent),
|
|
schritte: document.querySelectorAll(".an-schritt").length,
|
|
saetze: document.querySelectorAll(".an-saetze__zeile").length,
|
|
nicht: document.querySelectorAll(".an-nicht__zeile").length,
|
|
takt: document.querySelectorAll(".an-takt__wann").length,
|
|
fragen: document.querySelectorAll(".an-frage").length,
|
|
ohne: document.getElementById("ohne-fassung")?.hidden === false
|
|
? document.getElementById("ohne-fassung").textContent.slice(0, 70) : null,
|
|
/* Etappe 2 */
|
|
haken: document.querySelectorAll(".an-haken").length,
|
|
abgehakt: document.querySelectorAll(".an-haken__feld:checked").length,
|
|
stand: document.getElementById("stand")?.hidden === false
|
|
? document.getElementById("stand-satz").textContent : "(kein Fortschritt)",
|
|
});
|
|
|
|
async function bild(name, keks, breite, hoehe, vorher) {
|
|
const kontext = await browser.newContext({
|
|
viewport: { width: breite, height: hoehe }, hasTouch: breite <= 860,
|
|
});
|
|
await kontext.addCookies(keks.split("; ").map((k) => {
|
|
const [n, ...r] = k.split("=");
|
|
return { name: n, value: r.join("="), domain: "127.0.0.1", path: "/" };
|
|
}));
|
|
const seite = await kontext.newPage();
|
|
const konsole = [];
|
|
seite.on("console", (e) => {
|
|
if (e.type() === "error" && !/favicon|net::ERR/i.test(e.text())) konsole.push(e.text().slice(0, 160));
|
|
});
|
|
seite.on("pageerror", (e) => konsole.push("Absturz: " + e.message));
|
|
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/anleitung.html`,
|
|
{ waitUntil: "networkidle" });
|
|
await seite.waitForTimeout(1000);
|
|
if (vorher) await vorher(seite);
|
|
await seite.waitForTimeout(400);
|
|
|
|
const blick = await seite.evaluate(nachmessen);
|
|
await seite.screenshot({ path: `bild-anleitung-${name}.png`, fullPage: true });
|
|
|
|
console.log(`\n--- ${name} (${breite}px) ---`);
|
|
if (blick.ohne) console.log(` Ohne Fassung: ${blick.ohne}…`);
|
|
console.log(` Karten: ${blick.karten} Schritte: ${blick.schritte} `
|
|
+ `Saetze: ${blick.saetze} Nicht deins: ${blick.nicht} `
|
|
+ `Takt: ${blick.takt} Fragen: ${blick.fragen}`);
|
|
for (const s of blick.stufen) {
|
|
console.log(` ${String(s.stufe).padEnd(13)} ${String(s.zahl).padEnd(3)} `
|
|
+ `${s.offen ? "offen" : "zu "} ${s.sichtbar ? "" : "(ausgeblendet)"}`);
|
|
}
|
|
console.log(` Fortschritt: ${blick.stand} Haken: ${blick.haken} (${blick.abgehakt} gesetzt)`);
|
|
console.log(` Karten ohne Farbe: ${blick.ohneTon.length ? blick.ohneTon.join(", ") : "keine"}`);
|
|
console.log(` Quer schieben: ${blick.schiebtQuer ? "JA — Befund!" : "nein"}`);
|
|
console.log(` Zu kleine Ziele: ${blick.zuKlein.length ? blick.zuKlein.join(", ") : "keine"}`);
|
|
if (konsole.length) console.log(` KONSOLE: ${konsole.join(" | ")}`);
|
|
await kontext.close();
|
|
return blick;
|
|
}
|
|
|
|
await bild("creator-handy", keksCreator, 412, 950);
|
|
await bild("creator-rechner", keksCreator, 1440, 1000);
|
|
|
|
/* Der Filter muss etwas TUN. Auf einem Bild sieht man nur den
|
|
gedrueckten Knopf -- ob die anderen Kaesten verschwinden, nicht. */
|
|
await bild("creator-nur-muss", keksCreator, 412, 950, async (seite) => {
|
|
await seite.click('.an-filter__knopf[data-wert="muss"]');
|
|
});
|
|
|
|
/* DogFather ohne gewaehlte Sicht: Er bekommt keine Fassung, sondern
|
|
eine Auskunft. Das Bild zeigt, ob die wie eine Erklaerung aussieht
|
|
und nicht wie ein Fehler. */
|
|
await bild("dogfather-ohne-sicht", keksDogi, 412, 950);
|
|
|
|
/* =====================================================================
|
|
DIE BREITE KACHEL AUF DER STARTSEITE (Etappe 2)
|
|
|
|
Sie entsteht nur, solange Erste Schritte offen sind. Beide Zustaende
|
|
werden gemessen -- ein Bild von "sie ist da" allein beweist nicht,
|
|
dass sie auch wieder verschwindet, und genau das ist ihr Versprechen.
|
|
===================================================================== */
|
|
async function startBild(name, keks, breite) {
|
|
const kontext = await browser.newContext({
|
|
viewport: { width: breite, height: 1100 }, hasTouch: breite <= 860,
|
|
});
|
|
await kontext.addCookies(keks.split("; ").map((k) => {
|
|
const [n, ...r] = k.split("=");
|
|
return { name: n, value: r.join("="), domain: "127.0.0.1", path: "/" };
|
|
}));
|
|
const seite = await kontext.newPage();
|
|
const konsole = [];
|
|
seite.on("console", (e) => {
|
|
if (e.type() === "error" && !/favicon|net::ERR/i.test(e.text())) konsole.push(e.text().slice(0, 160));
|
|
});
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/start.html`, { waitUntil: "networkidle" });
|
|
await seite.waitForTimeout(1400);
|
|
const blick = await seite.evaluate(() => {
|
|
const gruppen = [...document.querySelectorAll(".gruppe")].map((g) =>
|
|
g.querySelector(".gruppe__name")?.textContent);
|
|
const breiteKachel = [...document.querySelectorAll('.kachel[data-gross="ja"]')]
|
|
.map((k) => k.querySelector(".kachel__name")?.textContent
|
|
+ " [" + (k.querySelector(".kachel__zahl")?.textContent || "ohne Zahl") + "]");
|
|
const dran = [...document.querySelectorAll(".dran__text")].map((e) => e.textContent);
|
|
return { gruppen, breiteKachel, dran,
|
|
anleitungKacheln: [...document.querySelectorAll('a[href="anleitung.html"]')].length };
|
|
});
|
|
/* Ganze Seite: Die breite Kachel sitzt unter der Zentrale und waere
|
|
im Ausschnitt nicht zu sehen -- also waere das Bild kein Beleg. */
|
|
await seite.screenshot({ path: `bild-anleitung-${name}.png`, fullPage: true });
|
|
console.log(`
|
|
--- ${name} (${breite}px, Startseite) ---`);
|
|
console.log(` Gruppen: ${blick.gruppen.join(" | ")}`);
|
|
console.log(` Breite Kacheln: ${blick.breiteKachel.length ? blick.breiteKachel.join(", ") : "keine"}`);
|
|
console.log(` Wege zur Anleitung: ${blick.anleitungKacheln}`);
|
|
console.log(` Was ist dran: ${blick.dran.join(" | ") || "nichts"}`);
|
|
if (konsole.length) console.log(` KONSOLE: ${konsole.join(" | ")}`);
|
|
await kontext.close();
|
|
return blick;
|
|
}
|
|
|
|
await startBild("start-mit-schritten", keksCreator, 412);
|
|
|
|
/* Jetzt alle fuenf abhaken -- ueber die Schnittstelle, wie es die
|
|
Seite auch tut -- und noch einmal hinsehen. */
|
|
const standVorher = await anfrage("/workspace/api/anleitung", "GET", null, keksCreator);
|
|
if (standVorher.code !== 200) {
|
|
console.log(`ABBRUCH: Anleitung nicht abrufbar (${standVorher.code})`);
|
|
process.exit(2);
|
|
}
|
|
const schritte = JSON.parse(standVorher.text).schritte || [];
|
|
for (const sch of schritte) {
|
|
await new Promise((fertig, schief) => {
|
|
const daten = JSON.stringify({ erledigt: true });
|
|
const a = httpAnfrage({
|
|
host: "127.0.0.1", port: PORT, path: `/workspace/api/anleitung/schritt/${sch.id}`,
|
|
method: "PUT",
|
|
headers: { Cookie: keksCreator, "Content-Type": "application/json",
|
|
"Content-Length": Buffer.byteLength(daten), Origin: `http://127.0.0.1:${PORT}` },
|
|
}, (antwort) => { antwort.on("data", () => {}); antwort.on("end", fertig); });
|
|
a.on("error", schief);
|
|
a.write(daten); a.end();
|
|
});
|
|
}
|
|
console.log(`
|
|
(${schritte.length} Schritte abgehakt)`);
|
|
await startBild("start-ohne-schritte", keksCreator, 412);
|
|
await bild("creator-fertig", keksCreator, 412, 950);
|
|
|
|
/* =====================================================================
|
|
DIE PFLEGE (Etappe 4) -- und ob sie LEISE ist, solange sie aus ist
|
|
===================================================================== */
|
|
await bild("spicy-pflege-aus", keksSpicy, 1440, 1000);
|
|
await bild("spicy-pflege-an", keksSpicy, 1440, 1000, async (seite) => {
|
|
await seite.click("#pflege-an");
|
|
await seite.waitForTimeout(500);
|
|
});
|
|
|
|
/* =====================================================================
|
|
DER RUNDGANG (Etappe 6)
|
|
=====================================================================
|
|
Eine FRISCHE Person -- "Neu Hier" hat die Einweisung noch nie
|
|
gesehen. Gemessen wird nicht nur, dass das Fenster kommt, sondern
|
|
auch, dass die Seite danach wieder NORMAL ist: Ein Rundgang, der
|
|
die Startseite gedaempft zuruecklaesst, waere schlimmer als keiner.
|
|
===================================================================== */
|
|
async function rundgangBild(name, keks, breite, schritte) {
|
|
const kontext = await browser.newContext({
|
|
viewport: { width: breite, height: 1000 }, hasTouch: breite <= 860,
|
|
});
|
|
await kontext.addCookies(keks.split("; ").map((k) => {
|
|
const [n, ...r] = k.split("=");
|
|
return { name: n, value: r.join("="), domain: "127.0.0.1", path: "/" };
|
|
}));
|
|
const seite = await kontext.newPage();
|
|
const konsole = [];
|
|
seite.on("console", (e) => {
|
|
if (e.type() === "error" && !/favicon|net::ERR/i.test(e.text())) konsole.push(e.text().slice(0, 160));
|
|
});
|
|
seite.on("pageerror", (e) => konsole.push("Absturz: " + e.message));
|
|
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/start.html`, { waitUntil: "networkidle" });
|
|
/* Das Skript wartet absichtlich 1,2 s, damit die Kacheln stehen. */
|
|
await seite.waitForTimeout(2600);
|
|
|
|
const gruss = await seite.$(".rg-gruss");
|
|
console.log(`\n--- ${name} (${breite}px) ---`);
|
|
console.log(` Begruessung da: ${gruss ? "ja" : "NEIN"}`);
|
|
if (gruss) {
|
|
await seite.screenshot({ path: `bild-anleitung-${name}-gruss.png` });
|
|
await seite.click(".rg-gruss .knopf");
|
|
await seite.waitForTimeout(700);
|
|
}
|
|
for (let i = 1; i <= schritte; i++) {
|
|
const stand = await seite.evaluate(() => ({
|
|
zaehler: document.querySelector(".rg__zaehler")?.textContent || "(kein Kasten)",
|
|
titel: document.querySelector(".rg__titel")?.textContent || "",
|
|
gedaempft: document.body.dataset.rundgang === "ja",
|
|
ziele: document.querySelectorAll("[data-rundgang-ziel]").length,
|
|
mussKacheln: document.querySelectorAll('[data-rundgang-muss="ja"]').length,
|
|
}));
|
|
console.log(` ${stand.zaehler.padEnd(18)} ${stand.titel.padEnd(28)} `
|
|
+ `Ziel ${stand.ziele} Muss-Kacheln ${stand.mussKacheln}`);
|
|
if (i === schritte) await seite.screenshot({ path: `bild-anleitung-${name}-letzter.png` });
|
|
const weiter = await seite.$(".rg__weiter");
|
|
if (!weiter) break;
|
|
if (i < schritte) { await weiter.click(); await seite.waitForTimeout(600); }
|
|
}
|
|
if (konsole.length) console.log(` KONSOLE: ${konsole.join(" | ")}`);
|
|
await kontext.close();
|
|
}
|
|
|
|
await rundgangBild("rundgang", keksNeu, 412, 5);
|
|
|
|
/* Und: Wer die Begruessung schon gesehen hat, bekommt sie nicht noch
|
|
einmal. Dieselbe Person, zweiter Aufruf. */
|
|
await rundgangBild("rundgang-zweites-mal", keksNeu, 412, 1);
|
|
|
|
await browser.close();
|
|
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
|
|
console.log("\nBilder geschrieben: bild-anleitung-*.png");
|
|
process.exit(0);
|