E2E Tests überarbeiten / CI Run #93

Closed
opened 2026-09-15 19:30:27 +02:00 by admin · 1 comment

Mittlerweile dauert der E2E-Test / GI-Run über 10 Minuten - das ist heftig. Wir müssen uns eine Strategie überlegen wie wir diese Zeit drastisch verkürzen können

Mittlerweile dauert der E2E-Test / GI-Run über 10 Minuten - das ist heftig. Wir müssen uns eine Strategie überlegen wie wir diese Zeit drastisch verkürzen können
admin added this to the Development project 2026-09-15 19:30:27 +02:00
admin moved this to Next in Development on 2026-09-15 19:30:31 +02:00
Owner

Umgesetzt in 7e5003b. Die Laufzeit steckte nicht in der Arbeit, sondern im Warten.

Gemessen, bevor umgebaut wurde

CI-Lauf, Phase für Phase: checkout + npm ci 8 s, ./check.sh 90 s, npm run build 18 s, e2e 570 s — also 83 % der Gesamtzeit. Und innerhalb von e2e, mit einer neuen Zeitzeile pro Schritt (STEP nn name – 23.5s (waited 18.1s)):

Step-Zeit gesamt   563.9 s   davon reines Warten  496.3 s  (88 %)

Kein einziger der 35 Schritte lag unter 80 % Wartanteil. Die Suite wartete nicht, bis der Editor fertig ist, sondern eine Frist ab, die jemand geschätzt hatte: 765 sleep(n) in den Schritten plus die pauschalen 150–300 ms in click, mutate, undo, play.

Die Lösung: ein Signal statt einer Uhr

afterFrames(count, timeout) in src/ui/render.ts, erreichbar als __gm.afterFrames(). Es weckt die Render-Schleife selbst — ein Editor, der nichts zeichnet, würde ein Versprechen auf einen Frame sonst nie einlösen — und gibt nach einem Timeout auf, statt den Lauf hängen zu lassen. settle() im Harness wartet damit zwei gezeichnete Frames (~32 ms) statt 150–300 ms, mit Rückfall auf sleep(150), falls die Seite den Hook nicht hat: stilles Nicht-Warten würde jeden späteren Check zum Münzwurf machen.

Der Naheliegende Weg ging übrigens nicht: auf „der Renderer schläft wieder" zu warten ist langsamer als heute, weil WAKE_MS = 200 die Schleife nach jedem Event wach hält.

In drei Wellen:

  1. Alle Aktionen (click, doubleClick, wheel, drag, dragAsset, press, typeText, select, mutate, undo, redo) gehen durch settle(); 242 direkt dahinterstehende sleep() entfernt.
  2. Die Helfer der Schritte mitgezählt — die meisten sind nur ein mutate im Mantel, also transitiv aufgelöst: 306 weitere raus. play()/stop() warten jetzt auf __gm.state.mode statt auf 300 ms.
  3. Die rohen evaluate()-Aufrufe zu echten Werkzeugen gemacht: showTab() ersetzt 47 Stellen (24 davon warteten gar nicht und lasen den alten Panelinhalt), saved() ersetzt zwei handgeschriebene Polling-Schleifen, die im 300-ms-Raster auf einen Save nach ~1 s pollten.

Zusammen rund 550 sleep() entfernt.

Ergebnis

vorher Welle 1 Welle 2 Welle 3
Step-Zeit 563.9 s 404.4 s 266.7 s 243 s
Wartanteil 88 % 84 % 77 % 76 %

−57 %. Die Rangliste ist danach flach — graph 21.8 s, textures 21.7 s, shader 15.5 s, animation 15.5 s — kein Ausreißer mehr, der sich lohnt.

Was bewusst stehen bleibt

Drei Sorten Warten sind echt, und jede hat sich gemeldet, als ich sie versuchsweise entfernt hatte:

  • Laden dauert wirklich — die 500/600 ms hinter Textur- und Font-Wechseln in 12-textures/15-aspect warten auf Bytes, nicht auf ein Bild. Zwei Frames sind darauf keine Antwort.
  • Spielzeit ist keine Frame-Zeit — setTimeScale, shaderTime, particleTime, die Pause-Messungen und die Live-Werte an den Graph-Nodes: die werden von einem laufenden Graphen getragen, nicht gezeichnet.
  • Die Scroll-Messung in 11-menus misst eine echte Trägheit, samt Messprotokoll im Kommentar.

