317 lines
11 KiB
JavaScript
317 lines
11 KiB
JavaScript
import { test, expect } from "@playwright/test";
|
|
|
|
import { packages } from "../../shared/packages.js";
|
|
import { packagesDetails } from "../../shared/packagesdetails.js";
|
|
|
|
const pageUrl = "/booking.html#/buchen/ernaehrung-starter";
|
|
async function fill(page) {
|
|
if (!(await page.locator("dialog[open]").count())) {
|
|
await page
|
|
.getByRole("button", { name: "Jetzt buchen", exact: true })
|
|
.click();
|
|
}
|
|
await page
|
|
.getByLabel("Name (Pflichtfeld)", { exact: true })
|
|
.fill("Test Person");
|
|
await page
|
|
.getByLabel("E-Mail-Adresse (Pflichtfeld)")
|
|
.fill("person@example.test");
|
|
await page.locator('[name="consent"]').check();
|
|
}
|
|
|
|
test("Tastaturbedienung, Validierung und vollständige Buchung über Vite-Proxy", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto(pageUrl);
|
|
await expect(
|
|
page.getByRole("heading", { name: packagesDetails[0].title }),
|
|
).toBeVisible();
|
|
await expect(page.locator(".booking__payment-notice")).toContainText(
|
|
"Zahlungsvorgang wird übersprungen",
|
|
);
|
|
await expect(page.locator(".booking__payment-notice")).toContainText(
|
|
"keine Abbuchung",
|
|
);
|
|
const opener = page.getByRole("button", {
|
|
name: "Jetzt buchen",
|
|
exact: true,
|
|
});
|
|
await opener.focus();
|
|
await page.keyboard.press("Enter");
|
|
await expect(
|
|
page.getByLabel("Name (Pflichtfeld)", { exact: true }),
|
|
).toBeFocused();
|
|
await page.keyboard.press("Enter");
|
|
await expect(page.getByRole("alert")).toBeFocused();
|
|
await expect(page.locator('[aria-invalid="true"]')).toHaveCount(3);
|
|
await page.keyboard.press("Tab");
|
|
await page.keyboard.type("Test Person");
|
|
await page.keyboard.press("Tab");
|
|
await page.keyboard.type("person@example.test");
|
|
await page.keyboard.press("Tab");
|
|
await page.keyboard.press("Tab");
|
|
await expect(page.locator('[name="consent"]')).toBeFocused();
|
|
await page.keyboard.press("Space");
|
|
await page.keyboard.press("Tab");
|
|
await page.keyboard.press("Tab");
|
|
await page.keyboard.press("Tab");
|
|
const submit = page.getByRole("button", {
|
|
name: "Paket buchen",
|
|
exact: true,
|
|
});
|
|
await expect(submit).toBeFocused();
|
|
expect(
|
|
await submit.evaluate(
|
|
(element) => getComputedStyle(element).outlineStyle,
|
|
),
|
|
).toBe("solid");
|
|
await page.keyboard.press("Enter");
|
|
await expect(page).toHaveURL(/booking-confirmation\.html$/);
|
|
await expect(page.locator("#confirmation-receipt")).toContainText(
|
|
/TH-\d{6,}/,
|
|
);
|
|
await page.reload();
|
|
await expect(page.locator("#confirmation-receipt")).toContainText(
|
|
/TH-\d{6,}/,
|
|
);
|
|
});
|
|
|
|
test("Schmales Display: Formular ohne horizontalen Überlauf", async ({
|
|
page,
|
|
}) => {
|
|
await page.setViewportSize({ width: 320, height: 640 });
|
|
await page.goto(pageUrl);
|
|
await fill(page);
|
|
expect(
|
|
await page.evaluate(
|
|
() => document.documentElement.scrollWidth <= window.innerWidth,
|
|
),
|
|
).toBe(true);
|
|
await expect(
|
|
page.getByRole("button", { name: "Paket buchen", exact: true }),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("Verlorene Antwort: Wiederholung nach Neuladen behält Schlüssel und Buchungsnummer", async ({
|
|
page,
|
|
}) => {
|
|
let firstKey;
|
|
let firstBooking;
|
|
let requests = 0;
|
|
await page.route("**/api/bookings", async (route) => {
|
|
requests++;
|
|
if (requests === 1) {
|
|
firstKey = route.request().headers()["idempotency-key"];
|
|
const response = await route.fetch();
|
|
firstBooking = (await response.json()).bookingId;
|
|
await route.abort("failed");
|
|
} else {
|
|
expect(route.request().headers()["idempotency-key"]).toBe(firstKey);
|
|
await route.continue();
|
|
}
|
|
});
|
|
await page.goto(pageUrl);
|
|
await fill(page);
|
|
await page
|
|
.getByRole("button", { name: "Paket buchen", exact: true })
|
|
.click();
|
|
await expect(page.getByRole("alert")).toBeVisible();
|
|
await page.reload();
|
|
await page
|
|
.getByRole("button", { name: "Jetzt buchen", exact: true })
|
|
.click();
|
|
await expect(
|
|
page.getByLabel("Name (Pflichtfeld)", { exact: true }),
|
|
).toHaveValue("Test Person");
|
|
await expect(
|
|
page.getByLabel("Name (Pflichtfeld)", { exact: true }),
|
|
).toHaveAttribute("readonly", "");
|
|
await page
|
|
.getByRole("button", { name: "Status prüfen / erneut versuchen" })
|
|
.click();
|
|
await expect(page.locator("#confirmation-receipt")).toContainText(
|
|
firstBooking,
|
|
);
|
|
expect(requests).toBe(2);
|
|
});
|
|
|
|
test("Ladezustand verhindert doppelte Klicks; Mailfehler behauptet keinen vollständigen Erfolg", async ({
|
|
page,
|
|
}) => {
|
|
let release;
|
|
const gate = new Promise((resolve) => {
|
|
release = resolve;
|
|
});
|
|
await page.route("**/api/bookings", async (route) => {
|
|
await gate;
|
|
await route.fulfill({
|
|
status: 202,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
saved: true,
|
|
bookingId: "test-mailfehler",
|
|
emailStatus: "failed",
|
|
}),
|
|
});
|
|
});
|
|
await page.goto(pageUrl);
|
|
await fill(page);
|
|
await page
|
|
.getByRole("button", { name: "Paket buchen", exact: true })
|
|
.click();
|
|
await expect(
|
|
page.getByRole("button", { name: "Buchung wird verarbeitet …" }),
|
|
).toBeDisabled();
|
|
release();
|
|
await expect(page.locator(".booking__result")).toContainText(
|
|
"Bestätigungsversand ist fehlgeschlagen",
|
|
);
|
|
await expect(page.locator(".booking__result")).toContainText(
|
|
"Bitte nicht erneut buchen",
|
|
);
|
|
await expect(
|
|
page.getByRole("button", { name: "Buchung gespeichert" }),
|
|
).toBeDisabled();
|
|
});
|
|
|
|
test("Fehlende und unbekannte Paket-ID zeigen kein Formular", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/booking.html");
|
|
await expect(page.getByText("Bitte wähle zuerst ein Paket")).toBeVisible();
|
|
await expect(page.locator("form")).toHaveCount(0);
|
|
await page.goto("/booking.html#/buchen/unbekannt");
|
|
await expect(
|
|
page.getByText("Dieses Paket ist nicht verfügbar"),
|
|
).toBeVisible();
|
|
await expect(page.locator("form")).toHaveCount(0);
|
|
});
|
|
|
|
test("Vite liefert Serverdateien und Umgebungsdateien nicht aus", async ({
|
|
request,
|
|
}) => {
|
|
for (const path of [
|
|
"/src/server/index.js",
|
|
"/src/server/data/.gitkeep",
|
|
"/.env.example",
|
|
]) {
|
|
const response = await request.get(path);
|
|
expect([403, 404]).toContain(response.status());
|
|
}
|
|
});
|
|
|
|
for (const pkg of packages) {
|
|
test(`Von der Startseite zur Buchung: ${pkg.type}`, async ({ page }) => {
|
|
await page.goto("/");
|
|
await page
|
|
.getByRole("link", { name: "Ernährungswissen entdecken" })
|
|
.click();
|
|
await expect(page.locator(".package-card")).toHaveCount(
|
|
packages.length,
|
|
);
|
|
await page.getByRole("link", { name: pkg.buttonText }).click();
|
|
await expect(page).toHaveURL(
|
|
new RegExp(`paket-details\\.html\\?id=${pkg.id}$`),
|
|
);
|
|
await page.locator("#proceed-to-booking-btn").click();
|
|
await expect(page.locator("dialog[open]")).toBeVisible();
|
|
const details = packagesDetails.find((entry) => entry.id === pkg.id);
|
|
await expect(page.locator(".booking__package-name")).toHaveText(
|
|
details.title,
|
|
);
|
|
await expect(page.locator(".booking__summary")).toHaveText(
|
|
details.summaryForBooking,
|
|
);
|
|
await expect(page.locator(".booking__features li")).toHaveCount(
|
|
details.includedFeatures.length,
|
|
);
|
|
await fill(page);
|
|
const responsePromise = page.waitForResponse(
|
|
(response) =>
|
|
response.url().endsWith("/api/bookings") &&
|
|
response.request().method() === "POST",
|
|
);
|
|
await page
|
|
.getByRole("button", { name: "Paket buchen", exact: true })
|
|
.click();
|
|
const response = await responsePromise;
|
|
expect(response.status()).toBe(201);
|
|
await expect(page).toHaveURL(/booking-confirmation\.html$/);
|
|
const booking = await page.evaluate(() =>
|
|
JSON.parse(sessionStorage.getItem("trihub.booking.confirmation")),
|
|
);
|
|
expect(booking.packageId).toBe(pkg.id);
|
|
expect(booking.packageName).toBe(pkg.name);
|
|
await expect(page.locator("#confirmation-receipt")).toContainText(
|
|
booking.bookingId,
|
|
);
|
|
await page.goto("/angebotsuebersicht.html");
|
|
await expect(page.locator(".package-card")).toHaveCount(
|
|
packages.length,
|
|
);
|
|
});
|
|
}
|
|
|
|
test("Angebotsübersicht passt auf ein schmales Display", async ({ page }) => {
|
|
await page.setViewportSize({ width: 320, height: 640 });
|
|
await page.goto("/angebotsuebersicht.html");
|
|
await expect(page.locator(".package-card")).toHaveCount(packages.length);
|
|
expect(
|
|
await page.evaluate(
|
|
() => document.documentElement.scrollWidth <= window.innerWidth,
|
|
),
|
|
).toBe(true);
|
|
});
|
|
|
|
test("Premium-Jahresvariante wird von der Detailseite bis zur Bestätigung übernommen", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/paket-details.html?id=ernaehrung-premium");
|
|
await page.locator("#variant-select").selectOption("ernaehrung-premium-52");
|
|
await page.locator("#proceed-to-booking-btn").click();
|
|
await expect(page.locator("dialog[open]")).toBeVisible();
|
|
await expect(page.locator(".booking__totals")).toContainText(
|
|
"52 Wochen Begleitung",
|
|
);
|
|
await expect(page.locator(".booking__totals")).toContainText("1.399,00");
|
|
await fill(page);
|
|
const responsePromise = page.waitForResponse(
|
|
(response) =>
|
|
response.url().endsWith("/api/bookings") &&
|
|
response.request().method() === "POST",
|
|
);
|
|
await page
|
|
.getByRole("button", { name: "Paket buchen", exact: true })
|
|
.click();
|
|
const response = await responsePromise;
|
|
expect(response.request().postDataJSON().variantId).toBe(
|
|
"ernaehrung-premium-52",
|
|
);
|
|
await expect(page).toHaveURL(/booking-confirmation\.html$/);
|
|
const booking = await page.evaluate(() =>
|
|
JSON.parse(sessionStorage.getItem("trihub.booking.confirmation")),
|
|
);
|
|
expect(booking.bookedPackage.grossPrice).toBe(1399);
|
|
expect(booking.bookedPackage.durationWeeks).toBe(52);
|
|
await expect(page.locator("#confirmation-receipt")).toContainText(
|
|
booking.bookingId,
|
|
);
|
|
await page.reload();
|
|
await expect(page.locator("#confirmation-totals")).toContainText(
|
|
"1.399,00",
|
|
);
|
|
await expect(page.locator("#confirmation-receipt")).toContainText(
|
|
booking.bookingId,
|
|
);
|
|
});
|
|
|
|
test("Paketfremde Variante zeigt kein Buchungsformular", async ({ page }) => {
|
|
await page.goto(
|
|
"/booking.html?id=ernaehrung-starter&variant=ernaehrung-premium-52",
|
|
);
|
|
await expect(
|
|
page.getByText("Diese Variante gehört nicht zum ausgewählten Paket."),
|
|
).toBeVisible();
|
|
await expect(page.locator("form")).toHaveCount(0);
|
|
});
|