Files
dogfather-universe/pruef-musik.mjs
T
DogFatherGitandClaude Opus 5 43264c0990 Startseite hochwertiger: Typografie, Weltfarben, Event-Karte
Wunsch 27.08.2026: "soll nur noch hochwertiger und professioneller
aussehen." Kein neues Design, sondern die Details, die den Unterschied
zwischen "gemacht" und "gesetzt" ausmachen:

- Ueberschrift enger gesetzt (-.022em, Zeilenabstand 1.08, text-wrap:
  balance). Bei 4rem wirkt normale Laufweite auseinandergefallen; die
  drei Zeilen verteilen sich jetzt gleichmaessig statt mit kurzer
  Restzeile.
- Jede Welt-Kachel traegt die Farbe IHRER Welt statt dreimal derselben:
  Babyblau, Silber, das gedeckte Rot von Spicy Media. Die Werte sind
  nicht erfunden, sondern die --accent-Toene der drei Themendateien.
- Plaketten ("WELT 1") klein, in Versalien, weit gesperrt -- liest sich
  als Kapitelmarke statt als Beschriftung.
- Event-Karte: Datum als gesperrte Versalzeile (ordnet sich dem Titel
  unter), "Mehr erfahren" als ruhiger Knopf statt nacktem Textlink,
  Haarlinie am Bild, Text auf 5 Zeilen begrenzt -- damit nicht die
  Textlaenge aus der Verwaltung das Aussehen der Startseite bestimmt und
  bei zwei Events beide Kacheln gleich hoch sind (geprueft: 572/572px).

ZWEI FUNDE BEIM PRUEFEN, BEIDE WICHTIGER ALS DER FEINSCHLIFF:

