feat(doku): Anleitung, PDF und Ende-zu-Ende-Abnahme
Prüfung / pruefung (push) Canceled after 0s
Prüfung / pruefung (pull_request) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
Prüfung / e2e (pull_request) Canceled after 0s

Neun Kapitel unter docs/anleitung/ fuer Technik und Verwaltung, mit 35
Bildern aus `pnpm docs:screenshots` und einem Fehlerbehebungskapitel aus
echten Meldungen. `pnpm docs:pdf` macht daraus 72 Seiten A4 — hell
gedruckt, mit Deckblatt, Inhaltsverzeichnis und Seitenzahlen, ohne neue
Abhaengigkeit.

Die Ablaeufe pruefen ihre Voraussetzungen, bevor der erste startet:
antwortet ein gebauter Stand, steht der Seed-Bestand, und liest der
Server dieselbe Datenbank wie der Testprozess. Jeder Ablauf legt an, was
er braucht, und raeumt es weg. Vier neue kommen dazu: Hauptablauf,
Onboarding mit Vergleich der Zwischenablage, Rollen und 390 px auf
achtzehn Seiten.

Drei Fehler kamen dabei heraus. Das Projekt „handy" pruefte 412 px statt
der verlangten 390. Ein angemeldeter Benutzer ohne Recht erfuhr nie,
warum er abgewiesen wurde — die Meldung stand auf einer Seite, die ihn
sofort weiterleitet; jetzt gibt es /kein-zugriff. Und der
Verzeichnis-Ablauf schrieb die Attributzuordnung der ersten vorhandenen
Quelle um, in einer echten Installation also der produktiven.

101 Ablaeufe in INTERN und 97 in DIENSTLEISTUNG, beide ohne Fehlschlag.

Closes #18

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-09-26 11:03:02 +02:00
co-authored by Claude Opus 5
parent 9bd198a531
commit 10e272654f
104 changed files with 4527 additions and 299 deletions
+103
View File
@@ -0,0 +1,103 @@
import { chromium } from "@playwright/test";
import "dotenv/config";
import { prisma } from "@/lib/db";
import { anmelden, SEED_GERAET, SEED_TAG, SEED_VORLAGE } from "./helfer";
/**
* Was vor dem ersten Ablauf stimmen muss (#018).
*
* Bisher standen die Voraussetzungen als Satz im Kopfkommentar jeder Datei
* („Voraussetzung: ein gebauter Stand laeuft und `pnpm beispiel:vorlage` hat
* …“). Ein Kommentar prueft nichts. Stimmte etwas davon nicht, fiel das erst
* mitten im Lauf auf, und die Meldung sprach von einem Zeitablauf oder von null
* Treffern — nie von der Ursache.
*
* Hier wird es geprueft, einmal, bevor irgendein Ablauf startet. Jede Meldung
* sagt, **was** fehlt und **was zu tun ist**.
*/
const APP_URL = process.env["E2E_APP_URL"] ?? "http://127.0.0.1:3000";
function fehlen(was: string, abhilfe: string): never {
throw new Error(`Ende-zu-Ende-Tests: ${was}\n → ${abhilfe}`);
}
export default async function vorbereiten(): Promise<void> {
if (!process.env["SEED_ADMIN_EMAIL"] || !process.env["SEED_ADMIN_PASSWORD"]) {
fehlen(
"SEED_ADMIN_EMAIL oder SEED_ADMIN_PASSWORD fehlt.",
"Beide stehen in der .env und müssen zu dem Administrator passen, den der Seed angelegt hat.",
);
}
// 1. Laeuft ueberhaupt etwas an dieser Adresse?
const antwort = await fetch(`${APP_URL}/api/gesundheit`, { signal: AbortSignal.timeout(5000) }).catch(
() => undefined,
);
if (!antwort?.ok) {
fehlen(
`Unter ${APP_URL} antwortet keine Anwendung.`,
"Erst bauen und starten: pnpm build && PORT=3300 APP_URL=http://127.0.0.1:3300 pnpm start — " +
"und die Adresse über E2E_APP_URL mitgeben. Gegen `pnpm dev` laufen die Abläufe nicht " +
"(siehe CLAUDE.md, „Oberfläche prüfen“).",
);
}
// 2. Steht der Seed-Bestand, mit dem die Ablaeufe arbeiten?
const vorlage = await prisma.vorlage.findFirst({
where: { name: SEED_VORLAGE },
select: { id: true, versionen: { where: { status: "VEROEFFENTLICHT" }, select: { id: true } } },
});
if (!vorlage || vorlage.versionen.length === 0) {
fehlen(
`Die Vorlage „${SEED_VORLAGE}“ fehlt oder ist nicht veröffentlicht.`,
"pnpm db:seed laufen lassen.",
);
}
const geraet = await prisma.geraet.findUnique({ where: { tagCode: SEED_TAG }, select: { id: true } });
if (!geraet) {
fehlen(`Das Beispielgerät ${SEED_GERAET} (${SEED_TAG}) fehlt.`, "pnpm db:seed laufen lassen.");
}
/*
* 2b. In welchem Betriebsmodus laeuft die Installation?
*
* Ein Ablauf soll das nicht selbst aus der Datenbank holen muessen — und zwei
* Quellen fuer dieselbe Auskunft liefen frueher oder spaeter auseinander. Der
* Modus steht danach in `E2E_BETRIEBSMODUS`, und ein Ablauf, der nur in einem
* der beiden Sinn ergibt, ueberspringt sich selbst mit einem Grund.
*/
const einstellung = await prisma.einstellung.findUnique({
where: { id: "global" },
select: { modus: true },
});
process.env["E2E_BETRIEBSMODUS"] = einstellung?.modus ?? "INTERN";
/*
* 3. Liest der Server **dieselbe** Datenbank?
*
* Der Testprozess schreibt über DATABASE_URL aus der .env, der Server bekommt
* seine eigene mit. Zeigen beide woandershin, legt ein Ablauf seine Daten in
* die eine und sucht sie in der anderen. Geprüft wird an einer Vorlage, die
* hier entsteht und gleich wieder verschwindet.
*/
const probe = `ZZ Datenbankprüfung ${Date.now()}`;
const angelegt = await prisma.vorlage.create({ data: { name: probe, kategorie: "ZZ Test" } });
const browser = await chromium.launch();
try {
const tab = await browser.newPage({ baseURL: APP_URL });
await anmelden(tab);
await tab.goto(`/vorlagen?suche=${encodeURIComponent(probe)}`);
if ((await tab.getByRole("link", { name: probe }).count()) === 0) {
fehlen(
"Der Server unter Test liest eine andere Datenbank als dieser Lauf.",
"DATABASE_URL des Testprozesses (aus der .env) und die des Servers müssen auf dieselbe " +
"Datenbank zeigen — sonst schreibt der Ablauf in die eine und sucht in der anderen.",
);
}
} finally {
await browser.close();
await prisma.vorlage.delete({ where: { id: angelegt.id } });
await prisma.$disconnect();
}
}