Files
dogfather-universe/server/test-webdesign-formular.mjs
DogFatherGitandClaude Opus 5 eaeaec6b56 Anfrageformular fertig - vier Schritte, Zwischenspeicherung, echte Absendung
Masterplan S.10 "Projektanfrage ohne Informationsverlust" umgesetzt.
End-to-End gegen die echte Datenbank belegt: Anfrage A-2608-0002 liegt drin.
38/38 Browsertests, 45/45 Handy-Abnahme.

Aufbau: 4 Schritte + Zusammenfassung + Dankeseite.
- Zwischenspeicherung nach jedem Tastendruck. Geschlossener Tab, leerer
  Akku oder versehentliches Zurueck kosten keine Arbeit. Entwuerfe
  verfallen nach 30 Tagen -- ein halbes Jahr alter Entwurf verwirrt mehr
  als er hilft.
- Folgefragen erscheinen nur passend zur Auswahl und werden beim
  Zurueckwechseln NICHT mitgeschickt, sonst landen alte Shop-Antworten in
  einer Onepager-Anfrage.
- Die Zusammenfassung wird aus den Feldern gelesen, nicht aus einem
  nebenher gepflegten Objekt -- sie kann dadurch nie etwas anderes
  behaupten als das, was tatsaechlich abgeschickt wird.
- Telefon wird zur Pflicht, sobald "Per Telefon" gewaehlt ist. Ein Wunsch,
  der nicht erfuellbar ist, ist schlimmer als eine Pflichtangabe.

Drei Fehler, die erst der Test gefunden hat:
1. Der "Zurueck"-Knopf stand auch auf Schritt 1 da. Ursache: das
   hidden-Attribut wird nur ueber "display:none" umgesetzt und verliert
   gegen jedes eigene display -- und Knoepfe sind inline-flex. Zentral
   behoben ueber ".wd [hidden] { display:none !important }".
2. Nach erfolgreichem Absenden legte zeigeSeite() den geloeschten Entwurf
   sofort wieder an. Beim naechsten Besuch haette eine bereits gesendete
   Anfrage erneut im Formular gestanden.
3. Der Server meldet bei Spamverdacht bewusst Erfolg ohne Nummer. Ein
   echter Mensch, der zufaellig sehr schnell war, haette eine Dankeseite
   mit "—" gesehen und auf eine Antwort gewartet, die nie kommt. Jetzt
   ein ehrlicher Hinweis mit zweitem Weg, und der Entwurf bleibt erhalten.

Pruefskripte geschaerft:
- i18n-Pruefung liest jetzt auch assets/js/wd-<seite>.js, sonst meldet sie
  reihenweise "unbenutzt" fuer Schluessel, die aus dem Seitenskript kommen.
- Handy-Abnahme ueberspringt absichtlich unsichtbare Eingabefelder
  (Auswahlkacheln, Honigtopf). Fehlalarme sind das Ende jeder Pruefung,
  weil man sie irgendwann pauschal ignoriert.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-22 18:47:45 +02:00

217 lines
10 KiB
JavaScript

