From 568a91dfcf521e4b24c661330f5d4ecc0a64e224 Mon Sep 17 00:00:00 2001 From: fengsilin Date: Thu, 18 Jun 2026 11:44:51 +0800 Subject: [PATCH] test: harden e2e helper error handling Co-Authored-By: Codex --- web/e2e/utils/process.js | 19 +++++++++++++++++-- web/e2e/utils/wait.js | 17 +++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/web/e2e/utils/process.js b/web/e2e/utils/process.js index 1366d91..9937ace 100644 --- a/web/e2e/utils/process.js +++ b/web/e2e/utils/process.js @@ -12,6 +12,10 @@ export function startProcess(command, args, options = {}) { child.stdout.on('data', (chunk) => output.push(chunk.toString())); child.stderr.on('data', (chunk) => output.push(chunk.toString())); + child.once('error', (error) => { + output.push(`\nprocess failed to start: ${error.message}\n`); + }); + child.once('exit', (code) => { if (code !== null && code !== 0) { output.push(`\nprocess exited with code ${code}\n`); @@ -25,6 +29,17 @@ function hasExited(child) { return child.exitCode !== null || child.signalCode !== null; } +function sendSignal(child, signal) { + if (!child || hasExited(child)) return false; + + try { + return child.kill(signal); + } catch (error) { + if (error?.code === 'ESRCH' || hasExited(child)) return false; + throw error; + } +} + function waitForExit(child, timeoutMs) { if (!child || hasExited(child)) return Promise.resolve(true); @@ -72,10 +87,10 @@ export async function stopProcess(child) { return; } - child.kill('SIGTERM'); + sendSignal(child, 'SIGTERM'); const exited = await waitForExit(child, 5000); if (exited) return; - child.kill('SIGKILL'); + sendSignal(child, 'SIGKILL'); await waitForExit(child, 5000); } diff --git a/web/e2e/utils/wait.js b/web/e2e/utils/wait.js index dae9661..b7100ad 100644 --- a/web/e2e/utils/wait.js +++ b/web/e2e/utils/wait.js @@ -1,18 +1,31 @@ export async function waitForHttp(url, options = {}) { const timeoutMs = options.timeoutMs || 60000; const intervalMs = options.intervalMs || 500; + const attemptTimeoutMs = options.attemptTimeoutMs || 5000; const start = Date.now(); let lastError; while (Date.now() - start < timeoutMs) { + const remainingMs = timeoutMs - (Date.now() - start); + if (remainingMs <= 0) break; + + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), Math.min(attemptTimeoutMs, remainingMs)); + timeout.unref?.(); + try { - const response = await fetch(url, { cache: 'no-store' }); + const response = await fetch(url, { cache: 'no-store', signal: controller.signal }); if (response.ok) return response; lastError = new Error(`HTTP ${response.status}`); } catch (error) { lastError = error; + } finally { + clearTimeout(timeout); } - await new Promise((resolve) => setTimeout(resolve, intervalMs)); + + const remainingAfterAttemptMs = timeoutMs - (Date.now() - start); + if (remainingAfterAttemptMs <= 0) break; + await new Promise((resolve) => setTimeout(resolve, Math.min(intervalMs, remainingAfterAttemptMs))); } throw new Error(`Timed out waiting for ${url}: ${lastError?.message || 'no response'}`);