import { expect, test } from "@playwright/test"; import { ADMIN_EMAIL, ADMIN_PASSWORT, anmelden } from "./wehr"; /** * E2E-Tests für Phase 10 — Mandantenfähigkeit. * * Geprüft wird das, wovon die Trennung abhängt: * * * wer nur zu einer Wehr gehört, sieht keinen Wähler * * wer zu mehreren gehört, landet in seiner eigenen und kann wechseln * * nach dem Wechsel stehen die Daten der anderen Wehr da, nicht die alten * * als Kreisaufsicht steht der Hinweis, und es gibt nichts zu ändern * * die Kasse der betreuten Wehr bleibt zu * * Voraussetzung: `npm run db:test-reset`. Der Seed legt dabei die Testwehr, * eine Nachbarwehr und ein Konto an, das in der einen Mitglied und in der * anderen Aufsicht ist. */ test.describe.configure({ mode: "serial" }); /** Das Konto der Kreisebene — Mitglied hier, Aufsicht nebenan. */ const KBI_EMAIL = process.env.E2E_KBI_EMAIL ?? "kbi@ff-teststelle.de"; const KBI_PASSWORT = process.env.E2E_KBI_PASSWORT ?? "Feuerwehr2026"; /** * Öffnet die Seitenleiste, falls sie hinter dem Menüknopf steckt. * * Auf dem Tablet (Galaxy Tab S4 quer, 1138 px) liegt die Leiste unterhalb der * `md`-Grenze und ist eingeklappt. Der Wehrwähler sitzt darin — wer ihn dort * sucht, ohne vorher aufzuklappen, wartet vergeblich. */ async function seitenleisteOeffnen(page: import("@playwright/test").Page) { const knopf = page.getByRole("button", { name: "Navigation öffnen" }); if (await knopf.isVisible()) await knopf.click(); } /** Meldet ein beliebiges Konto an. */ async function anmeldenAls(page: import("@playwright/test").Page, email: string, passwort: string) { await page.goto("/anmelden"); await page.getByLabel("E-Mail-Adresse").fill(email); await page.getByLabel("Passwort").fill(passwort); await page.getByRole("button", { name: "Anmelden" }).click(); await expect(page, `Anmeldung als ${email} fehlgeschlagen.`).toHaveURL("/"); } test.describe("Ein Konto, eine Wehr", () => { test("zeigt keinen Wehrwähler, wenn es nichts zu wählen gibt", async ({ page }) => { await anmelden(page); // Der Name der Wehr steht da — aber nicht als Auswahl. Ein Auswahlfeld mit // genau einem Eintrag ist eine Frage ohne Antwortmöglichkeit. await expect(page.getByRole("button", { name: /Wehr wechseln/ })).toHaveCount(0); await expect(page.getByRole("listbox", { name: "Wehr wechseln" })).toHaveCount(0); }); test("sieht keinen Aufsichtshinweis", async ({ page }) => { await anmelden(page); await expect(page.getByText("Sie sehen diese Wehr als")).toHaveCount(0); }); }); test.describe("Ein Konto, zwei Wehren", () => { test("landet nach der Anmeldung in der eigenen Wehr, nicht in der betreuten", async ({ page, }) => { await anmeldenAls(page, KBI_EMAIL, KBI_PASSWORT); // Die eigene vor der betreuten: wer eine Wehr führt und eine zweite // beaufsichtigt, will in seiner landen. await expect(page.getByText("Sie sehen diese Wehr als")).toHaveCount(0); }); test("bietet den Wehrwähler an", async ({ page }) => { await anmeldenAls(page, KBI_EMAIL, KBI_PASSWORT); await seitenleisteOeffnen(page); await page.getByRole("button", { name: /Wehr wechseln/ }).click(); const liste = page.getByRole("listbox", { name: "Wehr wechseln" }); await expect(liste).toBeVisible(); await expect(liste.getByRole("option")).toHaveCount(2); // Beide Arten stehen mit Klartext da — nicht nur mit einem Symbol. await expect(liste.getByText("Mitglied", { exact: true })).toBeVisible(); await expect(liste.getByText("Aufsicht — nur lesen")).toBeVisible(); }); test("wechselt in die betreute Wehr und sagt, dass dort nichts geht", async ({ page }) => { await anmeldenAls(page, KBI_EMAIL, KBI_PASSWORT); await seitenleisteOeffnen(page); await page.getByRole("button", { name: /Wehr wechseln/ }).click(); await page .getByRole("listbox", { name: "Wehr wechseln" }) .getByRole("option") .filter({ hasText: "Aufsicht" }) .click(); // Der Hinweis steht dauerhaft, nicht als kurz aufblitzende Meldung. await expect(page.getByText("Sie sehen diese Wehr als")).toBeVisible(); await expect(page.getByRole("note").filter({ hasText: "Kreisaufsicht" })).toBeVisible(); }); test("zeigt nach dem Wechsel die Mitglieder der anderen Wehr", async ({ page }) => { await anmeldenAls(page, KBI_EMAIL, KBI_PASSWORT); await seitenleisteOeffnen(page); await page.getByRole("button", { name: /Wehr wechseln/ }).click(); await page .getByRole("listbox", { name: "Wehr wechseln" }) .getByRole("option") .filter({ hasText: "Aufsicht" }) .click(); await expect(page.getByText("Sie sehen diese Wehr als")).toBeVisible(); await page.goto("/mitglieder"); // Die drei aus der Nachbarwehr — und nicht die fünfzehn der eigenen. await expect(page.getByRole("cell", { name: /Sailer/ })).toBeVisible(); await expect(page.getByRole("cell", { name: /Ostermeier/ })).toBeVisible(); // Die Kreisaufsicht selbst steht in keiner Mitgliederliste: sie gehört // der Wehr nicht an und hat dort keine Akte. await expect(page.getByRole("cell", { name: /Huber/ })).toHaveCount(0); }); test("lässt die Kreisaufsicht in der betreuten Wehr nichts anlegen", async ({ page }) => { await anmeldenAls(page, KBI_EMAIL, KBI_PASSWORT); await seitenleisteOeffnen(page); await page.getByRole("button", { name: /Wehr wechseln/ }).click(); await page .getByRole("listbox", { name: "Wehr wechseln" }) .getByRole("option") .filter({ hasText: "Aufsicht" }) .click(); await expect(page.getByText("Sie sehen diese Wehr als")).toBeVisible(); await page.goto("/mitglieder"); // Kein Knopf, der ins Leere führt. Das Recht fehlt serverseitig ohnehin — // die Oberfläche soll es aber gar nicht erst anbieten. await expect(page.getByRole("button", { name: /Mitglied anlegen/ })).toHaveCount(0); }); test("hält die Kasse der betreuten Wehr geschlossen", async ({ page }) => { await anmeldenAls(page, KBI_EMAIL, KBI_PASSWORT); await seitenleisteOeffnen(page); await page.getByRole("button", { name: /Wehr wechseln/ }).click(); await page .getByRole("listbox", { name: "Wehr wechseln" }) .getByRole("option") .filter({ hasText: "Aufsicht" }) .click(); await expect(page.getByText("Sie sehen diese Wehr als")).toBeVisible(); // Sie gehört dem Verein oder der Kommune, nicht der Kreisaufsicht. await seitenleisteOeffnen(page); await expect(page.locator("aside").getByRole("link", { name: "Kasse" })).toHaveCount(0); await page.goto("/kasse"); await expect(page).not.toHaveURL(/\/kasse$/); }); test("kommt in die eigene Wehr zurück", async ({ page }) => { await anmeldenAls(page, KBI_EMAIL, KBI_PASSWORT); await seitenleisteOeffnen(page); await page.getByRole("button", { name: /Wehr wechseln/ }).click(); await page .getByRole("listbox", { name: "Wehr wechseln" }) .getByRole("option") .filter({ hasText: "Aufsicht" }) .click(); await expect(page.getByText("Sie sehen diese Wehr als")).toBeVisible(); await seitenleisteOeffnen(page); await page.getByRole("button", { name: /Wehr wechseln/ }).click(); await page .getByRole("listbox", { name: "Wehr wechseln" }) .getByRole("option") .filter({ hasText: "Mitglied" }) .click(); await expect(page.getByText("Sie sehen diese Wehr als")).toHaveCount(0); await seitenleisteOeffnen(page); await expect(page.locator("aside").getByRole("link", { name: "Kasse" })).toBeVisible(); }); }); test.describe("Der Administrator", () => { test("bleibt nach der Umstellung anmeldbar", async ({ page }) => { // Die Migration hat sein Konto aus der Personenakte gelöst. Wäre dabei // etwas verlorengegangen, käme er hier nicht mehr hinein — und niemand // sonst auch. await anmeldenAls(page, ADMIN_EMAIL, ADMIN_PASSWORT); await expect(page.getByRole("heading", { level: 1 })).toBeVisible(); }); }); test.describe("Wehren und Konten", () => { test("steht nur dem Verwalter offen", async ({ page }) => { // Der KBI führt seine Wehr, verwaltet aber nicht die Installation. await anmeldenAls(page, KBI_EMAIL, KBI_PASSWORT); await seitenleisteOeffnen(page); await expect( page.locator("aside").getByRole("link", { name: "Wehren und Konten" }), ).toHaveCount(0); // Und die Seite selbst schickt ihn weg, nicht nur das Menü. await page.goto("/wehren"); await expect(page).not.toHaveURL(/\/wehren$/); }); test("zeigt dem Verwalter die Wehren dieser Installation", async ({ page }) => { await anmeldenAls(page, ADMIN_EMAIL, ADMIN_PASSWORT); await seitenleisteOeffnen(page); await page.locator("aside").getByRole("link", { name: "Wehren und Konten" }).click(); await expect(page.getByRole("heading", { name: "Wehren und Konten", level: 1 })).toBeVisible(); // Die Testwehr und ihre Nachbarwehr aus dem Seed. Der Verwalter sieht // bewusst **alle** Wehren der Installation — auch die einer anderen // Kommune, wenn sie auf demselben Server liegt. Das ist der Unterschied // zwischen dem Betreiber und einer Wehr. await expect(page.getByRole("heading", { name: "FF Teststelle Nachbar" })).toBeVisible(); await expect(page.getByRole("heading", { name: "FF Teststelle", exact: true })).toBeVisible(); }); test("zeigt, welches Konto zu welchen Wehren gehört", async ({ page }) => { await anmeldenAls(page, ADMIN_EMAIL, ADMIN_PASSWORT); await page.goto("/wehren"); // Das Konto der Kreisebene steht mit beiden Zuordnungen da — und die // Aufsicht ist als solche gekennzeichnet, nicht als Mitgliedschaft. await expect(page.getByText(KBI_EMAIL)).toBeVisible(); await expect(page.getByText("Kreisaufsicht — nur lesen").first()).toBeVisible(); }); });