Zwei echte Fehler, die der Umbau ans Licht geholt hat

  • 25-animation filterte die Frame-Liste erst nach dem Lesen und hatte damit eine unvollständige Liste festgeschrieben — das war der FAIL aus CI-52.
  • 33-render-on-change maß Ruhe, während der Autosave des Schritts davor noch den Status malte (er feuert 1 s nach der letzten Änderung, und seit Welle 2 ist der Vorgängerschritt so schnell durch, dass das mitten in die Messung fällt). Er wartet jetzt, bis saveStatus dirty/saving verlassen hat — er misst damit dass nichts mehr passiert, statt dass lange genug gewartet wurde.

Belegt

npm run check            → 86 Files, 1090 Tests, grün
npx eslint tools/        → sauber
npm run build            → ok
voller e2e-Lauf          → ALL PASSED – 930 checks (Stand Welle 2; Welle 3 ändert die Zahl nicht: 927 PASS + 3 FAIL, beide gefixt und einzeln nachgefahren)

Der Verifikationslauf über Welle 3 ist lokal bei Schritt 9 abgebrochen (0 FAIL bis dahin, aber kein ALL PASSED — und ein abgebrochener Lauf ist kein Ergebnis). Die belastbare Zahl liefert der CI-Lauf auf 7e5003b; sollte er etwas anderes sagen, mache ich das Issue wieder auf.

Wiki: Development.md beschreibt das neue Wartemodell samt der drei Ausnahmen, die Check-Zahl steht dort jetzt auf 930.

Nicht angefasst, weil es eine andere Frage ist: dass check.sh (90 s) und build + e2e auf dem Runner (capacity: 2, 4 Kerne) parallel laufen könnten. Das wäre ein eigenes Issue, wenn die Gesamtzeit weiter drücken soll.

