- Figtree als einzige Schrift, Logo mittig in fester Kopfzeile - Headerbild ueber volle Breite und Hoehe, ohne Preis, mit Terminleiste - "Was dich erwartet" stellt die Villa mit animierter Bilderfolge vor, darunter Tagesablauf als Karussell (Programm folgt als PDF) - Preis: regulaerer Preis durchgestrichen, kein Verkaufsstartdatum, Ueberschrift "Jetzt Platz sichern mit Early Bird" - Team als "Mit dabei", danach drei Reels im Hochformat - Logo, Headerbild, Portraets und Reels ueber feste Dateinamen im Medienverzeichnis (docs/MEDIEN.md), ohne Neustart austauschbar - Widerrufsbelehrung im Admin pflegbar, Seite /widerruf, Live-Verkauf ohne Freigabe gesperrt - Anreise: Abholung am Bahnhof Firenze Santa Maria Novella - Booking-Link entfernt, Gedankenstriche aus Kundentexten entfernt Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
82 lines
4.2 KiB
TypeScript
82 lines
4.2 KiB
TypeScript
import AxeBuilder from "@axe-core/playwright";
|
|
import { expect, test } from "@playwright/test";
|
|
|
|
/**
|
|
* Voraussetzung: Stripe-Testschluessel und `pnpm db:seed` (simulierte
|
|
* Early-Bird-Phase, Testgutschein TEST-STAMMMODEL). Die Zahlung selbst wird
|
|
* nicht ausgeloest — der Test endet vor der Weiterleitung zu Stripe.
|
|
*/
|
|
|
|
test("Onepage: Aufbau, kein horizontales Scrollen, Barrierefreiheit", async ({ page }, testInfo) => {
|
|
await page.goto("/");
|
|
await expect(page.getByRole("heading", { level: 1 })).toContainText("ARTISTA");
|
|
for (const id of ["termin", "erwartet", "ablauf", "unterkunft", "preis", "team", "fragen"]) {
|
|
await expect(page.locator(`#${id}`)).toBeAttached();
|
|
}
|
|
// Regulaerer Preis durchgestrichen, kein Verkaufsstartdatum, keine Booking-Seite.
|
|
await expect(page.locator("#preis s")).toContainText("2.750");
|
|
await expect(page.locator("body")).not.toContainText(/öffnet am|Buchung startet|ab 1\. November/);
|
|
await expect(page.locator('a[href*="booking.com"]')).toHaveCount(0);
|
|
// Keine oeffentliche Platzzahl und kein Restplatz-Zaehler.
|
|
await expect(page.locator("body")).not.toContainText(/Restpl|noch \d+ Pl|Plätze frei/);
|
|
|
|
const overflow = await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth);
|
|
expect(overflow).toBeLessThanOrEqual(0);
|
|
|
|
await page.waitForTimeout(1600); // Hero-Eroeffnung abwarten
|
|
await page.screenshot({ path: testInfo.outputPath(`onepage-${testInfo.project.name}.png`), fullPage: true });
|
|
|
|
const results = await new AxeBuilder({ page }).withTags(["wcag2a", "wcag2aa", "wcag21aa"]).analyze();
|
|
expect(results.violations.map((v) => `${v.id}: ${v.nodes.length}`)).toEqual([]);
|
|
});
|
|
|
|
test("Buchungsdialog: Daten, Gutschein, Ratenplan, Erklaerungen, Fokus", async ({ page }, testInfo) => {
|
|
await page.goto("/");
|
|
const trigger = page.locator("#top").getByRole("button", { name: "Jetzt Platz sichern" });
|
|
await trigger.click();
|
|
|
|
const dialog = page.getByRole("dialog");
|
|
await expect(dialog.getByRole("heading", { name: "Deine Daten" })).toBeFocused();
|
|
|
|
// Pflichtfelder werden verstaendlich gemeldet.
|
|
await dialog.getByRole("button", { name: "Weiter" }).click();
|
|
await expect(dialog.getByText("Bitte gib deinen Vornamen an.")).toBeVisible();
|
|
|
|
await dialog.getByLabel("Vorname").fill("Test");
|
|
await dialog.getByLabel("Nachname").fill("Person");
|
|
await dialog.getByLabel("E-Mail").fill("[email protected]");
|
|
await dialog.getByLabel("Telefon").fill("+49 30 123456");
|
|
await dialog.getByRole("button", { name: "Weiter" }).click();
|
|
|
|
await expect(dialog.getByRole("heading", { name: "Zahlungsweise" })).toBeFocused();
|
|
await dialog.getByRole("radio", { name: /Drei Raten/ }).check();
|
|
await dialog.getByLabel("Gutscheincode (optional)").fill("test-stammmodel");
|
|
await dialog.getByRole("button", { name: "Anwenden" }).click();
|
|
await expect(dialog.getByText("Gutschein angewendet.")).toBeVisible();
|
|
await expect(dialog.getByText("1.980,00 €").first()).toBeVisible();
|
|
|
|
await dialog.getByRole("button", { name: "Weiter" }).click();
|
|
await expect(dialog.getByText("Heute fällig: 660,00 €", { exact: false })).toBeVisible();
|
|
await expect(dialog.getByText("Nicht enthalten:", { exact: false })).toBeVisible();
|
|
|
|
// Ohne Erklaerungen keine Buchung.
|
|
await dialog.getByRole("button", { name: "Zahlungspflichtig buchen" }).click();
|
|
await expect(dialog.getByText("Bitte stimme dem Einzug der weiteren Raten zu.")).toBeVisible();
|
|
await dialog.screenshot({ path: testInfo.outputPath(`dialog-${testInfo.project.name}.png`) });
|
|
|
|
// Escape schliesst, Fokus kehrt zum ausloesenden Button zurueck.
|
|
await page.keyboard.press("Escape");
|
|
await expect(dialog).toBeHidden();
|
|
await expect(trigger).toBeFocused();
|
|
});
|
|
|
|
test("Verwaltung und Buchungsstatus sind geschuetzt und nicht indexierbar", async ({ page, request }) => {
|
|
await page.goto("/admin/buchungen");
|
|
await expect(page).toHaveURL(/\/admin\/anmelden$/);
|
|
const exportResponse = await request.get("/admin/export", { maxRedirects: 0, headers: { cookie: "better-auth.session_token=gefaelscht" } });
|
|
expect(exportResponse.status()).toBe(401);
|
|
const status = await request.get("/buchung/status/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
|
expect(status.status()).toBe(404);
|
|
expect(status.headers()["x-robots-tag"]).toContain("noindex");
|
|
});
|