test(e2e): skip killing an Electron that already exited

stopElectron now checks the recorded exit state before each signal and
tolerates a kill that races the exit: on Windows taskkill throws for a PID
that no longer exists. Startup cleanup can no longer replace the startup
error that explains the failure; a cleanup failure there is logged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-09-29 20:48:57 +02:00
1 parent c980ced90a
commit 4febe1d420
1 file changed
+19 -3
@@ -172,12 +172,25 @@ async function stopElectron(
child: ChildProcess,
exited: Promise<void>
): Promise<void> {
let lastKillError: unknown;
for (const signal of ['SIGTERM', 'SIGKILL'] as const) {
terminateElectronProcess(child, signal);
// Already gone (exited on its own, or during startup): nothing to
// kill. On Windows taskkill throws for a PID that no longer exists.
if (await waitForExit(child, exited, 0)) return;
try {
terminateElectronProcess(child, signal);
} catch (error) {
// The process may have exited between the check and the kill.
lastKillError = error;
}
if (await waitForExit(child, exited, EXIT_WAIT_MS)) return;
}
const killFailure =
lastKillError === undefined
? ''
: ` (last kill: ${String(lastKillError)})`;
throw new Error(
`Electron (pid ${child.pid}) did not exit after SIGTERM and SIGKILL`
`Electron (pid ${child.pid}) did not exit after SIGTERM and SIGKILL${killFailure}`
);
}
@@ -220,7 +233,10 @@ export async function launchUnautomatedElectronApp(
},
};
} catch (error) {
await stopElectron(child, exited);
// Cleanup must never replace the startup failure that explains the test.
await stopElectron(child, exited).catch((cleanupError: unknown) =>
console.warn('Unautomated Electron cleanup failed:', cleanupError)
);
throw error;
}
}