; Mosis Designer Test Utilities ; AutoHotkey v2 #Include "..\config.ahk" ; Find the designer window and return its handle FindDesignerWindow() { return WinExist(WINDOW_TITLE) } ; Wait for the designer window to appear ; Returns window handle or 0 if timeout WaitForDesignerWindow(timeout := 0) { if (timeout = 0) timeout := STARTUP_TIMEOUT startTime := A_TickCount while (A_TickCount - startTime < timeout) { hwnd := FindDesignerWindow() if (hwnd) { ; Give window time to fully initialize Sleep(500) return hwnd } Sleep(100) } return 0 } ; Get the client area position of the designer window ; Returns {x, y, w, h} or 0 if window not found GetClientArea(hwnd := 0) { if (hwnd = 0) hwnd := FindDesignerWindow() if (!hwnd) return 0 ; Get window position WinGetPos(&winX, &winY, &winW, &winH, hwnd) ; Get client area - for GLFW windows, typically minimal border ; Client area starts after title bar ; We'll estimate based on typical Windows decorations ; Title bar is usually ~30-40 pixels, borders ~8 pixels each side ; For a more accurate approach, we could use DllCall to GetClientRect ; But for GLFW windows, the client area often matches the requested size ; Calculate client area assuming standard decorations borderWidth := 8 titleHeight := 31 return { x: winX + borderWidth, y: winY + titleHeight, w: PHONE_WIDTH, h: PHONE_HEIGHT } } ; Convert phone coordinates to screen coordinates PhoneToScreen(phoneX, phoneY, hwnd := 0) { client := GetClientArea(hwnd) if (!client) return 0 return { x: client.x + phoneX, y: client.y + phoneY } } ; Click at phone coordinates ClickPhone(phoneX, phoneY, hwnd := 0) { if (hwnd = 0) hwnd := FindDesignerWindow() if (!hwnd) { LogMessage("ERROR: Designer window not found") return false } ; Activate window first WinActivate(hwnd) Sleep(50) screen := PhoneToScreen(phoneX, phoneY, hwnd) if (!screen) { LogMessage("ERROR: Could not convert coordinates") return false } ; Move and click Click(screen.x, screen.y) Sleep(CLICK_DELAY) LogMessage("Clicked at phone(" . phoneX . "," . phoneY . ") -> screen(" . screen.x . "," . screen.y . ")") return true } ; Click on a named app icon ClickApp(appName, hwnd := 0) { if (!APP_POSITIONS.Has(appName)) { LogMessage("ERROR: Unknown app: " . appName) return false } pos := APP_POSITIONS[appName] LogMessage("Clicking app: " . appName) return ClickPhone(pos.x, pos.y, hwnd) } ; Click on a dock item ClickDock(appName, hwnd := 0) { if (!DOCK_POSITIONS.Has(appName)) { LogMessage("ERROR: Unknown dock item: " . appName) return false } pos := DOCK_POSITIONS[appName] LogMessage("Clicking dock: " . appName) return ClickPhone(pos.x, pos.y, hwnd) } ; Send keyboard input to the designer SendToDesigner(keys, hwnd := 0) { if (hwnd = 0) hwnd := FindDesignerWindow() if (!hwnd) return false WinActivate(hwnd) Sleep(50) Send(keys) return true } ; Read the log file and check for a pattern ; Returns the matching line or empty string CheckLogFor(pattern, logFile := 0) { if (logFile = 0) logFile := LOG_FILE if (!FileExist(logFile)) return "" content := FileRead(logFile) lines := StrSplit(content, "`n") ; Search from end (most recent) loop lines.Length { idx := lines.Length - A_Index + 1 line := lines[idx] if (InStr(line, pattern)) return line } return "" } ; Wait for a specific pattern to appear in the log WaitForLog(pattern, timeout := 0, logFile := 0) { if (timeout = 0) timeout := NAVIGATION_TIMEOUT if (logFile = 0) logFile := LOG_FILE startTime := A_TickCount while (A_TickCount - startTime < timeout) { result := CheckLogFor(pattern, logFile) if (result) return result Sleep(100) } return "" } ; Wait for navigation to a specific screen WaitForNavigation(screenName, timeout := 0) { pattern := "Navigated to: " . screenName return WaitForLog(pattern, timeout) } ; Wait for back navigation WaitForBack(timeout := 0) { return WaitForLog("Back to:", timeout) } ; Take a screenshot of the designer window CaptureScreenshot(filename, hwnd := 0) { if (hwnd = 0) hwnd := FindDesignerWindow() if (!hwnd) return false ; Ensure screenshot directory exists if (!DirExist(SCREENSHOT_DIR)) DirCreate(SCREENSHOT_DIR) fullPath := SCREENSHOT_DIR . "\" . filename ; Use built-in screenshot capability ; Note: This captures the entire window including decorations try { ; Activate and bring to front WinActivate(hwnd) Sleep(100) ; Get window position WinGetPos(&x, &y, &w, &h, hwnd) ; Use GDI+ or Windows API for screenshot ; For simplicity, we'll use the Snipping approach ; In production, you'd use a proper GDI+ screenshot LogMessage("Screenshot requested: " . fullPath . " (not implemented)") return false } catch as e { LogMessage("Screenshot error: " . e.Message) return false } } ; Log a message with timestamp LogMessage(msg) { timestamp := FormatTime(, "yyyy-MM-dd HH:mm:ss") line := "[" . timestamp . "] " . msg . "`n" ; Write to console OutputDebug(line) ; Also append to a test log file testLog := A_ScriptDir . "\test_run.log" FileAppend(line, testLog) } ; Clear test logs ClearLogs() { testLog := A_ScriptDir . "\test_run.log" if (FileExist(testLog)) FileDelete(testLog) if (FileExist(LOG_FILE)) FileDelete(LOG_FILE) } ; Close the designer window CloseDesigner(hwnd := 0) { if (hwnd = 0) hwnd := FindDesignerWindow() if (hwnd) { WinClose(hwnd) Sleep(500) } } ; Kill any running designer processes KillDesigner() { try { Run('taskkill /F /IM mosis-designer.exe', , "Hide") } Sleep(500) } ; Start the designer process with log redirection ; Returns the process ID or 0 on failure StartDesigner(rmlFile := 0) { if (rmlFile = 0) rmlFile := HOME_RML ; Build command with output redirection cmd := '"' . DESIGNER_EXE . '" "' . rmlFile . '"' LogMessage("Starting designer: " . cmd) ; Start process and capture output ; We'll redirect to a log file fullCmd := A_ComSpec . ' /c "' . cmd . '" > "' . LOG_FILE . '" 2>&1' Run(fullCmd, A_ScriptDir, "Hide", &pid) if (pid) { LogMessage("Designer started with PID: " . pid) return pid } LogMessage("ERROR: Failed to start designer") return 0 } ; Assert helper - logs pass/fail and returns result Assert(condition, testName) { if (condition) { LogMessage("PASS: " . testName) return true } else { LogMessage("FAIL: " . testName) return false } }