1. INHALTSRICHTLINIE UND ZEILENENDEN. Der Browser rechnet die Pruefsumme
   eines Inline-Skripts NICHT ueber die Bytes der Datei, sondern ueber
   den Text im Dokument -- und der HTML-Parser ersetzt beim Einlesen
   jedes CR LF durch LF (HTML-Spezifikation, "preprocessing the input
   stream"). Eine Datei mit Windows-Zeilenenden ergibt serverseitig also
   eine ANDERE Summe, und die Seite fuehrt ihr eigenes Skript nicht mehr
   aus: kein Live-Status, keine Events, nichts. Live war es unauffaellig
   (Linux-Auscheckung hat LF), auf dem Windows-Rechner sofort tot
   (core.autocrlf=true). inhaltsrichtlinie.js vereinheitlicht jetzt vor
   dem Rechnen auf LF -- das ist die Summe, die der Browser wirklich
   bildet, und macht die Richtlinie unabhaengig davon, mit welchem
   Werkzeug eine Datei zuletzt gespeichert wurde.

2. TESTS MIT FESTEM PORT KOENNEN LUEGEN. Drei Server aus frueheren
   Laeufen liefen noch. Neue Testlaeufe konnten ihren Port nicht belegen,
   starben still -- und massen weiter gegen den ALTEN Code. Ergebnis
   waren Fehlermeldungen zu einem laengst behobenen Fehler. pruef-musik,
   pruef-kasse und pruef-startseite suchen sich jetzt einen freien Port.

Neuer Test pruef-startseite.mjs (19 Pruefungen) mit FESTEN Event-Daten:
Beim Pruefen kam einmal nichts vom Server, der Abschnitt blieb leer und
der Test haette "kein Fehler" gemeldet, obwohl er nichts gesehen hat.
Geprueft werden Maske am Artwork, genau eine Kachel bei einem Event,
volle Breite, saubere Textkuerzung auf ganze Zeilen, eigene Weltfarben,
zwei gleich hohe Kacheln bei zwei Events, Handy ohne Ueberlauf.

Alles gruen: startseite 19, musik 17, kasse 15, inhaltsrichtlinie 22,
handy 56.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-27 16:06:09 +02:00

165 lines
8.3 KiB
JavaScript

/* =====================================================================
Musik-Knopf unten rechts — läuft gegen den ECHTEN Server
WARUM NICHT GEGEN EINEN SIMPLEN DATEI-SERVER
Der Fehler, um den es hier geht, stand in keiner HTML-Datei und in
keiner Zeile JavaScript: Die Seite war fehlerfrei, aber der Server
schickte "frame-src 'none'" mit — und der Browser warf die beiden
Spotify-Player still weg (gemeldet 27.08.2026). Ein Test, der die
Dateien nur ausliefert, hätte das nie gesehen, weil er die Kopfzeile
gar nicht erst erzeugt.
Deshalb startet dieser Test server/index.js selbst auf einem eigenen
Port und prüft im echten Browser, ob die Player wirklich ankommen.
Geprüft wird:
1. Der Knopf ist da und groß genug zum Antippen.
2. Klick klappt das Feld auf, beide Player werden nachgeladen.
3. Die Rahmen laden WIRKLICH von open.spotify.com (nicht Fehlerseite).
4. Kein einziger Verstoß gegen die Inhaltsrichtlinie in der Konsole.
5. Die Richtlinie bleibt eng: nur open.spotify.com, kein Sternchen.
6. Schließen über ✕ und über Escape.
7. Am Handy passt das Feld in den Bildschirm.
===================================================================== */
import { chromium } from "playwright";
import { spawn } from "node:child_process";
import { createServer } from "node:net";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const HIER = dirname(fileURLToPath(import.meta.url));
/* Freien Port suchen statt festen nehmen: Laeuft noch ein Server aus einem
frueheren Lauf, kann der neue seinen Port nicht belegen, stirbt still --
und der Test misst gegen den ALTEN Code. Genau das ist am 27.08.2026
passiert und hat fast zu einer Fehlersuche in die falsche Richtung
gefuehrt. */
const PORT = await new Promise((auf, ab) => {
const probe = createServer();
probe.on("error", ab);
probe.listen(0, "127.0.0.1", () => { const p = probe.address().port; probe.close(() => auf(p)); });
});
const BASIS = `http://127.0.0.1:${PORT}`;
let ok = 0, fehl = 0;
const funde = [];
const pruefe = (name, gut, info) => {
if (gut) { ok++; console.log(" ok " + name + (info ? " -> " + info : "")); }
else { fehl++; console.log(" FEHL " + name + (info ? " -> " + info : "")); funde.push(name + " — " + info); }
};
const server = spawn(process.execPath, ["index.js"], {
cwd: join(HIER, "server"),
env: { ...process.env, PORT: String(PORT) },
stdio: ["ignore", "pipe", "pipe"],
});
async function warteAufServer() {
for (let i = 0; i < 40; i++) {
try {
const res = await fetch(BASIS + "/index.html");
if (res.ok) return res;
} catch { /* noch nicht oben */ }
await new Promise((r) => setTimeout(r, 250));
}
throw new Error("Server ist nicht hochgekommen");
}
(async () => {
const antwort = await warteAufServer();
const richtlinie = antwort.headers.get("content-security-policy") || "";
console.log("\n== Inhaltsrichtlinie ==");
const frameRegel = (richtlinie.split(";").find((t) => t.trim().startsWith("frame-src")) || "").trim();
pruefe("frame-src erlaubt Spotify", /https:\/\/open\.spotify\.com/.test(frameRegel), frameRegel || "(keine frame-src-Regel)");
pruefe("frame-src bleibt eng (kein *)", !/[\s:]\*/.test(frameRegel) && !/'unsafe/.test(frameRegel), frameRegel);
const browser = await chromium.launch();
/* ---------- Computer ---------- */
console.log("\n== Computer ==");
const seite = await browser.newPage({ viewport: { width: 1366, height: 800 } });
const verstoesse = new Set();
seite.on("console", (m) => {
const text = m.text();
if (/Content Security Policy|violates the following/i.test(text)) verstoesse.add(text.replace(/\s+/g, " ").slice(0, 160));
});
await seite.goto(BASIS + "/index.html", { waitUntil: "domcontentloaded" });
await seite.waitForSelector("#music-toggle", { timeout: 10000 });
const knopf = await seite.evaluate(() => {
const r = document.getElementById("music-toggle").getBoundingClientRect();
return { breite: Math.round(r.width), hoehe: Math.round(r.height), rechts: Math.round(innerWidth - r.right), unten: Math.round(innerHeight - r.bottom) };
});
pruefe("Knopf ist groß genug zum Antippen", knopf.breite >= 44 && knopf.hoehe >= 44, knopf.breite + "x" + knopf.hoehe + "px");
pruefe("Knopf sitzt unten rechts", knopf.rechts >= 0 && knopf.rechts < 80 && knopf.unten >= 0 && knopf.unten < 80, "Abstand rechts " + knopf.rechts + "px, unten " + knopf.unten + "px");
pruefe("Feld ist zu Beginn zu", await seite.evaluate(() => document.getElementById("music-panel").hidden), "");
await seite.click("#music-toggle");
await seite.waitForTimeout(500);
const nachKlick = await seite.evaluate(() => {
const p = document.getElementById("music-panel");
return {
offen: !p.hidden,
quellen: [...p.querySelectorAll("iframe")].map((f) => f.getAttribute("src") || ""),
nochUngeladen: p.querySelectorAll("iframe[data-src]").length,
};
});
pruefe("Feld klappt auf", nachKlick.offen, "");
pruefe("Beide Player werden nachgeladen", nachKlick.quellen.length === 2 && nachKlick.quellen.every((q) => q.startsWith("https://open.spotify.com/embed/artist/")), nachKlick.quellen.length + " Player");
pruefe("Kein Player bleibt ungeladen", nachKlick.nochUngeladen === 0, nachKlick.nochUngeladen + " ohne src");
/* Der eigentliche Punkt: Kommt der Rahmen wirklich an? Wird er von der
Richtlinie geblockt, steht in seiner Adresse chrome-error://. */
await seite.waitForTimeout(6000);
const rahmen = seite.frames().map((f) => f.url());
const spotifyRahmen = rahmen.filter((u) => u.startsWith("https://open.spotify.com/"));
const kaputteRahmen = rahmen.filter((u) => u.startsWith("chrome-error://"));
pruefe("Beide Spotify-Rahmen sind wirklich geladen", spotifyRahmen.length === 2, spotifyRahmen.length + " geladen: " + spotifyRahmen.map((u) => u.slice(38, 60)).join(", "));
pruefe("Kein Rahmen ist eine Fehlerseite", kaputteRahmen.length === 0, kaputteRahmen.length + " Fehlerseiten");
pruefe("Kein Verstoß gegen die Inhaltsrichtlinie", verstoesse.size === 0, [...verstoesse].join(" | ") || "keine");
await seite.screenshot({ path: "bild-musik-computer.png" });
/* ---------- Schließen ---------- */
console.log("\n== Schließen ==");
await seite.click("#music-close");
await seite.waitForTimeout(300);
pruefe("✕ schließt das Feld", await seite.evaluate(() => document.getElementById("music-panel").hidden), "");
await seite.click("#music-toggle");
await seite.waitForTimeout(300);
await seite.keyboard.press("Escape");
await seite.waitForTimeout(300);
pruefe("Escape schließt das Feld", await seite.evaluate(() => document.getElementById("music-panel").hidden), "");
/* ---------- Handy ---------- */
console.log("\n== Handy (390x844) ==");
const handy = await browser.newPage({ viewport: { width: 390, height: 844 } });
await handy.goto(BASIS + "/index.html", { waitUntil: "domcontentloaded" });
await handy.waitForSelector("#music-toggle", { timeout: 10000 });
await handy.click("#music-toggle");
await handy.waitForTimeout(4000);
const h = await handy.evaluate(() => {
const r = document.getElementById("music-panel").getBoundingClientRect();
return { links: Math.round(r.left), rechts: Math.round(innerWidth - r.right), oben: Math.round(r.top), unten: Math.round(r.bottom),
fensterHoehe: innerHeight, ueberlauf: document.documentElement.scrollWidth - document.documentElement.clientWidth };
});
pruefe("Feld passt seitlich in den Bildschirm", h.links >= 0 && h.rechts >= 0, "links " + h.links + "px, rechts " + h.rechts + "px");
pruefe("Feld ragt nicht oben heraus", h.oben >= 0, "oben " + h.oben + "px");
pruefe("Seite läuft nicht seitlich über", h.ueberlauf <= 1, h.ueberlauf + "px");
const handyRahmen = handy.frames().filter((f) => f.url().startsWith("https://open.spotify.com/")).length;
pruefe("Am Handy laden die Player auch", handyRahmen === 2, handyRahmen + " geladen");
await handy.screenshot({ path: "bild-musik-handy.png" });
await browser.close();
server.kill();
console.log("\n===== " + ok + " ok, " + fehl + " Fehler =====");
if (funde.length) { console.log("\nOffene Punkte:"); funde.forEach((f) => console.log(" - " + f)); }
process.exit(fehl ? 1 : 0);
})().catch((e) => {
console.error("Testlauf abgebrochen:", e.message);
server.kill();
process.exit(1);
});