diff --git a/apps/desktop/e2e/window-input.unit.test.ts b/apps/desktop/e2e/window-input.unit.test.ts index d9da8da466..a15b2b32ba 100644 --- a/apps/desktop/e2e/window-input.unit.test.ts +++ b/apps/desktop/e2e/window-input.unit.test.ts @@ -6,6 +6,28 @@ const { prepareWindowForInput } = createRequire(import.meta.url)( '../../../tests/install/e2e-assets/window-input.cjs', ) +test('does not finish when IPC reports 100% before the window factor settles', async () => { + let observations = 0 + const previous = (globalThis as any).hermesDesktop + ;(globalThis as any).hermesDesktop = { zoom: { + setPercent: () => undefined, + get: async () => ({ percent: 100 }), + } } + const appWindow = { evaluate: async (fn: any) => fn({ webContents: { + getZoomFactor: () => ++observations === 1 ? 0.9 : 1, + } }) } + const page = { + evaluate: async (fn: any) => fn(), + waitForTimeout: async () => undefined, + } + try { + await prepareWindowForInput({ browserWindow: async () => appWindow }, page) + expect(observations).toBeGreaterThan(1) + } finally { + ;(globalThis as any).hermesDesktop = previous + } +}) + test('reapplies zoom when startup overwrites the first request', async () => { let requests = 0 let factor = 0.9 diff --git a/tests/install/e2e-assets/drive-update.cjs b/tests/install/e2e-assets/drive-update.cjs index ffb33d2658..1473d01ae2 100644 --- a/tests/install/e2e-assets/drive-update.cjs +++ b/tests/install/e2e-assets/drive-update.cjs @@ -161,6 +161,8 @@ async function main() { let iter = 0 while (!openedSettings) { + // A boot-time restore or focus event can move the scale after preparation. + await prepareWindowForInput(app, page) iter++ for (const make of laterLocators) { try { diff --git a/tests/install/e2e-assets/install-and-launch.ahk b/tests/install/e2e-assets/install-and-launch.ahk index 960a740bb3..31a14eb7a7 100644 --- a/tests/install/e2e-assets/install-and-launch.ahk +++ b/tests/install/e2e-assets/install-and-launch.ahk @@ -199,14 +199,16 @@ if launchFound { } Log("Launch clicked; waiting for the Hermes desktop app window") -; The installer spawns Hermes.exe detached and exits itself. +; WinWait returns 0 on timeout; it does not throw. The old unchecked return +; led to WinGetPos throwing "Target window not found." Reuse the bounded +; real-window poll so transient handles are ignored and failures name the wait. +; CI's installer remained on LAUNCHING past 120s after a successful bootstrap. try { - WinWait(appWin, , 120) + appRect := WaitForRealWindow(appWin, 300000) } catch { - throw Error("Hermes.exe window did not appear within 120s of clicking Launch") + throw Error("Hermes.exe real-sized window did not appear within 300s of clicking Launch") } -WinGetPos(&ax, &ay, &aw, &ah, appWin) -Log(Format("App window appeared at x={1} y={2} w={3} h={4}", ax, ay, aw, ah)) +Log(Format("App window appeared at x={1} y={2} w={3} h={4}", appRect.x, appRect.y, appRect.w, appRect.h)) Sleep(8000) ; let the renderer paint (recorded as proof) Log("done") diff --git a/tests/install/e2e-assets/launch-from-spec.mjs b/tests/install/e2e-assets/launch-from-spec.mjs index 49db869218..bfe372e08c 100644 --- a/tests/install/e2e-assets/launch-from-spec.mjs +++ b/tests/install/e2e-assets/launch-from-spec.mjs @@ -216,6 +216,7 @@ async function main() { } }).then((d) => JSON.stringify(d)).catch((e) => `hit-dump failed: ${e.message}`) for (let iter = 1; ; iter++) { + await prepareWindowForInput(app, window); await later .click({ timeout: 2_000 }) .then(async () => { @@ -225,6 +226,9 @@ async function main() { .catch((e) => log(`[overlay] iter ${iter} chooseLater click failed: ${brief(e)}`)) try { await settingsButton.click({ timeout: 4_000 }) + // A landed click during shell hydration can be lost on a remount. + // Confirm the destination before looking for its About control. + await window.waitForURL(/[#/]settings(?:[/?]|$)/, { timeout: 4_000 }) settingsOpened = true break } catch (e) { diff --git a/tests/install/e2e-assets/window-input.cjs b/tests/install/e2e-assets/window-input.cjs index a15dea187d..5fbbb76355 100644 --- a/tests/install/e2e-assets/window-input.cjs +++ b/tests/install/e2e-assets/window-input.cjs @@ -21,8 +21,13 @@ async function prepareWindowForInput(app, page) { globalThis.hermesDesktop.zoom.setPercent(100) return globalThis.hermesDesktop.zoom.get() }) - if (state.percent === 100) break - if (Date.now() >= deadline) throw new Error('timed out waiting for 100% app window zoom') + // The renderer IPC and BrowserWindow can observe different moments of + // startup restoration. Both must agree before the driver sends input. + const factor = await window.evaluate(win => win.webContents.getZoomFactor()) + if (state.percent === 100 && Math.abs(factor - 1) < 0.001) return + if (Date.now() >= deadline) { + throw new Error(`timed out waiting for 100% app window zoom (IPC ${state.percent}%, factor ${factor})`) + } await page.waitForTimeout(100) } } else {