Files
FireAdministration/tests/e2e/anmeldung.spec.ts
T
Florian WildeandClaude Opus 5 0b8874da26 Phase 7: Benachrichtigungen und Termine
Terminkalender mit Serienterminen, Selbstanmeldung, Warteliste und
Anwesenheitsliste. Vier Zustellwege hinter Schnittstellen: echtes SMTP,
ntfy (selbst gehostet), Telegram und Web-Push. Kalenderabonnement als
ICS-Feed, digitaler Dienstausweis mit signiertem QR und Pruefseite.

Niemand wird ungefragt benachrichtigt: ein neuer Zustellweg hat eine leere
Ereignisliste, und der Seed legt bewusst keine an. Was das Netz der Wehr
verlaesst, bekommt nur die Kurzfassung ohne Namen und Details.

Kein CalDAV (Lesen genuegt, ICS kann jeder Kalender) und kein WhatsApp
(Verarbeitung ueber Meta, Kosten je Konversation, Telefonbuchabgleich) --
beides im README begruendet.

56 neue Unit-Tests, 17 neue E2E-Tests, Version 0.8.0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 08:39:38 +02:00

105 lines
4.3 KiB
TypeScript

import { expect, test } from "@playwright/test";
/**
* Beispiel-E2E-Test für Phase 0.
*
* Geprüft wird der Weg, ohne den nichts anderes funktioniert: jemand ruft die
* Anwendung auf, wird zur Anmeldung geschickt, meldet sich an und landet auf
* der Übersicht seiner Wehr.
*
* Voraussetzung: Stack läuft, Datenbank ist migriert und geseedet.
* docker compose up -d && npm run db:migrate && npm run db:seed
*/
const ADMIN_EMAIL = process.env.SEED_ADMIN_EMAIL ?? "admin@beispielwehr.de";
const ADMIN_PASSWORT = process.env.SEED_ADMIN_PASSWORT ?? "Feuerwehr2026";
test.describe("Anmeldung", () => {
test("schickt nicht angemeldete Aufrufe zur Anmeldeseite", async ({ page }) => {
await page.goto("/");
await expect(page).toHaveURL(/\/anmelden/);
await expect(page.getByRole("heading", { name: "FLORIAN" })).toBeVisible();
});
test("weist eine falsche Kombination ab und nennt den Grund auf Deutsch", async ({ page }) => {
await page.goto("/anmelden");
await page.getByLabel("E-Mail-Adresse").fill(ADMIN_EMAIL);
await page.getByLabel("Passwort").fill("definitiv-das-falsche-passwort");
await page.getByRole("button", { name: "Anmelden" }).click();
// Nicht ueber die Rolle "alert" suchen: Next.js haengt einen eigenen
// Routen-Ansager mit derselben Rolle in die Seite.
const meldung = page.getByTestId("anmeldefehler");
await expect(meldung).toBeVisible();
await expect(meldung).toContainText("stimmt nicht");
// Und niemand ist hineingekommen.
await expect(page).toHaveURL(/\/anmelden/);
});
test("lässt den Administrator hinein und zeigt die Übersicht seiner Wehr", async ({ page }) => {
await page.goto("/anmelden");
await page.getByLabel("E-Mail-Adresse").fill(ADMIN_EMAIL);
await page.getByLabel("Passwort").fill(ADMIN_PASSWORT);
await page.getByRole("button", { name: "Anmelden" }).click();
await page.waitForURL("/");
// Die Wehr aus dem Seed.
await expect(
page.getByRole("heading", { name: "Freiwillige Feuerwehr Musterhausen" }),
).toBeVisible();
// Die Kennzahlen aus dem Seed: 15 Mitglieder, 3 Fahrzeuge, 40 Geräte.
await expect(page.getByText("Aktive Mitglieder")).toBeVisible();
await expect(page.getByText("Fahrzeuge", { exact: true }).first()).toBeVisible();
await expect(page.getByText("Geräte", { exact: true }).first()).toBeVisible();
// Der Sicherheitshinweis muss stehen, solange nichts freigegeben ist.
await expect(page.getByRole("note")).toContainText("analoge Überwachung");
});
test("zeigt die Navigation und markiert künftige Module als noch nicht verfügbar", async ({
page,
}) => {
await page.goto("/anmelden");
await page.getByLabel("E-Mail-Adresse").fill(ADMIN_EMAIL);
await page.getByLabel("Passwort").fill(ADMIN_PASSWORT);
await page.getByRole("button", { name: "Anmelden" }).click();
await page.waitForURL("/");
const navigation = page.getByRole("navigation", { name: "Hauptnavigation" });
await expect(navigation.getByRole("link", { name: "Übersicht" })).toBeVisible();
// Termine, Dienstausweis und Benachrichtigungen stehen seit Phase 7 zur
// Verfügung und sind Verweise.
await expect(navigation.getByRole("link", { name: "Termine" })).toBeVisible();
await expect(navigation.getByRole("link", { name: "Dienstausweis" })).toBeVisible();
await expect(navigation.getByRole("link", { name: "Benachrichtigungen" })).toBeVisible();
// Die Kasse kommt erst in Phase 9 — der Eintrag ist sichtbar, aber kein
// Verweis. Beim Abschluss einer Phase wandert diese Zusicherung eine Zeile
// weiter nach unten.
await expect(navigation.getByText("Kasse")).toBeVisible();
await expect(navigation.getByRole("link", { name: "Kasse" })).toHaveCount(0);
});
});
test.describe("Erreichbarkeit", () => {
test("meldet den Zustand der Randsysteme unter /api/health", async ({ request }) => {
const antwort = await request.get("/api/health");
expect(antwort.status()).toBe(200);
const daten = (await antwort.json()) as {
status: string;
dienste: { datenbank: boolean; speicher: boolean; warteschlange: boolean };
};
expect(daten.status).toBe("ok");
expect(daten.dienste.datenbank).toBe(true);
expect(daten.dienste.speicher).toBe(true);
expect(daten.dienste.warteschlange).toBe(true);
});
});