fix(e2e): AHK driver died on first Log() — no-console stdout write throws
Frame-0005 of the proof capture showed the exact failure: 'Unhandled
error: (6) The handle is invalid' rendered over the installer within
seconds of launch. AutoHotkey started via Start-Process has no console,
so FileAppend to '*' (stdout) throws — and the throw fired inside Log(),
killing the script before it clicked anything. The installer then sat
untouched at the INSTALL screen for 50 minutes.
* Log() now try-wraps the stdout write (file log is the real record)
* Install/Launch clicks fall back to the button's relative window
position when the #68183-era PNG templates don't match the restyled
UI ('[ INSTALL ]' bracket style visible in the same frame)
* install-finished has a second signal: 'bootstrap complete' in
bootstrap-installer.log (read with write-sharing), so a template miss
can't strand the wait
* driver passes the bootstrap log path as arg 3
This commit is contained in:
@@ -9,18 +9,32 @@
|
||||
; (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 actually clicks Launch instead of closing the window,
|
||||
; because the Launch hand-off is part of the flow under test.
|
||||
; confused, and it clicks Launch instead of closing the window, because
|
||||
; the Launch hand-off is part of the flow under test.
|
||||
;
|
||||
; Args: [1] log path [2] setup exe name (default Hermes-Setup.exe)
|
||||
; 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.
|
||||
;
|
||||
; Args: [1] log path [2] setup exe name [3] bootstrap-installer.log path
|
||||
|
||||
logPath := A_Args.Length >= 1 ? A_Args[1] : "ahk.log"
|
||||
setupExe := A_Args.Length >= 2 ? A_Args[2] : "Hermes-Setup.exe"
|
||||
bootstrapLog := A_Args.Length >= 3 ? A_Args[3] : ""
|
||||
|
||||
Log(text) {
|
||||
msg := Format("[autohotkey] {}`n", text)
|
||||
ToolTip(text)
|
||||
FileAppend(msg, '*')
|
||||
; 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, '*')
|
||||
FileAppend(msg, logPath)
|
||||
}
|
||||
|
||||
@@ -43,10 +57,13 @@ ClickWithMarker(x, y, button := "Left") {
|
||||
Log(Format("Clicked at {1}, {2}", x, y))
|
||||
}
|
||||
|
||||
FindImageInWindow(winTitle, imageFile, &outX, &outY, timeoutMs := 10000, intervalMs := 250)
|
||||
{
|
||||
WinGetPos(&wx, &wy, &ww, &wh, winTitle)
|
||||
|
||||
; 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
|
||||
}
|
||||
hBitmap := LoadPicture(imageFile)
|
||||
if !hBitmap {
|
||||
throw Error("LoadPicture failed: " imageFile)
|
||||
@@ -55,39 +72,49 @@ FindImageInWindow(winTitle, imageFile, &outX, &outY, timeoutMs := 10000, interva
|
||||
DllCall("GetObject", "Ptr", hBitmap, "Int", bm.Size, "Ptr", bm)
|
||||
width := NumGet(bm, 4, "Int")
|
||||
height := NumGet(bm, 8, "Int")
|
||||
|
||||
startTime := A_TickCount
|
||||
timeLeft := 1
|
||||
Log(Format("Searching for {} in {} ...", imageFile, winTitle))
|
||||
searchImage := Format("*10 {}", imageFile)
|
||||
while (timeLeft > 0)
|
||||
{
|
||||
; Refresh the window rect each pass — the installer window can move
|
||||
; or resize between stages.
|
||||
try WinGetPos(&wx, &wy, &ww, &wh, winTitle)
|
||||
if ImageSearch(&x, &y, wx, wy, wx + ww, wy + wh, searchImage)
|
||||
{
|
||||
outX := x + Floor(width / 2)
|
||||
outY := y + Floor(height / 2)
|
||||
Log("Found " imageFile)
|
||||
return
|
||||
}
|
||||
Sleep intervalMs
|
||||
timeLeft := timeoutMs - (A_TickCount - startTime)
|
||||
ToolTip(Format("Searching {} in {} ... {}s left", imageFile, winTitle, Round(timeLeft / 1000, 2)))
|
||||
if ImageSearch(&x, &y, wx, wy, wx + ww, wy + wh, Format("*10 {}", imageFile)) {
|
||||
outX := x + Floor(width / 2)
|
||||
outY := y + Floor(height / 2)
|
||||
return true
|
||||
}
|
||||
throw Error(Format("Failed to find {} in window {}", imageFile, winTitle))
|
||||
return false
|
||||
}
|
||||
|
||||
ClickCenterOfImageInWindow(winTitle, imageFile, timeoutMs := 10000, intervalMs := 250)
|
||||
{
|
||||
FindImageInWindow(winTitle, imageFile, &x, &y, timeoutMs, intervalMs)
|
||||
ClickWithMarker(x, y)
|
||||
; 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")
|
||||
if !f {
|
||||
return false
|
||||
}
|
||||
content := f.Read()
|
||||
f.Close()
|
||||
return InStr(content, needle) > 0
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
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).
|
||||
BTN_FX := 0.50
|
||||
BTN_FY := 0.87
|
||||
|
||||
Log("Waiting for the installer window (" installerWin ") ...")
|
||||
try {
|
||||
WinWait(installerWin, , 60)
|
||||
@@ -97,16 +124,62 @@ try {
|
||||
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 ───────────────────────────────────────────────
|
||||
ClickCenterOfImageInWindow(installerWin, A_ScriptDir "\install-button.png", 60000)
|
||||
Log("Install clicked; waiting for the Launch button (install can take a while)")
|
||||
; ── Step 1: click Install (template first, relative-position fallback) ──
|
||||
installClicked := false
|
||||
deadline := A_TickCount + 60000
|
||||
while (A_TickCount < deadline) {
|
||||
if TryFindImage(installerWin, A_ScriptDir "\install-button.png", &ix, &iy) {
|
||||
ClickWithMarker(ix, iy)
|
||||
Log("Install clicked (template match)")
|
||||
installClicked := true
|
||||
break
|
||||
}
|
||||
Sleep(500)
|
||||
}
|
||||
if !installClicked {
|
||||
WindowRelPoint(installerWin, BTN_FX, BTN_FY, &ix, &iy)
|
||||
ClickWithMarker(ix, iy)
|
||||
Log("FALLBACK: install template never matched; clicked relative position")
|
||||
}
|
||||
|
||||
; ── Step 2: wait for install to finish (Launch button appears) ──────────
|
||||
FindImageInWindow(installerWin, A_ScriptDir "\launch-button.png", &launchX, &launchY, 1000 * 60 * 45)
|
||||
Log("Install finished (Launch button visible)")
|
||||
; ── 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.
|
||||
launchX := 0, launchY := 0
|
||||
launchFound := false
|
||||
completeSince := 0
|
||||
waitDeadline := A_TickCount + 1000 * 60 * 45
|
||||
Log("Waiting for install to finish (Launch template or bootstrap log) ...")
|
||||
while (A_TickCount < waitDeadline) {
|
||||
if TryFindImage(installerWin, 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)")
|
||||
}
|
||||
|
||||
; ── Step 3: click Launch — the hand-off under test ──────────────────────
|
||||
ClickWithMarker(launchX, launchY)
|
||||
if launchFound {
|
||||
ClickWithMarker(launchX, launchY)
|
||||
} else {
|
||||
WindowRelPoint(installerWin, BTN_FX, BTN_FY, &lx, &ly)
|
||||
ClickWithMarker(lx, ly)
|
||||
Log("FALLBACK: clicked Launch at relative position")
|
||||
}
|
||||
Log("Launch clicked; waiting for the Hermes desktop app window")
|
||||
|
||||
; The installer spawns Hermes.exe detached and exits itself.
|
||||
|
||||
@@ -292,8 +292,10 @@ function Invoke-PhaseInstallGui {
|
||||
Write-Host " Hermes-Setup.exe launched (pid $($installer.Id))"
|
||||
|
||||
# Drive it: Install click -> wait -> Launch click -> Hermes.exe window.
|
||||
# Arg 3 lets the AHK script use the installer's own log as the
|
||||
# install-finished fallback signal.
|
||||
$ahk = Start-Process -FilePath $ahkExe `
|
||||
-ArgumentList (Join-Path $AhkDir "install-and-launch.ahk"), $ahkLog `
|
||||
-ArgumentList (Join-Path $AhkDir "install-and-launch.ahk"), $ahkLog, "Hermes-Setup.exe", (Join-Path $HermesHome "logs\bootstrap-installer.log") `
|
||||
-PassThru
|
||||
# Install on a cold runner takes a while; the AHK script's own inner
|
||||
# timeout (45 min on the Launch wait) is the effective budget.
|
||||
|
||||
Reference in New Issue
Block a user