Phase 5: das sicherheitsrelevanteste Modul des Projekts. Ueber jeder Ansicht steht dauerhaft, dass die analoge Atemschutzueberwachung fuehrend bleibt — abschaltbar nur zusammen mit der Fundstelle einer Freigabe des Kommandanten. Es gibt keinen Timer. In der Datenbank stehen Zeitstempel; wie lange ein Trupp unter Atemschutz ist, rechnet jede Ansicht bei jedem Bild neu aus. Damit ist die Anforderung "uebersteht Neuladen und Verbindungsabbruch" keine Funktion, die gelingen muss, sondern eine Eigenschaft, die sich nicht verlieren laesst. Server und Browser rechnen mit denselben Funktionen. Der Rueckzugsdruck folgt dem doppelten Anmarschverbrauch: was der Weg hinein gekostet hat, kostet der Weg zurueck noch einmal. Bis der Trupp das Einsatzziel meldet, greift die Zwei-Drittel-Regel — sie liegt hoeher und ist damit die vorsichtigere Annahme. Das Protokoll ist doppelt gesichert: entzogene Rechte und ein Trigger auf druckmessung und atemschutz_ereignis. Ein Fehler wird durch einen neuen Eintrag richtiggestellt, nicht durch Aendern. Verteilt wird ueber Server-Sent Events statt Socket.IO — es geht nur in eine Richtung, und so bleibt das Deployment bei "next start". Behoben: ein abgerissener Ereignisstrom meldete sich nicht. Die Anzeige haette weiter "Verbunden" behauptet, waehrend der Draht tot war. Jetzt gilt Stille von acht Sekunden als Abriss. Dazu zwei Altlasten: der Navigationseintrag "Protokoll" fuehrte ins Leere, und die Startseite stand noch auf Phase 1. 68 neue Tests (51 Unit, 17 gegen eine echte Datenbank, 8 E2E je Browser). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
79 lines
3.1 KiB
TypeScript
79 lines
3.1 KiB
TypeScript
import { defineConfig, devices } from "@playwright/test";
|
|
|
|
/**
|
|
* End-to-End-Tests.
|
|
*
|
|
* Voraussetzung: der Stack läuft (docker compose up -d) und die Datenbank ist
|
|
* migriert und geseedet. Der Entwicklungsserver wird bei Bedarf selbst
|
|
* gestartet — ein bereits laufender wird wiederverwendet.
|
|
*/
|
|
const BASIS_URL = process.env.E2E_BASIS_URL ?? "http://localhost:3100";
|
|
|
|
export default defineConfig({
|
|
testDir: "./tests/e2e",
|
|
fullyParallel: true,
|
|
forbidOnly: Boolean(process.env.CI),
|
|
/**
|
|
* Ein Wiederholungsversuch auch lokal.
|
|
*
|
|
* Der Grund ist derselbe wie bei den großzügigen Zeitlimits unten: "next
|
|
* dev" übersetzt jede Route beim ersten Aufruf, und mit vier gleichzeitigen
|
|
* Arbeitern kommt dazu die Anmeldung mit bcrypt (12 Runden, wie in
|
|
* Produktion). Unter dieser Last läuft gelegentlich ein Test ins Zeitlimit,
|
|
* der einzeln zuverlässig grün ist — verstreut über alle Dateien, nicht an
|
|
* einer Stelle. Ein echter Fehler fällt zweimal durch und bleibt sichtbar.
|
|
*/
|
|
retries: process.env.CI ? 2 : 1,
|
|
/**
|
|
* Bewusst wenige Worker: "next dev" übersetzt jede Route erst beim ersten
|
|
* Aufruf. Zu viele gleichzeitige Erstaufrufe lassen den Server so lange
|
|
* kompilieren, dass die Navigation ins Zeitlimit läuft — und das sieht dann
|
|
* nach einem Fehler in der Anwendung aus, ist aber keiner.
|
|
*
|
|
* Ab Phase 5 von vier auf zwei: mit über 130 Tests, bcrypt bei jeder
|
|
* Anmeldung und einer Datenbank daneben fielen bei vier Arbeitern in jedem
|
|
* Gesamtlauf zwei bis fünf Tests ins Zeitlimit — jedes Mal andere, jeder
|
|
* einzeln grün. Ein Ergebnis, das man nicht glauben kann, ist wertlos; der
|
|
* Lauf dauert dafür etwa ein Drittel länger.
|
|
*/
|
|
workers: process.env.CI ? 1 : 2,
|
|
reporter: process.env.CI ? [["list"], ["html", { open: "never" }]] : [["list"]],
|
|
/** Großzügig, aus demselben Grund. Gegen einen Produktionsbuild reicht ein Bruchteil. */
|
|
timeout: 90_000,
|
|
/**
|
|
* Ab Phase 3 großzügiger: mit fünfzig Routen dauert die erste Übersetzung
|
|
* einer bisher unbesuchten Seite unter Last auch mal zwanzig Sekunden. Das
|
|
* sind Wartezeiten des Entwicklungsservers, keine der Anwendung — vorher
|
|
* schlugen dadurch gelegentlich Tests fehl, die einzeln laufen und grün sind.
|
|
*/
|
|
expect: { timeout: 25_000 },
|
|
|
|
use: {
|
|
baseURL: BASIS_URL,
|
|
trace: "on-first-retry",
|
|
screenshot: "only-on-failure",
|
|
video: "retain-on-failure",
|
|
// Die Oberfläche ist durchgängig deutsch — auch der Browser soll es sein.
|
|
locale: "de-DE",
|
|
timezoneId: "Europe/Berlin",
|
|
},
|
|
|
|
projects: [
|
|
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
|
|
// Am Terminal im Gerätehaus steht ein Touchgerät — deshalb mitgetestet.
|
|
// Bewusst mit Chromium statt WebKit: das Terminal läuft auf einem
|
|
// Android-Tablet im Kiosk-Browser, nicht auf einem iPad.
|
|
{
|
|
name: "tablet",
|
|
use: { ...devices["Galaxy Tab S4 landscape"], browserName: "chromium" },
|
|
},
|
|
],
|
|
|
|
webServer: {
|
|
command: "npm run dev",
|
|
url: BASIS_URL,
|
|
reuseExistingServer: !process.env.CI,
|
|
timeout: 180_000,
|
|
},
|
|
});
|