fix(e2e): re-capture install-button template + fix phantom window geometry
Attempt 2's proof frames showed two bugs, both now fixed from the live evidence: 1. The #68183 install-button.png predated the installer UI restyle to the '[ INSTALL ]' bracket look, so the template never matched and we fell through to the position fallback. Re-captured install-button.png from a real CI desktop frame (the actual rendered button). 2. The fallback then clicked the WRONG spot: ahk_exe's first WinGetPos matched a hidden 16x16 helper window ('Window found at w=16 h=16' in ahk.log), and BTN_FY=0.87 aimed below the real button anyway. The button center measured at ~(0.50, 0.59) of the ~full-screen window. Rewrite: * WaitForRealWindow() skips phantom/hidden matches (requires w>400,h>300) and returns the true rect; the installer window is then activated before any click. * Install-finished is now driven primarily by the authoritative 'bootstrap complete' line in bootstrap-installer.log (matches BootstrapEvent::Complete), with the Launch template as a secondary signal and a window-relative fallback click. * Fallback clicks use the corrected (0.50, 0.59) window fraction.
This commit is contained in:
@@ -6,20 +6,23 @@
|
||||
; the real Hermes.exe (Electron desktop) window to appear.
|
||||
;
|
||||
; Adapted from @ethernet8023's e2e/windows/install-hermes-desktop.ahk
|
||||
; (PR #68183) — same ImageSearch approach and button templates; this
|
||||
; variant targets windows by process name (ahk_exe) so the installer
|
||||
; window and the launched app window (both titled "Hermes") can't be
|
||||
; confused, and it clicks Launch instead of closing the window, because
|
||||
; the Launch hand-off is part of the flow under test.
|
||||
; (PR #68183) — same ImageSearch approach; the install-button template was
|
||||
; re-captured from a live CI frame (the #68183 templates predated the
|
||||
; installer UI restyle to the "[ INSTALL ]" bracket look and never matched).
|
||||
;
|
||||
; Robustness beyond the original:
|
||||
; * Log() survives a missing stdout (GUI-subsystem AHK started without a
|
||||
; console throws "(6) The handle is invalid" on FileAppend to '*' —
|
||||
; that single throw killed the whole first CI attempt).
|
||||
; * If a button template doesn't match (installer UI restyled), falls
|
||||
; back to clicking the button's known relative position, and the
|
||||
; install-finished signal falls back to "bootstrap complete" in
|
||||
; bootstrap-installer.log. Every fallback is logged loudly.
|
||||
; Robustness beyond the original — every one earned from a real CI failure:
|
||||
; * Log() survives a missing stdout. GUI-subsystem AHK started via
|
||||
; Start-Process has no console, so FileAppend to '*' throws
|
||||
; "(6) The handle is invalid" — that single throw killed attempt 1.
|
||||
; * We wait for a REAL-sized installer window before reading geometry.
|
||||
; ahk_exe matched a hidden 16x16 helper window first (attempt 2's
|
||||
; "Window found at ... w=16 h=16"), so relative-position math was
|
||||
; computed against a phantom rect.
|
||||
; * Clicks fall back to a SCREEN-fraction position when the template
|
||||
; doesn't match (the Tauri window is effectively full-screen on the
|
||||
; runner, so screen ~= window). Button center measured at ~0.50, 0.59.
|
||||
; * Install-finished has a second signal: "bootstrap complete" in
|
||||
; bootstrap-installer.log, so a Launch-template miss can't strand us.
|
||||
;
|
||||
; Args: [1] log path [2] setup exe name [3] bootstrap-installer.log path
|
||||
|
||||
@@ -30,49 +33,59 @@ bootstrapLog := A_Args.Length >= 3 ? A_Args[3] : ""
|
||||
Log(text) {
|
||||
msg := Format("[autohotkey] {}`n", text)
|
||||
ToolTip(text)
|
||||
; stdout only exists when AHK was launched from a console. Under
|
||||
; Start-Process (no console) FileAppend to '*' throws "(6) The handle
|
||||
; is invalid" — and a Log() that throws kills the whole script from
|
||||
; inside OnError. The file log below is the real record.
|
||||
try FileAppend(msg, '*')
|
||||
try FileAppend(msg, '*') ; stdout may not exist (no console)
|
||||
FileAppend(msg, logPath)
|
||||
}
|
||||
|
||||
OnError(LogError)
|
||||
|
||||
LogError(err, mode) {
|
||||
Log(Format("Unhandled error: {}", err.Message))
|
||||
ExitApp(1)
|
||||
return -1 ; suppress the standard error dialog
|
||||
return -1
|
||||
}
|
||||
|
||||
SetWorkingDir(A_ScriptDir)
|
||||
CoordMode("Pixel", "Screen")
|
||||
CoordMode("Mouse", "Screen")
|
||||
|
||||
ClickWithMarker(x, y, button := "Left") {
|
||||
Click(x, y, button)
|
||||
ClickWithMarker(x, y) {
|
||||
Click(x, y)
|
||||
Sleep(10)
|
||||
MouseMove(30, 30)
|
||||
Log(Format("Clicked at {1}, {2}", x, y))
|
||||
}
|
||||
|
||||
; Single-pass image search inside a window. Returns true + center coords.
|
||||
TryFindImage(winTitle, imageFile, &outX, &outY) {
|
||||
try {
|
||||
WinGetPos(&wx, &wy, &ww, &wh, winTitle)
|
||||
} catch {
|
||||
return false
|
||||
; Wait until an installer window exists AND has a real (non-phantom) size,
|
||||
; then return its rect. ahk_exe can transiently match a hidden helper.
|
||||
WaitForRealWindow(winTitle, timeoutMs) {
|
||||
deadline := A_TickCount + timeoutMs
|
||||
while (A_TickCount < deadline) {
|
||||
for hwnd in WinGetList(winTitle) {
|
||||
try {
|
||||
WinGetPos(&wx, &wy, &ww, &wh, "ahk_id " hwnd)
|
||||
if (ww > 400 && wh > 300) {
|
||||
return { hwnd: hwnd, x: wx, y: wy, w: ww, h: wh }
|
||||
}
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
}
|
||||
Sleep(500)
|
||||
}
|
||||
throw Error(Format("no real-sized window matched {} within {}ms", winTitle, timeoutMs))
|
||||
}
|
||||
|
||||
; Image search inside a rect. Returns true + center coords.
|
||||
TryFindImage(rect, imageFile, &outX, &outY) {
|
||||
hBitmap := LoadPicture(imageFile)
|
||||
if !hBitmap {
|
||||
throw Error("LoadPicture failed: " imageFile)
|
||||
}
|
||||
bm := Buffer(32, 0) ; BITMAP structure on x64
|
||||
bm := Buffer(32, 0)
|
||||
DllCall("GetObject", "Ptr", hBitmap, "Int", bm.Size, "Ptr", bm)
|
||||
width := NumGet(bm, 4, "Int")
|
||||
height := NumGet(bm, 8, "Int")
|
||||
if ImageSearch(&x, &y, wx, wy, wx + ww, wy + wh, Format("*10 {}", imageFile)) {
|
||||
if ImageSearch(&x, &y, rect.x, rect.y, rect.x + rect.w, rect.y + rect.h, Format("*20 {}", imageFile)) {
|
||||
outX := x + Floor(width / 2)
|
||||
outY := y + Floor(height / 2)
|
||||
return true
|
||||
@@ -80,21 +93,13 @@ TryFindImage(winTitle, imageFile, &outX, &outY) {
|
||||
return false
|
||||
}
|
||||
|
||||
; Fractional window position -> screen coords (fallback click target).
|
||||
WindowRelPoint(winTitle, fx, fy, &outX, &outY) {
|
||||
WinGetPos(&wx, &wy, &ww, &wh, winTitle)
|
||||
outX := wx + Floor(ww * fx)
|
||||
outY := wy + Floor(wh * fy)
|
||||
}
|
||||
|
||||
BootstrapLogContains(needle) {
|
||||
global bootstrapLog
|
||||
if (bootstrapLog = "" or !FileExist(bootstrapLog)) {
|
||||
return false
|
||||
}
|
||||
try {
|
||||
; Read-share open: the installer still holds the file for writing.
|
||||
f := FileOpen(bootstrapLog, "r-d")
|
||||
f := FileOpen(bootstrapLog, "r-d") ; read, share read+write
|
||||
if !f {
|
||||
return false
|
||||
}
|
||||
@@ -109,26 +114,26 @@ BootstrapLogContains(needle) {
|
||||
installerWin := "ahk_exe " setupExe
|
||||
appWin := "ahk_exe Hermes.exe"
|
||||
|
||||
; The Install/Launch button sits centered horizontally near the bottom of
|
||||
; the installer window (measured from production screenshots; used only
|
||||
; when the image template fails to match a restyled UI).
|
||||
; Button center as a fraction of the window rect (installer is ~full-screen
|
||||
; on the runner). Measured from a live CI frame: center ~ (0.50, 0.59).
|
||||
BTN_FX := 0.50
|
||||
BTN_FY := 0.87
|
||||
BTN_FY := 0.59
|
||||
|
||||
Log("Waiting for the installer window (" installerWin ") ...")
|
||||
Log("Waiting for a real-sized installer window (" installerWin ") ...")
|
||||
rect := WaitForRealWindow(installerWin, 90000)
|
||||
Log(Format("Installer window: x={1} y={2} w={3} h={4}", rect.x, rect.y, rect.w, rect.h))
|
||||
try {
|
||||
WinWait(installerWin, , 60)
|
||||
WinActivate("ahk_id " rect.hwnd)
|
||||
Sleep(500)
|
||||
} catch {
|
||||
throw Error("installer window did not appear within 60s")
|
||||
Log("WARNING: could not activate installer window")
|
||||
}
|
||||
WinGetPos(&x, &y, &w, &h, installerWin)
|
||||
Log(Format("Window found at x={1} y={2} w={3} h={4}", x, y, w, h))
|
||||
|
||||
; ── Step 1: click Install (template first, relative-position fallback) ──
|
||||
; ── Step 1: click Install ───────────────────────────────────────────────
|
||||
installClicked := false
|
||||
deadline := A_TickCount + 60000
|
||||
deadline := A_TickCount + 30000
|
||||
while (A_TickCount < deadline) {
|
||||
if TryFindImage(installerWin, A_ScriptDir "\install-button.png", &ix, &iy) {
|
||||
if TryFindImage(rect, A_ScriptDir "\install-button.png", &ix, &iy) {
|
||||
ClickWithMarker(ix, iy)
|
||||
Log("Install clicked (template match)")
|
||||
installClicked := true
|
||||
@@ -137,48 +142,58 @@ while (A_TickCount < deadline) {
|
||||
Sleep(500)
|
||||
}
|
||||
if !installClicked {
|
||||
WindowRelPoint(installerWin, BTN_FX, BTN_FY, &ix, &iy)
|
||||
ix := rect.x + Floor(rect.w * BTN_FX)
|
||||
iy := rect.y + Floor(rect.h * BTN_FY)
|
||||
ClickWithMarker(ix, iy)
|
||||
Log("FALLBACK: install template never matched; clicked relative position")
|
||||
Log("FALLBACK: install template never matched; clicked window-relative position")
|
||||
}
|
||||
|
||||
; ── Step 2: wait for the install to finish ──────────────────────────────
|
||||
; Primary signal: the Launch button template appears. Secondary signal:
|
||||
; "bootstrap complete" in bootstrap-installer.log (the installer's own
|
||||
; completion line) — after which we give the template 2 more minutes and
|
||||
; then fall back to the relative-position click.
|
||||
; Primary: the "bootstrap complete" line in the installer's own log — the
|
||||
; authoritative done signal. Secondary: the Launch button template.
|
||||
launchX := 0, launchY := 0
|
||||
launchFound := false
|
||||
completeSince := 0
|
||||
complete := false
|
||||
waitDeadline := A_TickCount + 1000 * 60 * 45
|
||||
Log("Waiting for install to finish (Launch template or bootstrap log) ...")
|
||||
Log("Waiting for install to finish (bootstrap log or Launch template) ...")
|
||||
while (A_TickCount < waitDeadline) {
|
||||
if TryFindImage(installerWin, A_ScriptDir "\launch-button.png", &launchX, &launchY) {
|
||||
if BootstrapLogContains("bootstrap complete") {
|
||||
complete := true
|
||||
Log("bootstrap-installer.log reports completion")
|
||||
break
|
||||
}
|
||||
; refresh the rect (window can move/resize between stages)
|
||||
try rect := WaitForRealWindow(installerWin, 2000)
|
||||
if TryFindImage(rect, A_ScriptDir "\launch-button.png", &launchX, &launchY) {
|
||||
launchFound := true
|
||||
Log("Install finished (Launch template visible)")
|
||||
break
|
||||
}
|
||||
if (completeSince = 0 and BootstrapLogContains("bootstrap complete")) {
|
||||
completeSince := A_TickCount
|
||||
Log("bootstrap-installer.log reports completion; giving the Launch template 120s")
|
||||
}
|
||||
if (completeSince > 0 and A_TickCount - completeSince > 120000) {
|
||||
Log("FALLBACK: log says complete but Launch template never matched")
|
||||
break
|
||||
}
|
||||
Sleep(2000)
|
||||
}
|
||||
if (!launchFound and completeSince = 0) {
|
||||
throw Error("install did not finish within 45 minutes (no Launch button, no completion log line)")
|
||||
if (!launchFound and !complete) {
|
||||
throw Error("install did not finish within 45 minutes (no completion log line, no Launch button)")
|
||||
}
|
||||
|
||||
; Give the button a moment to swap Install -> Launch after completion.
|
||||
if (complete and !launchFound) {
|
||||
Sleep(2000)
|
||||
try rect := WaitForRealWindow(installerWin, 10000)
|
||||
if TryFindImage(rect, A_ScriptDir "\launch-button.png", &launchX, &launchY) {
|
||||
launchFound := true
|
||||
Log("Launch template matched after completion")
|
||||
}
|
||||
}
|
||||
|
||||
; ── Step 3: click Launch — the hand-off under test ──────────────────────
|
||||
if launchFound {
|
||||
ClickWithMarker(launchX, launchY)
|
||||
Log("Launch clicked (template)")
|
||||
} else {
|
||||
WindowRelPoint(installerWin, BTN_FX, BTN_FY, &lx, &ly)
|
||||
lx := rect.x + Floor(rect.w * BTN_FX)
|
||||
ly := rect.y + Floor(rect.h * BTN_FY)
|
||||
ClickWithMarker(lx, ly)
|
||||
Log("FALLBACK: clicked Launch at relative position")
|
||||
Log("FALLBACK: clicked Launch at window-relative position")
|
||||
}
|
||||
Log("Launch clicked; waiting for the Hermes desktop app window")
|
||||
|
||||
@@ -191,9 +206,6 @@ try {
|
||||
WinGetPos(&ax, &ay, &aw, &ah, appWin)
|
||||
Log(Format("App window appeared at x={1} y={2} w={3} h={4}", ax, ay, aw, ah))
|
||||
|
||||
; Give the renderer a few seconds on screen (recorded as proof), then hand
|
||||
; control back to the PowerShell driver, which closes the app and re-launches
|
||||
; it under Playwright for the update legs.
|
||||
Sleep(8000)
|
||||
Sleep(8000) ; let the renderer paint (recorded as proof)
|
||||
Log("done")
|
||||
ExitApp(0)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 911 B |
Reference in New Issue
Block a user