diff --git a/scripts/verify-interaction-ux.mjs b/scripts/verify-interaction-ux.mjs index 5e7039e..f756d62 100644 --- a/scripts/verify-interaction-ux.mjs +++ b/scripts/verify-interaction-ux.mjs @@ -483,15 +483,32 @@ async function verifyBattlePointerFlow(page) { assert(postMovement.waitBounds, 'Expected the wait command button after movement.'); await page.waitForTimeout(120); - await clickSceneBounds(page, 'BattleScene', postMovement.waitBounds); - await page.waitForFunction((unitId) => { - const battle = window.__HEROS_DEBUG__?.battle(); - return ( - battle?.turnPromptVisible === true && - battle?.turnPromptMode === 'turn-end' && - battle?.actedUnitIds?.includes(unitId) + const waitCommandClick = await clickBattleCommand( + page, + 'wait' + ); + try { + await page.waitForFunction((unitId) => { + const battle = window.__HEROS_DEBUG__?.battle(); + return ( + battle?.turnPromptVisible === true && + battle?.turnPromptMode === 'turn-end' && + battle?.actedUnitIds?.includes(unitId) + ); + }, lastAllySetup.unitId); + } catch (error) { + const diagnostic = + await battleCommandPointerDiagnostic( + page, + waitCommandClick + ); + throw new Error( + `The wait-command pointer click did not finish the final ally action: ${JSON.stringify( + diagnostic + )}`, + { cause: error } ); - }, lastAllySetup.unitId); + } const automaticTurnPrompt = await page.evaluate(() => window.__HEROS_DEBUG__?.battle()?.turnPrompt); assert( automaticTurnPrompt?.title === '모든 아군의 행동이 종료되었습니다.' && @@ -1923,6 +1940,146 @@ async function clickTurnPromptSecondary(page) { return { button, point }; } +async function clickBattleCommand(page, command) { + await page.evaluate( + () => + new Promise((resolve) => + requestAnimationFrame(() => + requestAnimationFrame(resolve) + ) + ) + ); + const button = await page.evaluate( + (requestedCommand) => { + const scene = + window.__HEROS_GAME__?.scene.getScene( + 'BattleScene' + ); + const candidate = scene?.commandButtons?.find( + (entry) => + entry.command === requestedCommand + ); + const bounds = + candidate?.background?.getBounds?.(); + return candidate && bounds + ? { + command: candidate.command, + available: candidate.available, + active: + candidate.background.active, + visible: + candidate.background.visible, + inputEnabled: + candidate.background.input?.enabled === + true, + bounds: { + x: bounds.x, + y: bounds.y, + width: bounds.width, + height: bounds.height + } + } + : null; + }, + command + ); + assert( + button?.command === command && + button.available === true && + button.active === true && + button.visible === true && + button.inputEnabled === true, + `Expected an active ${command} command button: ${JSON.stringify( + button + )}` + ); + const point = await clickSceneBounds( + page, + 'BattleScene', + button.bounds + ); + return { button, point }; +} + +async function battleCommandPointerDiagnostic( + page, + click +) { + return page.evaluate((requestedClick) => { + const scene = + window.__HEROS_GAME__?.scene.getScene( + 'BattleScene' + ); + const canvas = document.querySelector('canvas'); + const canvasBounds = + canvas?.getBoundingClientRect(); + const pointer = scene?.input?.activePointer; + const buttons = ( + scene?.commandButtons ?? [] + ).map((button) => { + const bounds = + button.background?.getBounds?.(); + return { + command: button.command, + available: button.available, + active: + button.background?.active ?? false, + visible: + button.background?.visible ?? false, + inputEnabled: + button.background?.input?.enabled === + true, + bounds: bounds + ? { + x: bounds.x, + y: bounds.y, + width: bounds.width, + height: bounds.height + } + : null + }; + }); + return { + click: requestedClick, + battle: + window.__HEROS_DEBUG__?.battle?.() ?? + null, + buttons, + commandMenuObjectCount: + scene?.commandMenuObjects?.filter( + (object) => object.active + ).length ?? 0, + suppressNextLeftClick: + scene?.suppressNextLeftClick ?? null, + canvasBounds: canvasBounds + ? { + x: canvasBounds.x, + y: canvasBounds.y, + width: canvasBounds.width, + height: canvasBounds.height + } + : null, + sceneSize: scene + ? { + width: scene.scale.width, + height: scene.scale.height + } + : null, + activePointer: pointer + ? { + x: pointer.x, + y: pointer.y, + downX: pointer.downX, + downY: pointer.downY, + upX: pointer.upX, + upY: pointer.upY, + isDown: pointer.isDown + } + : null + }; + }, click); +} + async function turnPromptPointerDiagnostic(page, click) { return page.evaluate((requestedClick) => { const scene = window.__HEROS_GAME__?.scene.getScene('BattleScene');