Umsetzung des "Website Masterplan" (16 Seiten) unter /webdesign. Zugangsschutz mit EIGENER Schranke (server/webdesign-gate.js) statt gate.js: gate.js laesst seit dem oeffentlichen Start am 21.08.2026 jeden durch, weil die Pruefung auf SITE_PUBLIC_LAUNCH_AT als allererste Zeile steht. Haette man /webdesign dahintergehaengt, waere der ausdruecklich nicht-oeffentliche Bereich inklusive Preisen und spaeteren Kundendaten ab der ersten Sekunde fuer jeden lesbar gewesen. Eigenes Sitzungs-Cookie, bereich="webdesign" im Token, damit ein gueltiges Universe-Cookie hier NICHT gilt. 37/37 Tests. Sieben oeffentliche Seiten in fuenf Sprachen (de, de-CH mit echtem Dialekt, en, fr, pt). Preise, Zeitrahmen, Paketnamen und die 30-%-Regel stehen an genau EINER Stelle in wd-core.js -- der Masterplan verlangt "ueberall widerspruchsfrei", und vier Kopien laufen bei der ersten Preisaenderung auseinander. Als App installierbar auf Handy und PC. Der Service Worker speichert bewusst KEINE HTML-Seite zwischen: nach dem Abmelden wuerden sonst geschuetzte Seiten weiter ausgeliefert, ohne dass der Server je gefragt wird. 18/18 Tests. Handy-Abnahme ueber alle Seiten in fuenf Breiten (320-1440) und fuenf Sprachen: 40/40. Der Test fand 35 echte Fehler (Touch-Ziele unter 44px), behoben im Designsystem statt einzeln pro Seite. PayPal (Wunsch 22.08.2026 "sofort auf meinem paypal"): Orders API mit intent=CAPTURE, also sofortiger Einzug statt blosser Reservierung. Gebuehr und Nettobetrag getrennt gespeichert. Betraege durchgehend als Ganzzahl in Cent. Gefaelschte Webhooks werden abgewiesen. Fail closed solange Zugangsdaten fehlen. 28/28 Tests gegen einen nachgebauten PayPal-Server. Datenbank: 15 Tabellen mit Praefix wd_, fachlich vollstaendig vom Universe getrennt. Co-Authored-By: Claude Opus 5 <[email protected]>
89 lines
4.2 KiB
JavaScript
89 lines
4.2 KiB
JavaScript
/* Prüft die App-Installierbarkeit des Webdesign-Bereichs:
|
|
- Manifest erreichbar, gültig, Icons laden
|
|
- Service Worker registriert sich und wird aktiv
|
|
- HTML wird NICHT zwischengespeichert (Sicherheitsvorgabe)
|
|
- Bausteine unter /assets/ werden zwischengespeichert */
|
|
import { chromium } from "playwright";
|
|
const BASIS = "http://127.0.0.1:4199";
|
|
|
|
const browser = await chromium.launch();
|
|
const kontext = await browser.newContext();
|
|
const seite = await kontext.newPage();
|
|
const fehler = [];
|
|
seite.on("console", (m) => { if (m.type() === "error") fehler.push(m.text()); });
|
|
seite.on("pageerror", (e) => fehler.push("pageerror: " + e.message));
|
|
|
|
let ok = 0, schlecht = 0;
|
|
const pruefe = (name, b, zusatz = "") => {
|
|
if (b) { ok++; console.log(" ok " + name); }
|
|
else { schlecht++; console.log(" FEHLT " + name + " " + zusatz); }
|
|
};
|
|
|
|
console.log("\n=== App-Installierbarkeit ===\n");
|
|
|
|
// --- Manifest ohne Anmeldung erreichbar (Installation von der Zugangswand) ---
|
|
console.log("1. Manifest");
|
|
const mRes = await seite.request.get(BASIS + "/webdesign/app.webmanifest");
|
|
pruefe("ohne Anmeldung erreichbar", mRes.status() === 200, "(war " + mRes.status() + ")");
|
|
const manifest = await mRes.json().catch(() => null);
|
|
pruefe("gueltiges JSON", !!manifest);
|
|
if (manifest) {
|
|
pruefe("start_url im Webdesign-Bereich", manifest.start_url === "/webdesign/");
|
|
pruefe("scope begrenzt auf /webdesign/", manifest.scope === "/webdesign/");
|
|
pruefe("display standalone", manifest.display === "standalone");
|
|
pruefe("192er-Symbol vorhanden", manifest.icons.some((i) => i.sizes === "192x192"));
|
|
pruefe("512er-Symbol vorhanden", manifest.icons.some((i) => i.sizes === "512x512"));
|
|
pruefe("maskierbares Symbol vorhanden", manifest.icons.some((i) => (i.purpose || "").includes("maskable")));
|
|
for (const symbol of manifest.icons) {
|
|
const r = await seite.request.get(BASIS + symbol.src);
|
|
pruefe("Symbol laedt: " + symbol.src, r.status() === 200);
|
|
}
|
|
}
|
|
|
|
const swRes = await seite.request.get(BASIS + "/webdesign/sw.js");
|
|
pruefe("Service Worker ohne Anmeldung erreichbar", swRes.status() === 200, "(war " + swRes.status() + ")");
|
|
pruefe("wird als JavaScript ausgeliefert",
|
|
(swRes.headers()["content-type"] || "").includes("javascript"),
|
|
"(war " + swRes.headers()["content-type"] + ")");
|
|
|
|
// --- Registrierung im Browser ---
|
|
console.log("\n2. Service Worker im Browser");
|
|
await seite.goto(BASIS + "/webdesign/zugang.html", { waitUntil: "networkidle" });
|
|
await seite.fill("#zg-code-dogfather", "DOGI-TEST-1");
|
|
await Promise.all([
|
|
seite.waitForURL((u) => !u.pathname.endsWith("zugang.html"), { timeout: 15000 }),
|
|
seite.click("#zg-form-dogfather button[type=submit]"),
|
|
]);
|
|
await seite.waitForLoadState("networkidle");
|
|
|
|
const aktiv = await seite.evaluate(async () => {
|
|
const reg = await navigator.serviceWorker.ready.catch(() => null);
|
|
return reg ? { scope: reg.scope, aktiv: !!reg.active } : null;
|
|
});
|
|
pruefe("registriert und aktiv", !!(aktiv && aktiv.aktiv));
|
|
pruefe("Geltungsbereich ist /webdesign/", !!(aktiv && aktiv.scope.endsWith("/webdesign/")), aktiv ? aktiv.scope : "");
|
|
|
|
// --- Sicherheitsvorgabe: keine HTML-Seiten im Zwischenspeicher ---
|
|
console.log("\n3. Sicherheit des Zwischenspeichers");
|
|
await seite.reload({ waitUntil: "networkidle" });
|
|
await seite.waitForTimeout(1200);
|
|
const inhalt = await seite.evaluate(async () => {
|
|
const namen = await caches.keys();
|
|
const alles = [];
|
|
for (const n of namen) {
|
|
const c = await caches.open(n);
|
|
for (const anfrage of await c.keys()) alles.push(anfrage.url);
|
|
}
|
|
return alles;
|
|
});
|
|
console.log(" Zwischenspeicher enthaelt " + inhalt.length + " Eintraege");
|
|
const htmlDrin = inhalt.filter((u) => u.endsWith(".html") || new URL(u).pathname === "/webdesign/");
|
|
pruefe("KEINE geschuetzte HTML-Seite zwischengespeichert", htmlDrin.length === 0, htmlDrin.join(", "));
|
|
pruefe("Stilvorlage ist zwischengespeichert", inhalt.some((u) => u.includes("webdesign.css")));
|
|
pruefe("Kern-Skript ist zwischengespeichert", inhalt.some((u) => u.includes("wd-core.js")));
|
|
|
|
console.log(fehler.length ? "\nKONSOLENFEHLER:\n" + fehler.join("\n") : "\nKeine Konsolenfehler.");
|
|
await browser.close();
|
|
console.log(`\n=== ${ok} bestanden, ${schlecht} gescheitert ===\n`);
|
|
process.exit(schlecht === 0 && fehler.length === 0 ? 0 : 1);
|