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
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:
Alle Aktionen (click, doubleClick, wheel, drag, dragAsset, press, typeText, select, mutate, undo, redo) gehen durch settle(); 242 direkt dahinterstehende sleep() entfernt.
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.
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 saveStatusdirty/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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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
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 ci8 s,./check.sh90 s,npm run build18 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)):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 inclick,mutate,undo,play.Die Lösung: ein Signal statt einer Uhr
afterFrames(count, timeout)insrc/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 aufsleep(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 = 200die Schleife nach jedem Event wach hält.In drei Wellen:
click,doubleClick,wheel,drag,dragAsset,press,typeText,select,mutate,undo,redo) gehen durchsettle(); 242 direkt dahinterstehendesleep()entfernt.mutateim Mantel, also transitiv aufgelöst: 306 weitere raus.play()/stop()warten jetzt auf__gm.state.modestatt auf 300 ms.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
−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:
12-textures/15-aspectwarten auf Bytes, nicht auf ein Bild. Zwei Frames sind darauf keine Antwort.setTimeScale,shaderTime,particleTime, die Pause-Messungen und die Live-Werte an den Graph-Nodes: die werden von einem laufenden Graphen getragen, nicht gezeichnet.11-menusmisst eine echte Trägheit, samt Messprotokoll im Kommentar.Zwei echte Fehler, die der Umbau ans Licht geholt hat
25-animationfilterte 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-changemaß 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, bissaveStatusdirty/savingverlassen hat — er misst damit dass nichts mehr passiert, statt dass lange genug gewartet wurde.Belegt
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 auf7e5003b; sollte er etwas anderes sagen, mache ich das Issue wieder auf.Wiki:
Development.mdbeschreibt 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) undbuild + e2eauf dem Runner (capacity: 2, 4 Kerne) parallel laufen könnten. Das wäre ein eigenes Issue, wenn die Gesamtzeit weiter drücken soll.