/* =====================================================================
test-webdesign-formular.mjs — Anfrageformular im echten Browser
Spielt das Formular so durch, wie ein Mensch es tut, und prüft die
Dinge, die man beim Klicken NICHT sieht:
- Pflichtfelder blockieren das Weitergehen
- Folgefragen erscheinen nur passend zur Auswahl
- Eingaben überleben das Schließen des Tabs
- die Zusammenfassung zeigt wirklich das Eingegebene
- Absenden erzeugt eine echte Anfragenummer
Standardmäßig wird NICHT abgeschickt (sonst entsteht bei jedem Lauf ein
Datensatz in der echten Datenbank). Mit --senden wird einmal wirklich
abgeschickt:
node test-webdesign-formular.mjs --senden
===================================================================== */
import { chromium } from "playwright";
const BASIS = "http://127.0.0.1:4199";
const CODE = "DOGI-TEST-1";
const WIRKLICH_SENDEN = process.argv.includes("--senden");
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();
const kontext = await browser.newContext();
const seite = await kontext.newPage();
const konsole = [];
seite.on("console", (m) => { if (m.type() === "error") konsole.push(m.text()); });
seite.on("pageerror", (e) => konsole.push("pageerror: " + e.message));
async function anmelden(p) {
await p.goto(BASIS + "/webdesign/zugang.html", { waitUntil: "networkidle" });
await p.fill("#zg-code-dogfather", CODE);
await Promise.all([
p.waitForURL((u) => !u.pathname.endsWith("zugang.html"), { timeout: 15000 }),
p.click("#zg-form-dogfather button[type=submit]"),
]);
}
console.log("\n=== Anfrageformular im Browser ===\n");
await anmelden(seite);
/* ---------- 1. Aufbau ---------- */
console.log("1. Aufbau");
await seite.goto(BASIS + "/webdesign/anfrage.html", { waitUntil: "networkidle" });
await seite.waitForTimeout(400);
pruefe("Schritt 1 ist sichtbar", await seite.locator('[data-seite="1"]').isVisible());
pruefe("Schritt 2 ist verborgen", !(await seite.locator('[data-seite="2"]').isVisible()));
pruefe("Zaehler zeigt Schritt 1 von 4", (await seite.locator("#af-zaehler").textContent()).includes("1"));
pruefe("Zurueck ist auf Schritt 1 ausgeblendet", !(await seite.locator("#af-zurueck").isVisible()));
pruefe("Tabelle 'Was hast du schon' wurde gebaut",
(await seite.locator("#af-vorhanden tr").count()) === 4);
/* ---------- 2. Pflichtfelder ---------- */
console.log("\n2. Pflichtfelder blockieren");
await seite.click("#af-weiter");
await seite.waitForTimeout(400);
pruefe("bleibt auf Schritt 1", await seite.locator('[data-seite="1"]').isVisible());
pruefe("Paket wird bemaengelt", await seite.locator("#feld-paket.wd-feld--fehler").count() > 0);
pruefe("Ziel wird bemaengelt", await seite.locator("#feld-ziel.wd-feld--fehler").count() > 0);
pruefe("Sammelmeldung erscheint", await seite.locator("#af-meldung").isVisible());
/* ---------- 3. Vorauswahl per Adresszeile ---------- */
console.log("\n3. Vorauswahl ueber ?paket=");
await seite.goto(BASIS + "/webdesign/anfrage.html?paket=shop", { waitUntil: "networkidle" });
await seite.waitForTimeout(400);
pruefe("Paket 'shop' ist vorausgewaehlt",
await seite.locator('[name="paket"][value="shop"]').isChecked());
/* ---------- 4. Durchlauf ---------- */
console.log("\n4. Durchlauf");
await seite.fill("#af-ziel", "Wir wollen unsere Haekelwerke endlich online verkaufen.");
await seite.fill("#af-zielgruppe", "Bastelfans, 25-55 Jahre");
await seite.click("#af-weiter");
await seite.waitForTimeout(500);
pruefe("Schritt 2 erreicht", await seite.locator('[data-seite="2"]').isVisible());
pruefe("Shop-Folgefragen erscheinen bei Paket 'shop'",
await seite.locator("#af-folge-shop").isVisible(),
"-> Masterplan S.10 'Intelligente Felder'");
pruefe("Verwaltungs-Folgefragen bleiben verborgen",
!(await seite.locator("#af-folge-verwaltung").isVisible()));
await seite.fill("#af-shop-anzahl", "40");
await seite.check('[name="funktionen"][value="galerie"]');
await seite.check('[name="funktionen"][value="login"]');
await seite.waitForTimeout(300);
pruefe("Verwaltungs-Folgefragen erscheinen nach 'Kundenbereich'",
await seite.locator("#af-folge-verwaltung").isVisible());
await seite.click("#af-weiter");
await seite.waitForTimeout(500);
pruefe("Schritt 3 erreicht", await seite.locator('[data-seite="3"]').isVisible());
await seite.check('[name="v_texte"][value="teilweise"]');
await seite.check('[name="v_bilder"][value="ja"]');
await seite.check('[name="v_logo"][value="nein"]');
await seite.check('[name="v_domain"][value="ja"]');
await seite.selectOption("#af-budget", "1000-2500");
await seite.fill("#af-termin", "moeglichst im Oktober");
await seite.click("#af-weiter");
await seite.waitForTimeout(500);
pruefe("Schritt 4 erreicht", await seite.locator('[data-seite="4"]').isVisible());
pruefe("Knopf heisst jetzt 'Angaben pruefen'",
/pr[üu]fen/i.test(await seite.locator("#af-weiter").textContent()));
/* ---------- 5. Telefonpflicht ---------- */
console.log("\n5. Telefon wird Pflicht bei 'Per Telefon'");
await seite.fill("#af-name", "Testfirma Meier");
await seite.fill("#af-email", "[email protected]");
await seite.check('[name="kontaktweg"][value="telefon"]');
await seite.check("#af-datenschutz");
await seite.click("#af-weiter");
await seite.waitForTimeout(400);
pruefe("ohne Nummer wird blockiert", await seite.locator("#feld-telefon.wd-feld--fehler").count() > 0,
"-> sonst ein Wunsch, der gar nicht erfuellbar ist");
await seite.check('[name="kontaktweg"][value="email"]');
/* ---------- 6. Datenschutz ---------- */
await seite.uncheck("#af-datenschutz");
await seite.click("#af-weiter");
await seite.waitForTimeout(400);
pruefe("ohne Datenschutz-Zustimmung wird blockiert",
await seite.locator("#feld-datenschutz.wd-feld--fehler").count() > 0);
await seite.check("#af-datenschutz");
await seite.fill("#af-bemerkung", "Bitte moeglichst bald melden.");
/* ---------- 7. Zusammenfassung ---------- */
console.log("\n6. Zusammenfassung");
await seite.click("#af-weiter");
await seite.waitForTimeout(600);
pruefe("Zusammenfassung erreicht", await seite.locator('[data-seite="5"]').isVisible());
const zusText = await seite.locator("#af-zusammenfassung").textContent();
pruefe("zeigt das eingegebene Ziel", zusText.includes("Haekelwerke"));
pruefe("zeigt Name und E-Mail", zusText.includes("Testfirma Meier") && zusText.includes("[email protected]"));
pruefe("zeigt angekreuzte Funktionen", /Galerie|galerie/i.test(zusText));
pruefe("zeigt die Shop-Folgefrage", zusText.includes("40"));
pruefe("leere Felder als 'keine Angabe'", /keine Angabe/i.test(zusText));
pruefe("Knopf heisst jetzt 'Anfrage absenden'",
/absenden/i.test(await seite.locator("#af-weiter").textContent()));
pruefe("jeder Block hat einen Aendern-Knopf",
(await seite.locator("#af-zusammenfassung [data-zu]").count()) === 4);
await seite.locator('[data-zu="1"]').click();
await seite.waitForTimeout(400);
pruefe("'Aendern' springt zum richtigen Schritt", await seite.locator('[data-seite="1"]').isVisible());
pruefe("Eingaben sind dabei erhalten",
(await seite.inputValue("#af-ziel")).includes("Haekelwerke"));
/* ---------- 8. Zwischenspeicherung ---------- */
console.log("\n7. Zwischenspeicherung");
const neuerTab = await kontext.newPage();
await anmelden(neuerTab);
await neuerTab.goto(BASIS + "/webdesign/anfrage.html", { waitUntil: "networkidle" });
await neuerTab.waitForTimeout(600);
pruefe("Hinweis auf wiederhergestellte Eingaben erscheint",
await neuerTab.locator("#af-wieder").isVisible());
pruefe("Ziel wurde wiederhergestellt",
(await neuerTab.inputValue("#af-ziel")).includes("Haekelwerke"));
pruefe("Name wurde wiederhergestellt",
(await neuerTab.inputValue("#af-name")) === "Testfirma Meier");
pruefe("Paketwahl wurde wiederhergestellt",
await neuerTab.locator('[name="paket"][value="shop"]').isChecked());
await neuerTab.click("#af-verwerfen");
await neuerTab.waitForTimeout(600);
pruefe("'Neu anfangen' leert das Formular",
(await neuerTab.inputValue("#af-ziel")) === "");
await neuerTab.close();
/* ---------- 9. Absenden ---------- */
console.log("\n8. Absenden");
if (!WIRKLICH_SENDEN) {
console.log(" (uebersprungen -- mit --senden wird wirklich abgeschickt)");
} else {
await seite.goto(BASIS + "/webdesign/anfrage.html", { waitUntil: "networkidle" });
/* Der Server weist Absendungen unter 3 Sekunden als vermutlich
automatisch ab (Spamschutz, siehe routes/webdesign-anfragen.js).
Ein Testlauf ist naturgemaess schneller als ein Mensch -- ohne diese
Wartezeit prueft der Test versehentlich den Spamschutz statt des
Formulars. Genau so beim ersten Lauf passiert. */
await seite.waitForTimeout(3500);
await seite.check('[name="paket"][value="onepager"]');
await seite.fill("#af-ziel", "TESTANFRAGE aus dem Selbsttest - bitte archivieren.");
await seite.click("#af-weiter"); await seite.waitForTimeout(400);
await seite.click("#af-weiter"); await seite.waitForTimeout(400);
await seite.click("#af-weiter"); await seite.waitForTimeout(400);
await seite.fill("#af-name", "Selbsttest");
await seite.fill("#af-email", "[email protected]");
await seite.check("#af-datenschutz");
await seite.click("#af-weiter"); await seite.waitForTimeout(600);
await seite.click("#af-weiter");
await seite.waitForTimeout(6000);
const aufDanke = await seite.locator('[data-seite="6"]').isVisible();
pruefe("Dankeseite erscheint", aufDanke,
aufDanke ? "" : "Meldung: " + (await seite.locator("#af-meldung").textContent().catch(() => "")));
if (aufDanke) {
const nummer = (await seite.locator("#af-nummer").textContent()).trim();
pruefe("echte Anfragenummer im Format A-JJMM-0001", /^A-\d{4}-\d{4}$/.test(nummer), nummer);
console.log(" -> Nummer: " + nummer);
pruefe("Entwurf wurde nach Erfolg geloescht",
(await seite.evaluate(() => localStorage.getItem("wd-anfrage-entwurf"))) === null);
pruefe("Schrittanzeige ist auf der Dankeseite weg",
!(await seite.locator("#af-fortschritt").isVisible()));
}
}
await browser.close();
console.log(konsole.length ? "\nKONSOLENFEHLER:\n" + [...new Set(konsole)].join("\n") : "\nKeine Konsolenfehler.");
console.log(`\n=== ${ok} bestanden, ${schlecht} gescheitert ===\n`);
process.exit(schlecht === 0 && konsole.length === 0 ? 0 : 1);