"""Wiederaufbau-Test im Browser: Backend weg, Backend wieder da -- ohne Neuladen der Seite. Koordinator-Pruefskript (Windows-Python mit Playwright), nicht fuers Repo. Frage: Zeigt die Kopfzeile "Backend getrennt", wenn das Backend stirbt, und von selbst wieder "Backend verbunden", wenn es zurueck ist? Herkunft: Projektvorgabe (Spec, Abschnitt Frontend). Statustexte aus dashboard/src/components/Kopfzeile.tsx (abgeschrieben am 2026-09-17). Was es nicht sieht: die Wartezeiten 1/2/5/10 s im Einzelnen (dafuer api.test.ts), andere Browser als Chromium. Regressionsnachweis: --ohne-neustart laesst das Backend unten; der Lauf muss dann mit Exit 2 am Schritt "wieder verbunden" enden. Aufruf: python -X utf8 wiederaufbau.py [--ohne-neustart] """ import subprocess import sys import time import urllib.request from pathlib import Path from playwright.sync_api import TimeoutError as PlaywrightTimeout from playwright.sync_api import sync_playwright PORT = 8146 BASIS = f"http://localhost:{PORT}" HIER = Path(__file__).parent def starte_backend() -> subprocess.Popen: skript = open(HIER / "start_probe.sh", "rb") return subprocess.Popen( ["wsl", "-d", "Debian", "--", "bash", "-s", str(PORT), "1.0"], stdin=skript, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) def beende_backend() -> None: subprocess.run(["wsl", "-d", "Debian", "--", "pkill", "-f", f"start_probe_backend.py {PORT}"], stdin=subprocess.DEVNULL, capture_output=True) def warte_auf_backend(sekunden: float) -> bool: ende = time.time() + sekunden while time.time() < ende: try: with urllib.request.urlopen(BASIS + "/api/uebersicht", timeout=1) as antwort: if antwort.status == 200: return True except OSError: time.sleep(0.5) return False def main() -> int: ohne_neustart = "--ohne-neustart" in sys.argv beende_backend() prozess = starte_backend() schritte: list[str] = [] try: if not warte_auf_backend(40): print("BEFUND: Probe-Backend kommt nicht hoch -- der Lauf misst nichts") return 2 with sync_playwright() as p: browser = p.chromium.launch() seite = browser.new_page(viewport={"width": 1280, "height": 800}) fehler: list[str] = [] seite.on("pageerror", lambda e: fehler.append(str(e))) seite.goto(BASIS + "/", wait_until="domcontentloaded") seite.evaluate("() => { window.__marke = 'nicht neu geladen'; }") def erwarte(text: str, sekunden: float, name: str) -> bool: start = time.time() try: seite.get_by_text(text, exact=True).first.wait_for(timeout=sekunden * 1000) except PlaywrightTimeout: print(f"BEFUND: Schritt '{name}': '{text}' erscheint nicht binnen {sekunden} s") return False schritte.append(f"{name}: '{text}' nach {time.time() - start:.1f} s") return True if not erwarte("Backend verbunden", 20, "erste Verbindung"): return 2 beende_backend() prozess.wait(timeout=20) if not erwarte("Backend getrennt", 20, "nach dem Ende des Backends"): return 2 if not ohne_neustart: prozess = starte_backend() if not warte_auf_backend(40): print("BEFUND: Probe-Backend kommt nach dem Neustart nicht hoch") return 2 if not erwarte("Backend verbunden", 30, "wieder verbunden"): return 2 marke = seite.evaluate("() => window.__marke") if marke != "nicht neu geladen": print(f"BEFUND: Die Seite wurde neu geladen (Marke {marke!r})") return 2 if fehler: print(f"BEFUND: Seitenfehler: {fehler[:3]}") return 2 browser.close() for schritt in schritte: print("ok " + schritt) print("Kein Befund: Wiederaufbau ohne Neuladen.") return 0 finally: for schritt in schritte: pass beende_backend() try: prozess.wait(timeout=10) except subprocess.TimeoutExpired: prozess.kill() if __name__ == "__main__": sys.exit(main())