diff --git a/scripts/qa-representative-battles.mjs b/scripts/qa-representative-battles.mjs index 8d3bb09..8a09783 100644 --- a/scripts/qa-representative-battles.mjs +++ b/scripts/qa-representative-battles.mjs @@ -1273,7 +1273,7 @@ try { } async function setupBattle(page, battle) { - await page.goto(targetUrl, { waitUntil: 'domcontentloaded' }); + await gotoTargetPage(page); await page.waitForFunction(() => window.__HEROS_GAME__ !== undefined && window.__HEROS_DEBUG__ !== undefined, undefined, { timeout: 90000 }); await page.evaluate((selected) => { const now = new Date().toISOString(); @@ -1321,7 +1321,7 @@ async function setupCumulativeBattle(page, battle, firstBattle) { firstBattle && resumeCampaignSnapshot && campaignSnapshotPath && existsSync(campaignSnapshotPath) ? await readFile(campaignSnapshotPath, 'utf8') : undefined; - await page.goto(targetUrl, { waitUntil: 'domcontentloaded' }); + await gotoTargetPage(page); await page.waitForFunction(() => window.__HEROS_GAME__ !== undefined && window.__HEROS_DEBUG__ !== undefined, undefined, { timeout: 90000 }); const preparation = await page.evaluate( ({ firstBattle, initialSnapshot, labels, protectedUnitIds, selected, slotKey, storageKey }) => { @@ -1454,6 +1454,38 @@ async function setupCumulativeBattle(page, battle, firstBattle) { return preparation; } +async function gotoTargetPage(page) { + let lastError; + for (let attempt = 0; attempt < 3; attempt += 1) { + try { + await page.goto(targetUrl, { waitUntil: 'domcontentloaded' }); + return; + } catch (error) { + lastError = error; + if (!isServerConnectionError(error) || attempt === 2) { + throw error; + } + console.log(`QA server connection failed at ${targetUrl}; restarting local server and retrying (${attempt + 1}/2).`); + await restartLocalServer(); + } + } + throw lastError; +} + +async function restartLocalServer() { + if (serverProcess) { + serverProcess.kill(); + serverProcess = undefined; + await sleep(1000); + } + serverProcess = await ensureLocalServer(targetUrl); +} + +function isServerConnectionError(error) { + const message = String(error?.message ?? error); + return message.includes('ERR_CONNECTION_REFUSED') || message.includes('ECONNREFUSED') || message.includes('ERR_CONNECTION_RESET'); +} + async function normalizeRepresentativeBattleUnits(page, battle) { await page.evaluate( ({ no, selected, protectedUnitIds }) => {