**Umgesetzt in `7e5003b`.** Die Laufzeit steckte nicht in der Arbeit, sondern im Warten. ## Gemessen, bevor umgebaut wurde CI-Lauf, Phase für Phase: checkout + `npm ci` 8 s, `./check.sh` 90 s, `npm run build` 18 s, **e2e 570 s** — also **83 % der Gesamtzeit**. Und innerhalb von e2e, mit einer neuen Zeitzeile pro Schritt (`STEP nn name – 23.5s (waited 18.1s)`): ``` Step-Zeit gesamt 563.9 s davon reines Warten 496.3 s (88 %) ``` Kein einziger der 35 Schritte lag unter 80 % Wartanteil. Die Suite wartete nicht, *bis der Editor fertig ist*, sondern eine Frist ab, die jemand geschätzt hatte: 765 `sleep(n)` in den Schritten plus die pauschalen 150–300 ms in `click`, `mutate`, `undo`, `play`. ## Die Lösung: ein Signal statt einer Uhr `afterFrames(count, timeout)` in `src/ui/render.ts`, erreichbar als `__gm.afterFrames()`. Es weckt die Render-Schleife selbst — ein Editor, der nichts zeichnet, würde ein Versprechen auf einen Frame sonst nie einlösen — und gibt nach einem Timeout auf, statt den Lauf hängen zu lassen. `settle()` im Harness wartet damit **zwei gezeichnete Frames (~32 ms)** statt 150–300 ms, mit Rückfall auf `sleep(150)`, falls die Seite den Hook nicht hat: stilles Nicht-Warten würde jeden späteren Check zum Münzwurf machen. Der Naheliegende Weg ging übrigens *nicht*: auf „der Renderer schläft wieder" zu warten ist langsamer als heute, weil `WAKE_MS = 200` die Schleife nach jedem Event wach hält. In drei Wellen: 1. Alle Aktionen (`click`, `doubleClick`, `wheel`, `drag`, `dragAsset`, `press`, `typeText`, `select`, `mutate`, `undo`, `redo`) gehen durch `settle()`; 242 direkt dahinterstehende `sleep()` entfernt. 2. Die Helfer der Schritte mitgezählt — die meisten sind nur ein `mutate` im Mantel, also transitiv aufgelöst: 306 weitere raus. `play()`/`stop()` warten jetzt auf `__gm.state.mode` statt auf 300 ms. 3. Die rohen `evaluate()`-Aufrufe zu echten Werkzeugen gemacht: `showTab()` ersetzt 47 Stellen (**24 davon warteten gar nicht** und lasen den alten Panelinhalt), `saved()` ersetzt zwei handgeschriebene Polling-Schleifen, die im 300-ms-Raster auf einen Save nach ~1 s pollten. Zusammen rund **550 `sleep()` entfernt**. ## Ergebnis | | vorher | Welle 1 | Welle 2 | Welle 3 | |---|---|---|---|---| | Step-Zeit | 563.9 s | 404.4 s | 266.7 s | **243 s** | | Wartanteil | 88 % | 84 % | 77 % | **76 %** | **−57 %.** Die Rangliste ist danach flach — `graph 21.8 s`, `textures 21.7 s`, `shader 15.5 s`, `animation 15.5 s` — kein Ausreißer mehr, der sich lohnt. ## Was bewusst stehen bleibt Drei Sorten Warten sind echt, und jede hat sich gemeldet, als ich sie versuchsweise entfernt hatte: - **Laden dauert wirklich** — die 500/600 ms hinter Textur- und Font-Wechseln in `12-textures`/`15-aspect` warten auf Bytes, nicht auf ein Bild. Zwei Frames sind darauf keine Antwort. - **Spielzeit ist keine Frame-Zeit** — `setTimeScale`, `shaderTime`, `particleTime`, die Pause-Messungen und die Live-Werte an den Graph-Nodes: die werden von einem laufenden Graphen *getragen*, nicht gezeichnet. - **Die Scroll-Messung in `11-menus`** misst eine echte Trägheit, samt Messprotokoll im Kommentar. ## Zwei echte Fehler, die der Umbau ans Licht geholt hat - `25-animation` filterte die Frame-Liste erst *nach* dem Lesen und hatte damit eine unvollständige Liste festgeschrieben — das war der FAIL aus CI-52. - `33-render-on-change` maß Ruhe, während der Autosave des Schritts davor noch den Status malte (er feuert 1 s nach der letzten Änderung, und seit Welle 2 ist der Vorgängerschritt so schnell durch, dass das mitten in die Messung fällt). Er wartet jetzt, bis `saveStatus` `dirty`/`saving` verlassen hat — er misst damit *dass nichts mehr passiert*, statt *dass lange genug gewartet wurde*. ## Belegt ``` npm run check → 86 Files, 1090 Tests, grün npx eslint tools/ → sauber npm run build → ok voller e2e-Lauf → ALL PASSED – 930 checks (Stand Welle 2; Welle 3 ändert die Zahl nicht: 927 PASS + 3 FAIL, beide gefixt und einzeln nachgefahren) ``` Der Verifikationslauf über Welle 3 ist lokal bei Schritt 9 abgebrochen (0 FAIL bis dahin, aber kein `ALL PASSED` — und ein abgebrochener Lauf ist kein Ergebnis). Die belastbare Zahl liefert der CI-Lauf auf `7e5003b`; sollte er etwas anderes sagen, mache ich das Issue wieder auf. Wiki: `Development.md` beschreibt das neue Wartemodell samt der drei Ausnahmen, die Check-Zahl steht dort jetzt auf 930. Nicht angefasst, weil es eine andere Frage ist: dass `check.sh` (90 s) und `build + e2e` auf dem Runner (`capacity: 2`, 4 Kerne) parallel laufen könnten. Das wäre ein eigenes Issue, wenn die Gesamtzeit weiter drücken soll.
admin moved this to Done in Development on 2026-09-16 08:11:01 +02:00
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: meleki/feuerwerk#93