From c26bbe5d06377f611731d41d5b48f0b4b5562d64 Mon Sep 17 00:00:00 2001 From: yoniebans Date: Tue, 25 Aug 2026 09:47:23 +0300 Subject: [PATCH] fix(install-e2e): force 100% zoom before driving the app The app ships a 90% UI-zoom default, so a fresh install renders at devicePixelRatio 0.9 and Playwright's input coordinates land ~10% off target on the CI runners: clicks aimed at the titlebar settings gear hit the bar beside it, which read as a phantom overlay interception and failed every app-update leg. Set zoom to 100% through webContents after boot, retrying until dpr reads 1 because the boot path re-applies the default asynchronously. --- tests/install/e2e-assets/drive-update.cjs | 24 +++++++++++++++++++ tests/install/e2e-assets/launch-from-spec.mjs | 24 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/tests/install/e2e-assets/drive-update.cjs b/tests/install/e2e-assets/drive-update.cjs index a58919b51d..ac01da8539 100644 --- a/tests/install/e2e-assets/drive-update.cjs +++ b/tests/install/e2e-assets/drive-update.cjs @@ -117,6 +117,30 @@ async function main() { log(`window picked (${app.windows().length} windows, url=${page.url()})`) log('first window acquired') + // The app ships a 90% UI-zoom default, so a fresh install renders at + // devicePixelRatio 0.9 and Playwright's input coordinates land ~10% off + // target on the CI runners. Force 100% through the same webContents API + // the app's zoom control uses. A single set gets reverted (the boot path + // re-applies the default asynchronously), so set-verify-retry until dpr + // reads 1. + try { + const before = await page.evaluate(() => window.devicePixelRatio) + let after = before + for (let i = 0; i < 20; i++) { + await app.evaluate(({ BrowserWindow }) => { + for (const w of BrowserWindow.getAllWindows()) { + w.webContents.setZoomLevel(0) + } + }) + await page.waitForTimeout(1000) + after = await page.evaluate(() => window.devicePixelRatio) + if (Math.abs(after - 1) < 0.001) break + } + log(`[zoom] forced 100% via webContents.setZoomLevel(0): dpr ${before} -> ${after}`) + } catch (e) { + log(`[zoom] direct zoom set failed (continuing): ${e.message}`) + } + // Boot: wait for the composer to exist — the shell is mounted by then. // The real backend (`hermes serve`) is booting underneath; give it time. await page.waitForSelector('textarea, [contenteditable="true"]', { diff --git a/tests/install/e2e-assets/launch-from-spec.mjs b/tests/install/e2e-assets/launch-from-spec.mjs index 9cfff16ab0..0781f5203e 100644 --- a/tests/install/e2e-assets/launch-from-spec.mjs +++ b/tests/install/e2e-assets/launch-from-spec.mjs @@ -131,6 +131,30 @@ async function main() { log(`window up: ${await window.title()} (${app.windows().length} windows, picked url=${window.url()})`); await window.screenshot({ path: `${values.spec}.window.png` }).catch(() => {}); + // The app ships a 90% UI-zoom default, so a fresh install renders at + // devicePixelRatio 0.9 and Playwright's input coordinates land ~10% off + // target on the CI runners. Force 100% through the same webContents API + // the app's zoom control uses. A single set gets reverted (the boot path + // re-applies the default asynchronously), so set-verify-retry until dpr + // reads 1. + try { + const before = await window.evaluate(() => window.devicePixelRatio); + let after = before; + for (let i = 0; i < 20; i++) { + await app.evaluate(({ BrowserWindow }) => { + for (const w of BrowserWindow.getAllWindows()) { + w.webContents.setZoomLevel(0); + } + }); + await window.waitForTimeout(1000); + after = await window.evaluate(() => window.devicePixelRatio); + if (Math.abs(after - 1) < 0.001) break; + } + log(`[zoom] forced 100% via webContents.setZoomLevel(0): dpr ${before} -> ${after}`); + } catch (e) { + log(`[zoom] direct zoom set failed (continuing): ${e.message}`); + } + if (values['no-update']) { log('smoke mode: window proven, closing'); await app.close().catch(() => {});