/* SAUBER BEENDEN — nach dem Browser, vor dem Schlussstrich. =================================================================== DAS PROBLEM pruef-call-kategorien meldete "ALLES IN ORDNUNG" und gab danach Rueckgabewert 127 zurueck, mit dieser Zeile: Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c, line 94 Das ist libuv auf Windows: Etwas ruft `uv_async_send` auf ein Handle, das gerade geschlossen wird. Der Ausloeser ist `process.exit()` unmittelbar nach `browser.close()`. Playwright meldet den Browser als geschlossen, sobald der Befehl abgesetzt ist -- sein Transportkanal wird aber noch abgebaut, und der laeuft ueber genau so ein Handle. =================================================================== WARUM DAS SCHLIMMER IST ALS EIN ROTER LAUF Der Befund war richtig, die Pruefung war gruen, und trotzdem stand am Ende 127 -- die Schale liest das als "Befehl nicht gefunden". In einem Sammellauf zaehlt dieser Lauf damit als Fehlschlag, obwohl nichts fehlschlug. Wer das zweimal sieht, gewoehnt sich an, den Rueckgabewert dieser Datei zu ignorieren. Und ab da uebersieht man auch den echten. Es war nicht "sporadisch", wie ich es mir notiert hatte -- es kam in drei von drei Laeufen. Auch meine eigenen Notizen altern. =================================================================== MEIN ERSTER VERSUCH WAR DIE ELEGANTERE LOESUNG -- UND DIE SCHLECHTERE Ich wollte keine feste Pause: 400 ms sind eine Rechnung auf DIESEM Rechner, und auf einem langsameren waere der Fehler still zurueckgekommen. Also habe ich auf das EREIGNIS gewartet ("disconnected") und danach zwoelf Runden der Ereignisschleife durchgelassen (`setImmediate`). Sauber begruendet, gut lesbar. Gemessen: ZWEI VON DREI LAEUFEN weiterhin 127. Die feste Pause, die ich fuer die schlechtere Loesung hielt, war zweimal gruen. Der Grund ist eine falsche Annahme: "disconnected" meldet, dass die VERBINDUNG weg ist, nicht dass der Kanal ABGEBAUT ist. Und `setImmediate` gibt der Schleife Durchlaeufe, aber keine ZEIT -- der Kindprozess von Playwright braucht echte Millisekunden, um zu enden, und in genau diesem Fenster faellt die Assertion. Man kann sich eine Erklaerung zurechtlegen, die stimmig klingt, und trotzdem daneben liegen. Deshalb steht die Messung hier und nicht die Herleitung. =================================================================== WAS JETZT WIRKLICH DRINSTEHT Beides: erst auf das Ereignis warten (das ist die richtige Ordnung), dann eine ZEITLICHE Reserve. Die Reserve ist keine Schaetzung, wie lange der Abbau dauert -- sie ist grosszuegig bemessen (600 ms gegenueber gemessenen 400) und ueber PRUEF_ABBAU_MS einstellbar, falls ein langsamerer Rechner mehr braucht. Sie kostet einmal pro Prueflauf ein halbes Sekundchen; das ist der Preis dafuer, dass der Rueckgabewert die Wahrheit sagt. =================================================================== BENUTZUNG import { sauberBeenden } from "./helfer-beenden.mjs"; ... await sauberBeenden(browser, fehler ? 1 : 0); ersetzt das Paar `await browser.close(); process.exit(code);`. */ export async function sauberBeenden(browser, code) { if (browser) { /* Das Ereignis ABONNIEREN, bevor geschlossen wird -- sonst kann es zwischen close() und once() durchrutschen und wir warten auf etwas, das schon vorbei ist. */ const getrennt = new Promise((fertig) => { let erledigt = false; const einmal = () => { if (!erledigt) { erledigt = true; fertig(); } }; try { browser.once("disconnected", einmal); } catch { einmal(); } /* DRITTER AUSGANG: Kommt das Ereignis gar nicht (abgestuerzter Browser, fremde Playwright-Fassung), darf das Beenden nicht haengen. Ein Prueflauf, der still stehenbleibt, ist schlimmer als einer, der scheitert -- von aussen sieht er aus wie "laeuft noch". */ setTimeout(einmal, 5000).unref?.(); }); try { await browser.close(); } catch { /* schon zu */ } await getrennt; } /* ECHTE ZEIT, nicht nur Runden der Ereignisschleife. Begruendung ausfuehrlich oben: `setImmediate` allein liess zwei von drei Laeufen weiterhin abstuerzen. */ const abbau = Number(process.env.PRUEF_ABBAU_MS ?? 600); await new Promise((r) => setTimeout(r, abbau)); process.exit(code); }