From 0392a28a3201b1d87ffa019597c2bec848e09980 Mon Sep 17 00:00:00 2001 From: Wickedness Date: Wed, 29 Jul 2026 13:35:57 +0900 Subject: [PATCH] test: follow live title bounds in performance flow --- scripts/measure-performance.mjs | 83 +++++++++++++++++++++++++++++---- 1 file changed, 73 insertions(+), 10 deletions(-) diff --git a/scripts/measure-performance.mjs b/scripts/measure-performance.mjs index ac0f723..5e66eac 100644 --- a/scripts/measure-performance.mjs +++ b/scripts/measure-performance.mjs @@ -12,7 +12,6 @@ const chromiumLaunchOptions = Object.freeze({ headless: true, args: ['--use-angle=swiftshader', '--enable-unsafe-swiftshader'] }); -const legacyUiScale = 1.5; const budgets = { titleReadyMs: readBudget('PERF_BUDGET_TITLE_READY_MS', 30000), storyReadyFromNewGameMs: readBudget('PERF_BUDGET_STORY_READY_MS', 30000), @@ -28,8 +27,61 @@ const budgets = { largestJsKB: readBudget('PERF_BUDGET_LARGEST_JS_KB', 2500) }; -async function clickLegacyUi(page, x, y, options) { - await page.mouse.click(x * legacyUiScale, y * legacyUiScale, options); +async function clickTitleNewGame(page) { + const clickPoint = await page.evaluate(() => { + const scene = window.__HEROS_GAME__?.scene.getScene('TitleScene'); + const state = scene?.getDebugState?.(); + const item = state?.focus?.scope === 'main' + ? state.focus.items.find((candidate) => candidate.id === 'new-game' && candidate.enabled === true) + : undefined; + const canvas = document.querySelector('canvas'); + const canvasBounds = canvas?.getBoundingClientRect(); + if (!scene || !item?.bounds || !canvas || !canvasBounds) { + return null; + } + return { + x: canvasBounds.left + (item.bounds.x + item.bounds.width / 2) * canvasBounds.width / scene.scale.width, + y: canvasBounds.top + (item.bounds.y + item.bounds.height / 2) * canvasBounds.height / scene.scale.height, + itemBounds: item.bounds, + canvasBounds: { + x: canvasBounds.x, + y: canvasBounds.y, + width: canvasBounds.width, + height: canvasBounds.height + } + }; + }); + assert( + clickPoint && + Number.isFinite(clickPoint.x) && + Number.isFinite(clickPoint.y) && + clickPoint.itemBounds.width > 0 && + clickPoint.itemBounds.height > 0 && + clickPoint.x >= clickPoint.canvasBounds.x && + clickPoint.x <= clickPoint.canvasBounds.x + clickPoint.canvasBounds.width && + clickPoint.y >= clickPoint.canvasBounds.y && + clickPoint.y <= clickPoint.canvasBounds.y + clickPoint.canvasBounds.height, + `Expected an enabled title new-game control: ${JSON.stringify(clickPoint)}` + ); + const clickStartedAt = Date.now(); + await page.mouse.click(clickPoint.x, clickPoint.y); + try { + await page.waitForFunction(() => { + const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; + const title = window.__HEROS_DEBUG__?.title?.(); + return title?.navigating === true || activeScenes.includes('StoryScene'); + }, undefined, { timeout: 10000 }); + } catch (error) { + const diagnostic = await page.evaluate(() => ({ + activeScenes: window.__HEROS_DEBUG__?.activeScenes() ?? [], + title: window.__HEROS_DEBUG__?.title?.() ?? null + })); + throw new Error( + `The live title new-game control did not begin navigation: ${JSON.stringify({ clickPoint, diagnostic })}`, + { cause: error } + ); + } + return clickStartedAt; } async function clickBattleDeploymentStart(page) { @@ -92,8 +144,7 @@ try { const titleMs = Date.now() - titleStartedAt; const titleEntries = await resourceEntries(page); - const storyStartedAt = Date.now(); - await clickLegacyUi(page, 962, 240); + const storyStartedAt = await clickTitleNewGame(page); await waitForStoryReady(page); await page.waitForLoadState('networkidle', { timeout: 120000 }); const storyMs = Date.now() - storyStartedAt; @@ -313,11 +364,23 @@ async function waitForTitle(page) { } async function waitForStoryReady(page) { - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - const story = window.__HEROS_DEBUG__?.scene('StoryScene')?.getDebugState?.(); - return activeScenes.includes('StoryScene') && story?.ready === true; - }, undefined, { timeout: 120000 }); + try { + await page.waitForFunction(() => { + const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; + const story = window.__HEROS_DEBUG__?.scene('StoryScene')?.getDebugState?.(); + return activeScenes.includes('StoryScene') && story?.ready === true; + }, undefined, { timeout: 120000 }); + } catch (error) { + const diagnostic = await page.evaluate(() => ({ + activeScenes: window.__HEROS_DEBUG__?.activeScenes() ?? [], + title: window.__HEROS_DEBUG__?.title?.() ?? null, + story: window.__HEROS_DEBUG__?.scene('StoryScene')?.getDebugState?.() ?? null + })); + throw new Error( + `StoryScene did not become ready during performance measurement: ${JSON.stringify(diagnostic)}`, + { cause: error } + ); + } } async function advanceStoryUntilBattle(page, battleId) {