"""Browser-Lauf zu Teil 3b mit Playwright (Windows-Python), gegen ein laufendes Backend. Koordinator-Pruefskript, nicht fuers Repo. Aufruf: python -X utf8 browserlauf.py Prueft je Breite: kein waagerechter Scroll, keine Konsolenfehler, Ansicht "Laeufe" zeigt Zeilen, Ansicht "Markt" zeichnet (Canvas nicht leer). Schreibt Screenshots und eine ergebnis.json. Was es nicht sieht: ob die Zahlen stimmen (dafuer pruef_teil3b.py) und wie es aussieht (dafuer die Screenshots, die der Koordinator selbst ansieht). """ import json import sys from pathlib import Path from playwright.sync_api import TimeoutError as PlaywrightTimeout from playwright.sync_api import sync_playwright BREITEN = [375, 768, 1024, 1440] # Je ein Minuten- und ein Tagesdatensatz; die Namen stehen so im Katalog (gemessen 2026-09-17). MARKT_DATENSAETZE = [ ("minuten", "AAPL.XNAS-1-MINUTE-LAST-EXTERNAL"), ("tage", "MSFT.XNAS-1-DAY-LAST-EXTERNAL"), ] # aria-label der Knoepfe aus Laeufe.tsx: `Lauf ${quelle}/${datei}`. LAEUFE_EINZEL = [ ("tage", "Lauf signal/tage-teil9-20260917-030800.jsonl"), ("kampagne", "Lauf laeufe/aapl-minute-ppo-v20260914-132555.jsonl"), ] CANVAS_PROBE = """ () => { const flaechen = Array.from(document.querySelectorAll('canvas')); let groesste = null; for (const c of flaechen) { if (!groesste || c.width * c.height > groesste.width * groesste.height) groesste = c; } if (!groesste || groesste.width === 0 || groesste.height === 0) return {anzahl: flaechen.length, farben: 0}; const kontext = groesste.getContext('2d'); const daten = kontext.getImageData(0, 0, groesste.width, groesste.height).data; const farben = new Set(); const schritt = Math.max(4, Math.floor(daten.length / 4 / 20000) * 4); for (let i = 0; i < daten.length; i += schritt) { farben.add(`${daten[i]},${daten[i+1]},${daten[i+2]},${daten[i+3]}`); } return {anzahl: flaechen.length, breite: groesste.width, hoehe: groesste.height, farben: farben.size}; } """ SCROLL_PROBE = "() => ({scroll: document.documentElement.scrollWidth, sicht: document.documentElement.clientWidth})" def main() -> int: basis = sys.argv[1].rstrip("/") ziel = Path(sys.argv[2]) ziel.mkdir(parents=True, exist_ok=True) ergebnis: dict = {"basis": basis, "breiten": {}, "befunde": []} with sync_playwright() as spiel: browser = spiel.chromium.launch() for breite in BREITEN: seite = browser.new_page(viewport={"width": breite, "height": 900}) meldungen: list[str] = [] seite.on("console", lambda m, liste=meldungen: liste.append(f"{m.type}: {m.text}") if m.type in ("error", "warning") else None) seite.on("pageerror", lambda f, liste=meldungen: liste.append(f"pageerror: {f}")) seite.goto(basis + "/", wait_until="networkidle") eintrag: dict = {} def messe(name: str) -> None: masse = seite.evaluate(SCROLL_PROBE) eintrag[f"scroll_{name}"] = masse if masse["scroll"] > masse["sicht"] + 1: ergebnis["befunde"].append(f"{breite}px {name}: waagerechter Scroll {masse}") alarme = seite.get_by_role("alert").count() if alarme: ergebnis["befunde"].append(f"{breite}px {name}: {alarme} Alarm(e): {seite.get_by_role('alert').first.inner_text()[:160]}") seite.screenshot(path=str(ziel / f"{name}-{breite}.png"), full_page=True) for ansicht in ("Läufe", "Markt"): seite.get_by_role("button", name=ansicht, exact=True).click() seite.wait_for_timeout(1500) if ansicht == "Markt": # Die Ansicht laedt nach Plan nichts von selbst: erst die Auswahl holt Kerzen. eintrag["canvas"] = {} for kurz, bar_type in MARKT_DATENSAETZE: try: seite.get_by_label("Datensatz").select_option(bar_type) seite.wait_for_selector("canvas", timeout=20000) seite.wait_for_timeout(2000) except PlaywrightTimeout: pass probe = seite.evaluate(CANVAS_PROBE) eintrag["canvas"][kurz] = probe if probe["farben"] < 5: ergebnis["befunde"].append(f"{breite}px Markt/{kurz}: kein gezeichnetes Canvas ({probe})") messe(f"markt-{kurz}") else: eintrag["laeufe_zeilen"] = seite.locator("table tbody tr").count() if eintrag["laeufe_zeilen"] == 0: ergebnis["befunde"].append(f"{breite}px Laeufe: keine Tabellenzeile") messe("laeufe") for kurz, marke in LAEUFE_EINZEL: try: seite.get_by_role("button", name=marke, exact=True).click(timeout=8000) seite.wait_for_load_state("networkidle", timeout=20000) seite.wait_for_timeout(1200) except PlaywrightTimeout: ergebnis["befunde"].append(f"{breite}px Laeufe/{kurz}: Knopf '{marke}' nicht bedienbar") continue eintrag[f"einzel_{kurz}_tabellen"] = seite.locator("table").count() if eintrag[f"einzel_{kurz}_tabellen"] < 2: ergebnis["befunde"].append(f"{breite}px Laeufe/{kurz}: die Einzelansicht zeigt keine eigene Tabelle") messe(f"laeufe-{kurz}") eintrag["konsole"] = meldungen if any(m.startswith(("error", "pageerror")) for m in meldungen): ergebnis["befunde"].append(f"{breite}px: Konsolenfehler {meldungen[:3]}") ergebnis["breiten"][str(breite)] = eintrag seite.close() browser.close() (ziel / "ergebnis.json").write_text(json.dumps(ergebnis, indent=2, ensure_ascii=False), encoding="utf-8") print(json.dumps(ergebnis, indent=2, ensure_ascii=False)) return 2 if ergebnis["befunde"] else 0 if __name__ == "__main__": sys.exit(main())