complete milestone 1
This commit is contained in:
@@ -17,25 +17,56 @@ bool HierarchyReader::Reload() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Retry a few times with delay to handle file write race conditions
|
||||
for (int attempt = 0; attempt < 5; ++attempt) {
|
||||
// Retry with increasing delays to handle file write race conditions
|
||||
const int maxAttempts = 10;
|
||||
const int baseDelayMs = 30;
|
||||
|
||||
for (int attempt = 0; attempt < maxAttempts; ++attempt) {
|
||||
if (attempt > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
// Exponential backoff: 30, 60, 120, ... ms
|
||||
int delayMs = baseDelayMs * (1 << std::min(attempt - 1, 4));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(delayMs));
|
||||
}
|
||||
|
||||
std::ifstream file(m_path);
|
||||
if (!file.is_open()) {
|
||||
continue; // File might not exist yet, retry
|
||||
// Read entire file into string first (to avoid partial reads)
|
||||
std::string content;
|
||||
{
|
||||
std::ifstream file(m_path, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
continue; // File might not exist yet, retry
|
||||
}
|
||||
|
||||
// Get file size
|
||||
file.seekg(0, std::ios::end);
|
||||
auto size = file.tellg();
|
||||
if (size <= 0) {
|
||||
continue; // Empty file, retry
|
||||
}
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
// Read content
|
||||
content.resize(static_cast<size_t>(size));
|
||||
file.read(content.data(), size);
|
||||
|
||||
if (!file.good()) {
|
||||
continue; // Read error, retry
|
||||
}
|
||||
}
|
||||
|
||||
// Check for valid JSON ending (should end with "}")
|
||||
size_t lastNonSpace = content.find_last_not_of(" \t\n\r");
|
||||
if (lastNonSpace == std::string::npos || content[lastNonSpace] != '}') {
|
||||
continue; // File is incomplete, retry
|
||||
}
|
||||
|
||||
try {
|
||||
m_json = nlohmann::json::parse(file);
|
||||
m_json = nlohmann::json::parse(content);
|
||||
m_loaded = true;
|
||||
return true;
|
||||
} catch (const nlohmann::json::parse_error& e) {
|
||||
// File might be partially written, retry
|
||||
if (attempt == 4) {
|
||||
std::cerr << "Failed to parse hierarchy JSON after 5 attempts: " << e.what() << std::endl;
|
||||
if (attempt == maxAttempts - 1) {
|
||||
std::cerr << "Failed to parse hierarchy JSON after " << maxAttempts << " attempts: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <filesystem>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
|
||||
using namespace mosis::test;
|
||||
|
||||
@@ -104,26 +105,88 @@ bool ClickByClassIndex(TestContext& ctx, const std::string& className, int index
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper: Wait for hierarchy to update to expected screen
|
||||
bool WaitForScreen(TestContext& ctx, const std::string& expectedScreen, int timeoutMs = 3000) {
|
||||
auto startTime = std::chrono::steady_clock::now();
|
||||
while (true) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
ctx.hierarchy.Reload();
|
||||
std::string currentScreen = ctx.hierarchy.GetScreenName();
|
||||
if (currentScreen.find(expectedScreen) != std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
auto elapsed = std::chrono::steady_clock::now() - startTime;
|
||||
if (std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count() >= timeoutMs) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: Find a back button in the hierarchy
|
||||
std::optional<ElementInfo> FindBackButton(TestContext& ctx) {
|
||||
// Try to find back button by class (app-bar-nav is used in most screens)
|
||||
auto elements = ctx.hierarchy.FindByClass("app-bar-nav");
|
||||
for (const auto& elem : elements) {
|
||||
if (elem.visible && elem.bounds.width > 0 && elem.bounds.height > 0) {
|
||||
return elem;
|
||||
}
|
||||
}
|
||||
|
||||
// Try browser-specific back button
|
||||
elements = ctx.hierarchy.FindByClass("browser-nav-btn");
|
||||
for (const auto& elem : elements) {
|
||||
if (elem.visible && elem.bounds.width > 0 && elem.bounds.height > 0) {
|
||||
return elem;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Helper: Go back to home screen by clicking back button multiple times
|
||||
void GoHome(TestContext& ctx) {
|
||||
// Click back button (app-bar-nav) up to 5 times to ensure we're at home
|
||||
// Wait longer for hierarchy file to be updated from any previous navigation
|
||||
// The designer writes hierarchy every frame, but there can be a race condition
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1500));
|
||||
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
// Wait then reload to get fresh hierarchy data
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
||||
ctx.hierarchy.Reload();
|
||||
// Look for the back button by class
|
||||
auto elements = ctx.hierarchy.FindByClass("app-bar-nav");
|
||||
if (!elements.empty()) {
|
||||
auto& btn = elements[0];
|
||||
if (btn.visible && btn.bounds.width > 0) {
|
||||
int x = btn.bounds.centerX();
|
||||
int y = btn.bounds.centerY();
|
||||
ScaleToPhysical(ctx, x, y);
|
||||
ctx.window.SendClick(x, y);
|
||||
}
|
||||
|
||||
// Check hierarchy to see if dock-phone exists (indicating home screen)
|
||||
auto dockPhone = ctx.hierarchy.FindById("dock-phone");
|
||||
if (dockPhone && dockPhone->visible) {
|
||||
std::cout << " GoHome: At home screen (dock-phone found)" << std::endl;
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(400));
|
||||
|
||||
// Find back button from hierarchy
|
||||
auto backBtn = FindBackButton(ctx);
|
||||
int x, y;
|
||||
if (backBtn) {
|
||||
x = backBtn->bounds.centerX();
|
||||
y = backBtn->bounds.centerY();
|
||||
std::cout << " GoHome: Found back button at (" << x << "," << y << ")" << std::endl;
|
||||
} else {
|
||||
// Fallback to default position if no back button found
|
||||
x = 48;
|
||||
y = 36;
|
||||
std::cout << " GoHome: No back button found, using default position" << std::endl;
|
||||
}
|
||||
|
||||
ScaleToPhysical(ctx, x, y);
|
||||
std::cout << " GoHome: Clicking back at (" << x << "," << y << ")" << std::endl;
|
||||
ctx.window.SendClick(x, y);
|
||||
|
||||
// Wait for navigation animation to complete
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
}
|
||||
// Extra wait for animations to fully complete and hierarchy to update
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(800));
|
||||
|
||||
// Final verification - wait and reload hierarchy
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
ctx.hierarchy.Reload();
|
||||
std::cout << " GoHome: Final screen = " << ctx.hierarchy.GetScreenName() << std::endl;
|
||||
}
|
||||
|
||||
// Test: Navigate to dialer by clicking Phone dock icon
|
||||
|
||||
@@ -6,8 +6,35 @@
|
||||
|
||||
namespace mosis::test {
|
||||
|
||||
// Callback for EnumWindows to find window by partial title match
|
||||
struct FindWindowData {
|
||||
std::string searchTitle;
|
||||
HWND foundHwnd = nullptr;
|
||||
};
|
||||
|
||||
static BOOL CALLBACK EnumWindowsCallback(HWND hwnd, LPARAM lParam) {
|
||||
auto* data = reinterpret_cast<FindWindowData*>(lParam);
|
||||
|
||||
char title[256] = {0};
|
||||
GetWindowTextA(hwnd, title, sizeof(title));
|
||||
|
||||
// Check if the window title contains our search string
|
||||
if (std::string(title).find(data->searchTitle) != std::string::npos) {
|
||||
data->foundHwnd = hwnd;
|
||||
return FALSE; // Stop enumeration
|
||||
}
|
||||
return TRUE; // Continue enumeration
|
||||
}
|
||||
|
||||
bool WindowController::FindWindow(const std::string& title) {
|
||||
m_hwnd = ::FindWindowA(nullptr, title.c_str());
|
||||
// Use EnumWindows to find window by partial title match
|
||||
// This allows finding "Mosis Designer - home.rml" when searching for "Mosis Designer"
|
||||
FindWindowData data;
|
||||
data.searchTitle = title;
|
||||
|
||||
EnumWindows(EnumWindowsCallback, reinterpret_cast<LPARAM>(&data));
|
||||
|
||||
m_hwnd = data.foundHwnd;
|
||||
if (!m_hwnd) {
|
||||
return false;
|
||||
}
|
||||
@@ -80,24 +107,33 @@ LPARAM WindowController::PhoneToClientLParam(int phoneX, int phoneY) {
|
||||
bool WindowController::SendMouseDown(int phoneX, int phoneY) {
|
||||
if (!m_hwnd) return false;
|
||||
|
||||
// Convert to screen coordinates for SendInput
|
||||
// Convert phone coordinates to client coordinates
|
||||
int clientX = static_cast<int>(phoneX * m_info.scaleX);
|
||||
int clientY = static_cast<int>(phoneY * m_info.scaleY);
|
||||
|
||||
// Get DPI info for debugging
|
||||
UINT dpi = GetDpiForWindow(m_hwnd);
|
||||
|
||||
// Calculate screen coordinates from client position
|
||||
// On DPI-aware systems, Windows APIs return consistent coordinate spaces
|
||||
int screenX = m_info.clientX + clientX;
|
||||
int screenY = m_info.clientY + clientY;
|
||||
|
||||
// Use SendInput for proper GLFW compatibility
|
||||
// First move the cursor to the position
|
||||
SetCursorPos(screenX, screenY);
|
||||
// Ensure window is foreground before clicking
|
||||
SetForegroundWindow(m_hwnd);
|
||||
Sleep(10); // Small delay
|
||||
|
||||
// Use SendInput for GLFW compatibility
|
||||
SetCursorPos(screenX, screenY);
|
||||
Sleep(10); // Small delay for cursor move
|
||||
|
||||
// Send mouse down via SendInput
|
||||
INPUT input = {};
|
||||
input.type = INPUT_MOUSE;
|
||||
input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
|
||||
SendInput(1, &input, sizeof(INPUT));
|
||||
|
||||
std::cout << "MouseDown at phone(" << phoneX << "," << phoneY << ") -> screen("
|
||||
<< screenX << "," << screenY << ")" << std::endl;
|
||||
<< screenX << "," << screenY << ") dpi=" << dpi << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -105,12 +141,15 @@ bool WindowController::SendMouseDown(int phoneX, int phoneY) {
|
||||
bool WindowController::SendMouseUp(int phoneX, int phoneY) {
|
||||
if (!m_hwnd) return false;
|
||||
|
||||
// Send mouse up via SendInput
|
||||
Sleep(10); // Small delay before release
|
||||
|
||||
INPUT input = {};
|
||||
input.type = INPUT_MOUSE;
|
||||
input.mi.dwFlags = MOUSEEVENTF_LEFTUP;
|
||||
SendInput(1, &input, sizeof(INPUT));
|
||||
|
||||
Sleep(10); // Small delay after release
|
||||
|
||||
std::cout << "MouseUp at phone(" << phoneX << "," << phoneY << ")" << std::endl;
|
||||
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user