Zwei Rueckmeldungen vom 22.08.2026, beide behoben und mit Tests abgesichert.
1) "das sieht nicht gut aus" (Bildschirmfoto des Hauptknopfs)
Ursache: ".wd a" ist Klasse+Element und damit spezifischer als die reine
Knopfklasse ".wd-btn--haupt". Die Textfarbe des Knopfs wurde dadurch
ueberstimmt -- hellblaue Schrift auf hellblauem Grund, praktisch
unlesbar. Derselbe Spezifitaetsfehler wie zuvor bei den Namen auf der
Zugangswand. Fix: ".wd a:not(.wd-btn)" plus zweistufig geschriebene
Knopfregeln, damit das nicht wieder passieren kann.
2) "das sieht lang gezogen aus"
Die Pillenform (border-radius 999px) laesst breite Knoepfe
auseinandergezogen wirken, weil der Radius optisch mit der Breite
mitwaechst. Jetzt fester Radius von 14px -- gleiche Form bei jeder
Breite, ruhiger und hochwertiger.
3) "ich will das ich jedes mal den code gefragt werde wenn man die seite
zu macht"
Das Sitzungs-Cookie allein reicht dafuer nicht: Browser stellen genau
solche Cookies beim Wiederherstellen von Tabs zurueck ("Dort
fortfahren, wo du aufgehoert hast"), man landet dann ohne Codeabfrage
wieder mitten in der Seite. Zusaetzlich jetzt eine Sitzungsmarke im
sessionStorage, die beim echten Schliessen verschwindet. Fehlt sie bei
vorhandenem Cookie, wird die Sitzung serverseitig beendet und zur
Zugangswand geleitet. Token-Notbremse von 24 auf 8 Stunden gesenkt.
13/13 Tests im echten Browser, inklusive Schutz vor Endlosschleife.
DEPLOY.md: zwei Checkouts auf dem Server dokumentiert (/home/dogiweb und
/home/dogiintern) und der Vorfall, dass /webdesign nach dem Pull kurz ohne
Zugangsschutz erreichbar war, weil der Dienstneustart fehlte.
Co-Authored-By: Claude Opus 5 <[email protected]>
158 lines
6.6 KiB
JavaScript
158 lines
6.6 KiB
JavaScript
/* =====================================================================
|
|
test-webdesign-sitzung.mjs — "Beim Schließen der Seite wieder fragen"
|
|
|
|
Wunsch 22.08.2026: "ich will das ich jedes mal den code gefragt werde
|
|
wenn man die seite zu macht."
|
|
|
|
Das laesst sich nur in einem echten Browser pruefen, weil genau der
|
|
Punkt der schwierige ist: Browser stellen Sitzungs-Cookies beim
|
|
Wiederherstellen von Tabs zurueck. Playwright bildet das nach:
|
|
- neue Seite im GLEICHEN Kontext = neuer Tab, Cookie noch da,
|
|
sessionStorage leer -> muss erneut fragen
|
|
- gleiche Seite weiternavigieren = laufende Sitzung -> darf NICHT fragen
|
|
|
|
Voraussetzung: lokaler Testserver auf 4199 laeuft.
|
|
===================================================================== */
|
|
import { chromium } from "playwright";
|
|
|
|
const BASIS = "http://127.0.0.1:4199";
|
|
const CODE = "DOGI-TEST-1";
|
|
|
|
let ok = 0, schlecht = 0;
|
|
const pruefe = (name, b, zusatz = "") => {
|
|
if (b) { ok++; console.log(" ok " + name); }
|
|
else { schlecht++; console.log(" FEHLT " + name + " " + zusatz); }
|
|
};
|
|
|
|
const browser = await chromium.launch();
|
|
|
|
console.log("\n=== Zugang bei jedem Schliessen ===\n");
|
|
|
|
/* ---------- 1. Anmelden und normal weiterklicken ---------- */
|
|
console.log("1. Laufende Sitzung");
|
|
const kontext = await browser.newContext();
|
|
const seite = await kontext.newPage();
|
|
|
|
await seite.goto(BASIS + "/webdesign/zugang.html", { waitUntil: "networkidle" });
|
|
await seite.fill("#zg-code-dogfather", CODE);
|
|
await Promise.all([
|
|
seite.waitForURL((u) => !u.pathname.endsWith("zugang.html"), { timeout: 15000 }),
|
|
seite.click("#zg-form-dogfather button[type=submit]"),
|
|
]);
|
|
pruefe("Anmeldung fuehrt in den Bereich", seite.url().endsWith("/webdesign/"), seite.url());
|
|
|
|
await seite.goto(BASIS + "/webdesign/preise.html", { waitUntil: "networkidle" });
|
|
await seite.waitForTimeout(400);
|
|
pruefe("Weiterklicken fragt NICHT erneut", !seite.url().includes("zugang.html"), seite.url());
|
|
|
|
await seite.reload({ waitUntil: "networkidle" });
|
|
await seite.waitForTimeout(400);
|
|
pruefe("Neuladen fragt NICHT erneut", !seite.url().includes("zugang.html"), seite.url());
|
|
|
|
const markeDa = await seite.evaluate(() => sessionStorage.getItem("wd-sitzung-aktiv"));
|
|
pruefe("Sitzungsmarke ist gesetzt", markeDa === "1");
|
|
|
|
/* ---------- 2. Seite zu, Cookie bleibt (Tab-Wiederherstellung) ---------- */
|
|
console.log("\n2. Seite geschlossen, Cookie vom Browser wiederhergestellt");
|
|
{
|
|
const kekse = await kontext.cookies();
|
|
const sitzungsKeks = kekse.find((c) => c.name === "dogiwd_session");
|
|
pruefe("Sitzungs-Cookie hat KEIN Ablaufdatum",
|
|
!sitzungsKeks || sitzungsKeks.expires === -1,
|
|
"-> der Browser wirft es beim Schliessen selbst weg");
|
|
|
|
/* Neuer Tab im selben Kontext: Cookies bleiben (wie bei
|
|
"Tabs wiederherstellen"), sessionStorage ist aber leer. Genau der
|
|
Fall, in dem das Cookie allein NICHT reichen wuerde. */
|
|
const neuerTab = await kontext.newPage();
|
|
await neuerTab.goto(BASIS + "/webdesign/preise.html", { waitUntil: "networkidle" });
|
|
await neuerTab.waitForTimeout(1200);
|
|
pruefe("neuer Tab verlangt den Code erneut",
|
|
neuerTab.url().includes("zugang.html"),
|
|
"war " + neuerTab.url());
|
|
|
|
const nachher = await kontext.cookies();
|
|
const nochDa = nachher.find((c) => c.name === "dogiwd_session");
|
|
pruefe("die alte Sitzung wurde serverseitig beendet", !nochDa,
|
|
"-> nicht nur weggeklickt, sondern wirklich abgemeldet");
|
|
|
|
await neuerTab.close();
|
|
}
|
|
|
|
/* ---------- 3. Komplett neuer Browser ---------- */
|
|
console.log("\n3. Browser komplett neu gestartet");
|
|
{
|
|
const frisch = await browser.newContext();
|
|
const s = await frisch.newPage();
|
|
await s.goto(BASIS + "/webdesign/", { waitUntil: "networkidle" });
|
|
pruefe("frischer Browser landet auf der Zugangswand", s.url().includes("zugang.html"), s.url());
|
|
await frisch.close();
|
|
}
|
|
|
|
/* ---------- 4. Keine Endlosschleife ---------- */
|
|
console.log("\n4. Keine Endlosschleife nach dem Anmelden");
|
|
{
|
|
const k = await browser.newContext();
|
|
const s = await k.newPage();
|
|
let umleitungen = 0;
|
|
s.on("framenavigated", (f) => { if (f === s.mainFrame()) umleitungen++; });
|
|
|
|
await s.goto(BASIS + "/webdesign/zugang.html", { waitUntil: "networkidle" });
|
|
await s.fill("#zg-code-dogfather", CODE);
|
|
await Promise.all([
|
|
s.waitForURL((u) => !u.pathname.endsWith("zugang.html"), { timeout: 15000 }),
|
|
s.click("#zg-form-dogfather button[type=submit]"),
|
|
]);
|
|
await s.waitForTimeout(1500);
|
|
pruefe("bleibt nach der Anmeldung im Bereich", !s.url().includes("zugang.html"), s.url());
|
|
pruefe("keine auffaellige Umleitungsschleife", umleitungen < 8, umleitungen + " Navigationen");
|
|
await k.close();
|
|
}
|
|
|
|
/* ---------- 5. Knopf-Lesbarkeit (der gemeldete Fehler) ---------- */
|
|
console.log("\n5. Lesbarkeit des Hauptknopfs");
|
|
{
|
|
/* Frischer Kontext mit eigener Anmeldung: Schritt 2 hat die Sitzung von
|
|
`seite` absichtlich beendet -- dort wuerde jetzt die Zugangswand
|
|
stehen und der Knopf gar nicht existieren. */
|
|
const k5 = await browser.newContext();
|
|
const seite5 = await k5.newPage();
|
|
await seite5.goto(BASIS + "/webdesign/zugang.html", { waitUntil: "networkidle" });
|
|
await seite5.fill("#zg-code-dogfather", CODE);
|
|
await Promise.all([
|
|
seite5.waitForURL((u) => !u.pathname.endsWith("zugang.html"), { timeout: 15000 }),
|
|
seite5.click("#zg-form-dogfather button[type=submit]"),
|
|
]);
|
|
await seite5.waitForLoadState("networkidle");
|
|
const skip = seite5.locator("#wd-intro-skip");
|
|
if (await skip.isVisible().catch(() => false)) await skip.click();
|
|
await seite5.waitForTimeout(300);
|
|
|
|
const befund = await seite5.evaluate(() => {
|
|
const knopf = document.querySelector(".wd-hero .wd-btn--haupt");
|
|
if (!knopf) return null;
|
|
const s = getComputedStyle(knopf);
|
|
const zahl = (f) => (f.match(/[\d.]+/g) || []).slice(0, 3).map(Number);
|
|
return { farbe: zahl(s.color), radius: s.borderRadius, text: knopf.textContent.trim() };
|
|
});
|
|
|
|
pruefe("Hauptknopf gefunden", !!befund);
|
|
if (befund) {
|
|
/* Relative Helligkeit nach WCAG. Der Knopfgrund ist hellblau (~0.55) --
|
|
eine helle Schrift darauf waere der gemeldete Fehler. */
|
|
const [r, g, b] = befund.farbe;
|
|
const lin = (v) => { const x = v / 255; return x <= 0.04045 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4); };
|
|
const hell = 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
|
|
pruefe("Schrift auf dem Hauptknopf ist DUNKEL", hell < 0.1,
|
|
`Helligkeit ${hell.toFixed(3)}, Farbe rgb(${befund.farbe.join(",")})`);
|
|
pruefe("Knopf ist kein Pillen-Oval mehr",
|
|
!/999/.test(befund.radius) && parseInt(befund.radius, 10) <= 20,
|
|
befund.radius);
|
|
}
|
|
await k5.close();
|
|
}
|
|
|
|
await browser.close();
|
|
console.log(`\n=== ${ok} bestanden, ${schlecht} gescheitert ===\n`);
|
|
process.exit(schlecht === 0 ? 0 : 1);
|