diff --git a/server/pruef-zuteilung.mjs b/server/pruef-zuteilung.mjs index 58929fdc..0f97748b 100644 --- a/server/pruef-zuteilung.mjs +++ b/server/pruef-zuteilung.mjs @@ -370,6 +370,197 @@ melde("\n=== Grenzen beim Verteilen ==="); `die Aufgabe liegt bei Anna selbst (${beiAnna?.verantwortlich_name})`); } + +/* ===================================================================== + 8. AM ECHTEN BILDSCHIRM + ===================================================================== + + Bis hierher ist bewiesen, dass der Server richtig entscheidet. Das + sagt nichts darüber, ob ein Mensch es bedienen kann. Geprüft wird + der Weg, den er wirklich geht: Seite öffnen, Band lesen, Knopf + drücken. + + WARUM DAS NICHT DIE SCHNITTSTELLE NOCH EINMAL PRÜFT: Ein Knopf, den + es nicht gibt, ist genauso wirkungslos wie eine Route, die 403 sagt + — nur fällt er niemandem auf. + ===================================================================== */ +melde("\n=== Am echten Bildschirm ==="); +{ + /* WARUM HIER EIN https-VORBAU STEHT -- gemessen, nicht vermutet. + + `crew.dogfather-universe.com` steht in Chromiums eingebauter + HSTS-Liste. Der Browser schaltet von sich aus auf https um, noch + bevor eine Anfrage hinausgeht; der Testserver spricht http, und + das Ergebnis ist `chrome-error://chromewebdata/` -- die Seite + laedt gar nicht erst. Genau so ist dieser Abschnitt beim ersten + Lauf gescheitert. + + Abschalten laesst sich das nicht (die Liste ist einkompiliert). + Also spricht die Pruefung selbst https: ein eigenes Zertifikat + auf diesen Namen, ein Vorbau, der alles unveraendert + weiterreicht -- BESONDERS die Host-Kopfzeile, denn an ihr + entscheidet das Haus, wer was sieht. + + Derselbe Weg wie in pruef-handy-teamdogi. Eine Nachbildung waere + die zweite Fassung, die anders altert. */ + const { execFileSync } = await import("node:child_process"); + const { createServer: httpsServer } = await import("node:https"); + const { request: httpAnfrage } = await import("node:http"); + const { readFileSync } = await import("node:fs"); + + const schluessel = join(ordner, "pruef.key"); + const zertifikat = join(ordner, "pruef.crt"); + execFileSync("openssl", ["req", "-x509", "-newkey", "rsa:2048", "-nodes", + "-keyout", schluessel, "-out", zertifikat, "-days", "2", + "-subj", `/CN=${HOST}`, "-addext", `subjectAltName=DNS:${HOST}`], + { stdio: "ignore" }); + + const HTTPS_PORT = PORT + 1; + const vorbau = httpsServer({ + key: readFileSync(schluessel), cert: readFileSync(zertifikat), + }, (von, nach) => { + const weiter = httpAnfrage({ + host: "127.0.0.1", port: PORT, path: von.url, method: von.method, + headers: { ...von.headers, host: `${HOST}:${HTTPS_PORT}` }, + }, (antwort) => { + nach.writeHead(antwort.statusCode || 502, antwort.headers); + antwort.pipe(nach); + }); + weiter.on("error", () => { try { nach.writeHead(502); nach.end(); } catch { /* egal */ } }); + von.pipe(weiter); + }); + await new Promise((r) => vorbau.listen(HTTPS_PORT, "127.0.0.1", r)); + const SEITE = `https://${HOST}:${HTTPS_PORT}`; + + const pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); + const browser = await pw.chromium.launch({ + args: [`--host-resolver-rules=MAP ${HOST} 127.0.0.1`, + "--ignore-certificate-errors"], + }); + const seitenFehler = []; + async function alsSeite(rolle, code) { + const ctx = await browser.newContext({ viewport: { width: 1280, height: 950 } }); + const s2 = await ctx.newPage(); + s2.on("pageerror", (e) => seitenFehler.push(`${rolle}: ${String(e).slice(0, 110)}`)); + await s2.goto(`${SEITE}/workspace/`, { waitUntil: "networkidle" }); + await s2.click(`.rolle[data-rolle="${rolle}"]`); + await s2.fill("#code", code); + await s2.click("button[type=submit]"); + await s2.waitForURL("**/start.html", { timeout: 25000 }); + await s2.goto(`${SEITE}/workspace/aufgaben.html`, { waitUntil: "networkidle" }); + await s2.waitForTimeout(1500); + return { ctx, seite: s2 }; + } + + /* ---- Der Modi: sein eigenes Resümee und seine Knöpfe ---- */ + const pAnna = await alsSeite("modi", "CODE-MOD1-0001"); + try { + const band = await pAnna.seite.evaluate(() => { + const b = document.querySelector(".z-band"); + if (!b) return null; + return { + titel: b.querySelector(".z-band__titel")?.textContent, + satz: b.querySelector(".z-band__satz")?.textContent, + zahlen: [...b.querySelectorAll(".zk")].map((z) => + z.querySelector(".zk__zahl").textContent + " " + z.querySelector(".zk__wort").textContent), + istTeam: b.classList.contains("z-band--team"), + }; + }); + ok(!!band, "ein Modi sieht ein Band ueber dem Brett"); + ok(band && band.titel === "Deine Aufgaben", + `und es ist SEIN Resuemee (${band?.titel})`); + ok(band && band.istTeam === false, + "nicht die Uebersicht ueber alle"); + ok(band && band.zahlen.length >= 3, + `mit Zahlen (${(band?.zahlen || []).join(" · ")})`); + + /* DER KERN DER OBERFLAECHE: Der Knopf, mit dem sie annimmt. */ + const knopf = await pAnna.seite.evaluate(() => { + const k = [...document.querySelectorAll(".z-karte .knopf")] + .map((b) => b.textContent.trim()); + return k; + }); + ok(knopf.length > 0, `an ihren Karten stehen Knoepfe (${knopf.join(", ")})`); + ok(knopf.includes("Fertig") || knopf.includes("Ich fange an") || knopf.includes("Annehmen"), + "und darunter einer, der sie weiterbringt"); + + /* GEGENPROBE: Es darf KEIN Bewerten-Knopf dastehen. */ + const bewerten = await pAnna.seite.evaluate(() => + document.querySelectorAll(".z-bewerten").length); + ok(bewerten === 0, `ein Modi bekommt keine Bewerten-Knoepfe (${bewerten})`); + } finally { + await pAnna.ctx.close(); + } + + /* ---- DogFather: die Uebersicht ueber alle ---- */ + const pChef = await alsSeite("admin", "CODE-DOGI-0001"); + try { + const band = await pChef.seite.evaluate(() => { + const b = document.querySelector(".z-band"); + if (!b) return null; + return { + titel: b.querySelector(".z-band__titel")?.textContent, + istTeam: b.classList.contains("z-band--team"), + leute: [...b.querySelectorAll(".z-person__name")].map((n) => n.textContent), + marken: [...b.querySelectorAll(".z-marke")].map((m) => m.textContent), + }; + }); + ok(!!band && band.istTeam === true, + `DogFather sieht die Uebersicht ueber alle (${band?.titel})`); + ok((band?.leute || []).includes("Anna") && (band?.leute || []).includes("Bea"), + `mit allen Namen (${(band?.leute || []).join(", ")})`); + ok((band?.marken || []).some((m) => /erledigt|offen|in Arbeit/.test(m)), + `und Marken, die etwas sagen (${(band?.marken || []).slice(0, 4).join(" · ")})`); + + /* EINE PERSON ANTIPPEN FILTERT DAS BRETT. */ + const vorher = await pChef.seite.evaluate(() => + [...document.querySelectorAll(".karte[data-id]")].filter((k) => !k.hidden).length); + await pChef.seite.click('.z-person[data-person]'); + await pChef.seite.waitForTimeout(400); + const nachher = await pChef.seite.evaluate(() => + [...document.querySelectorAll(".karte[data-id]")].filter((k) => !k.hidden).length); + ok(vorher > 0, `vorher sind ${vorher} Karten zu sehen`); + ok(nachher < vorher, `nach dem Antippen weniger (${nachher})`); + + /* UND NOCH EINMAL ANTIPPEN ZEIGT WIEDER ALLE -- sonst waere der + Filter eine Falle: Man kommt hinein und nicht mehr heraus. */ + await pChef.seite.click('.z-person[data-person]'); + await pChef.seite.waitForTimeout(400); + const zurueck = await pChef.seite.evaluate(() => + [...document.querySelectorAll(".karte[data-id]")].filter((k) => !k.hidden).length); + ok(zurueck === vorher, `und noch einmal zeigt wieder alle (${zurueck})`); + + /* DIE MEHRFACHAUSWAHL IM FORMULAR. */ + await pChef.seite.click("#neu-oeffnen"); + await pChef.seite.waitForTimeout(500); + const mehr = await pChef.seite.evaluate(() => { + const k = document.getElementById("feld-mehrere"); + const auf = document.getElementById("mehrere-auf"); + if (!k || k.hidden || !auf) return { da: false }; + auf.click(); + const leute = [...document.querySelectorAll("#mehrere-leute input[type=checkbox]")]; + const artVorher = document.getElementById("mehrere-art").hidden; + leute.slice(0, 2).forEach((x) => { x.checked = true; x.dispatchEvent(new Event("change")); }); + return { da: true, zahl: leute.length, artVorher, + artNachher: document.getElementById("mehrere-art").hidden }; + }); + ok(mehr.da, "DogFather kann mehrere dazunehmen"); + ok(mehr.zahl >= 2, `es stehen Leute zur Auswahl (${mehr.zahl})`); + ok(mehr.artVorher === true, + "die Frage 'wie soll das laufen' steht erst nicht da"); + ok(mehr.artNachher === false, + "und erscheint, sobald zwei gewaehlt sind"); + } finally { + await pChef.ctx.close(); + } + + ok(seitenFehler.length === 0, seitenFehler.length === 0 + ? "kein Skript ist unterwegs gestolpert" + : `Skriptfehler: ${seitenFehler.join(" | ")}`); + await browser.close(); + await new Promise((r) => vorbau.close(r)); +} + try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Zugabe */ } melde(`\n ${fehler === 0 ? "ALLES IN ORDNUNG" : `${fehler} FEHLER`}`); process.exit(fehler ? 1 : 0); diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index b9e87786..8a833e76 100644 --- a/workspace/anruf-probe.html +++ b/workspace/anruf-probe.html @@ -16,15 +16,15 @@ verdeckt. Gefunden hat es pruef-struktur. Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. --> - - + + - +