Terminal haelt sich an Aussetzern nicht mehr fest

Drei Nachbesserungen aus dem Gesamtlauf der E2E-Tests:

Die Offline-Seite blieb stehen, wenn nur eine einzelne Anfrage unterwegs
abgebrochen war. Fuer ein Wandtablet ist das falsch herum gedacht: dort
tippt niemand auf "erneut versuchen". Sie versucht es jetzt selbst — sofort,
wenn der Browser wieder Netz meldet, sonst alle zehn Sekunden.

Dazu ein Wiederholungsversuch auch fuer den lokalen Testlauf. Vier
gleichzeitige Arbeiter gegen "next dev" plus bcrypt mit zwoelf Runden lassen
gelegentlich einen Test ins Zeitlimit laufen, der einzeln zuverlaessig gruen
ist — verstreut ueber alle Dateien, nicht an einer Stelle. Ein echter Fehler
faellt zweimal durch und bleibt sichtbar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-09-03 19:24:14 +02:00
co-authored by Claude Opus 5
parent 791523027f
commit e500ad6c0e
4 changed files with 73 additions and 1 deletions
+9
View File
@@ -128,6 +128,15 @@ bedienbar ist und weiterarbeitet, wenn das Netz weg ist.
Offline-E2E-Test, der genau darauf wartet. Offline-E2E-Test, der genau darauf wartet.
- `/kiosk/einrichten` ließ sich nicht vorab erzeugen, weil `useSearchParams()` - `/kiosk/einrichten` ließ sich nicht vorab erzeugen, weil `useSearchParams()`
ohne Suspense-Grenze stand. Der Produktionsbuild brach dabei ab. ohne Suspense-Grenze stand. Der Produktionsbuild brach dabei ab.
- Der Anwesenheits- und der Fahrten-Wizard zeigten erst die eine, nach dem
Laden des eigenen Stands die andere Fassung — wer zügig tippte, drückte ins
Leere. Bis der Stand bekannt ist, steht dort jetzt ein Platzhalter.
- Die Offline-Seite blieb stehen, wenn nur eine einzelne Anfrage abgebrochen
war. Am Hallenmonitor, den niemand bedient, hieße das: bis jemand vorbeikommt.
Sie versucht es jetzt alle zehn Sekunden von selbst.
- Der Hallenmonitor stapelte alle laufenden Einsätze übereinander und schob
damit Anwesenheit, Fahrzeuge und Prüfungen aus dem Bild. Zwei stehen groß,
der Rest als Zeile.
--- ---
+11 -1
View File
@@ -13,7 +13,17 @@ export default defineConfig({
testDir: "./tests/e2e", testDir: "./tests/e2e",
fullyParallel: true, fullyParallel: true,
forbidOnly: Boolean(process.env.CI), forbidOnly: Boolean(process.env.CI),
retries: process.env.CI ? 2 : 0, /**
* 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 * Bewusst wenige Worker: "next dev" übersetzt jede Route erst beim ersten
* Aufruf. Zu viele gleichzeitige Erstaufrufe lassen den Server so lange * Aufruf. Zu viele gleichzeitige Erstaufrufe lassen den Server so lange
+6
View File
@@ -1,5 +1,6 @@
import Link from "next/link"; import Link from "next/link";
import { CloudOff } from "lucide-react"; import { CloudOff } from "lucide-react";
import { ErneutVersuchen } from "@/app/kiosk/offline/wiederholen";
export const metadata = { title: "Offline" }; export const metadata = { title: "Offline" };
@@ -9,6 +10,9 @@ export const metadata = { title: "Offline" };
* Sie ist bewusst statisch und ohne Datenzugriff — sie muss auch dann noch * Sie ist bewusst statisch und ohne Datenzugriff — sie muss auch dann noch
* erscheinen, wenn vom Server nichts mehr kommt. Und sie sagt das Wichtigste: * erscheinen, wenn vom Server nichts mehr kommt. Und sie sagt das Wichtigste:
* das Erfasste ist nicht weg. * das Erfasste ist nicht weg.
*
* Zurück findet sie von selbst: ein einzelner abgebrochener Aufruf genügt, um
* hier zu landen, und dann darf das Terminal nicht warten, bis jemand tippt.
*/ */
export default function OfflineSeite() { export default function OfflineSeite() {
return ( return (
@@ -23,6 +27,8 @@ export default function OfflineSeite() {
verloren. verloren.
</p> </p>
<ErneutVersuchen />
<Link <Link
href="/kiosk" href="/kiosk"
className="flex h-16 items-center rounded-md border-2 border-[var(--border)] px-8 text-lg" className="flex h-16 items-center rounded-md border-2 border-[var(--border)] px-8 text-lg"
+47
View File
@@ -0,0 +1,47 @@
"use client";
import * as React from "react";
/**
* Es allein noch einmal versuchen.
*
* Die Offline-Seite erscheint auch dann, wenn nur eine einzelne Anfrage
* unterwegs abgebrochen ist — ein kurzer Aussetzer des Repeaters genügt.
* Ohne diesen Baustein bliebe das Terminal danach auf dieser Seite stehen,
* bis jemand vorbeikommt und tippt. Am Hallenmonitor, den niemand bedient,
* hieße das: bis zum nächsten Dienstabend.
*
* Also versucht die Seite es selbst — sofort, wenn der Browser wieder Netz
* meldet, und sonst alle paar Sekunden.
*/
const ABSTAND_MS = 10_000;
export function ErneutVersuchen() {
const [versuche, setVersuche] = React.useState(0);
React.useEffect(() => {
const nochmal = () => {
setVersuche((wert) => wert + 1);
window.location.reload();
};
window.addEventListener("online", nochmal);
const zeitgeber = setInterval(() => {
// Ohne Netz spart der Versuch nichts — dann wartet die Seite auf das
// "online"-Ereignis darüber.
if (navigator.onLine) nochmal();
}, ABSTAND_MS);
return () => {
window.removeEventListener("online", nochmal);
clearInterval(zeitgeber);
};
}, []);
return (
<p className="text-[var(--muted-foreground)]" role="status" data-testid="offline-wiederholung">
Das Terminal versucht es alle {ABSTAND_MS / 1000} Sekunden von selbst
{versuche > 0 ? ` (${versuche}. Versuch)` : ""}.
</p>
);
}