work in progress

This commit is contained in:
2026-01-16 12:43:06 +01:00
parent 77a9579025
commit c8ee7defe8
236 changed files with 11405 additions and 28 deletions

364
designer-test/src/main.cpp Normal file
View File

@@ -0,0 +1,364 @@
// Mosis Designer Test - Automated UI Testing
// Sends input events directly to the designer window using element coordinates from hierarchy dump
#include "test_runner.h"
#include <iostream>
#include <filesystem>
#include <thread>
#include <chrono>
using namespace mosis::test;
// Helper: Scale coordinates from hierarchy (logical) to window (physical) space
void ScaleToPhysical(TestContext& ctx, int& x, int& y) {
// The hierarchy reports coordinates in RmlUi's logical space
// which may be DPI-scaled. We need to convert to physical window coordinates.
int hierarchyWidth = ctx.hierarchy.GetWidth();
int hierarchyHeight = ctx.hierarchy.GetHeight();
int windowWidth = ctx.window.GetInfo().clientWidth;
int windowHeight = ctx.window.GetInfo().clientHeight;
if (hierarchyWidth > 0 && hierarchyHeight > 0) {
x = static_cast<int>(x * static_cast<float>(windowWidth) / hierarchyWidth);
y = static_cast<int>(y * static_cast<float>(windowHeight) / hierarchyHeight);
}
}
// Helper: Click on an element found by ID in the hierarchy
bool ClickById(TestContext& ctx, const std::string& id) {
ctx.hierarchy.Reload();
// Debug: Print hierarchy info
std::cout << " Hierarchy: " << ctx.hierarchy.GetWidth() << "x" << ctx.hierarchy.GetHeight()
<< ", screen: " << ctx.hierarchy.GetScreenName() << std::endl;
auto element = ctx.hierarchy.FindById(id);
if (!element) {
std::cerr << " Element not found: #" << id << std::endl;
// List available IDs for debugging
auto allElements = ctx.hierarchy.GetAllElements();
std::cerr << " Available IDs: ";
for (const auto& e : allElements) {
if (!e.id.empty()) {
std::cerr << "#" << e.id << " ";
}
}
std::cerr << std::endl;
return false;
}
if (!element->visible) {
std::cerr << " Element not visible: #" << id << std::endl;
return false;
}
int x = element->bounds.centerX();
int y = element->bounds.centerY();
int logicalX = x, logicalY = y;
ScaleToPhysical(ctx, x, y);
std::cout << " Clicking #" << id << " at logical(" << logicalX << "," << logicalY
<< ") -> physical(" << x << "," << y << ")" << std::endl;
ctx.window.SendClick(x, y);
return true;
}
// Helper: Click on an element found by class name (first visible match)
bool ClickByClass(TestContext& ctx, const std::string& className) {
ctx.hierarchy.Reload();
auto elements = ctx.hierarchy.FindByClass(className);
for (const auto& element : elements) {
if (element.visible && element.bounds.width > 0 && element.bounds.height > 0) {
int x = element.bounds.centerX();
int y = element.bounds.centerY();
ScaleToPhysical(ctx, x, y);
std::cout << " Clicking ." << className << " at physical(" << x << "," << y << ")" << std::endl;
ctx.window.SendClick(x, y);
return true;
}
}
std::cerr << " No visible element found with class: " << className << std::endl;
return false;
}
// Helper: Click on an element found by class name at a specific index
bool ClickByClassIndex(TestContext& ctx, const std::string& className, int index) {
ctx.hierarchy.Reload();
auto elements = ctx.hierarchy.FindByClass(className);
int visibleIndex = 0;
for (const auto& element : elements) {
if (element.visible && element.bounds.width > 0 && element.bounds.height > 0) {
if (visibleIndex == index) {
int x = element.bounds.centerX();
int y = element.bounds.centerY();
ScaleToPhysical(ctx, x, y);
std::cout << " Clicking ." << className << "[" << index << "] at physical("
<< x << "," << y << ")" << std::endl;
ctx.window.SendClick(x, y);
return true;
}
visibleIndex++;
}
}
std::cerr << " Element ." << className << "[" << index << "] not found (only "
<< visibleIndex << " visible)" << std::endl;
return false;
}
// 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
for (int i = 0; i < 5; ++i) {
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);
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(400));
}
// Extra wait for animations to fully complete and hierarchy to update
std::this_thread::sleep_for(std::chrono::milliseconds(800));
}
// Test: Navigate to dialer by clicking Phone dock icon
bool TestNavigateToDialer(TestContext& ctx) {
// Ensure we start from home screen
GoHome(ctx);
ctx.log.Clear();
// Click on Phone dock icon (dock-phone id)
if (!ClickById(ctx, "dock-phone")) {
return false;
}
// Wait for navigation
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
// Reload log and check for navigation
ctx.log.Reload();
return ctx.log.Contains("Loaded screen: apps/dialer/dialer.rml") ||
ctx.log.Contains("apps/dialer");
}
// Test: Navigate to messages
bool TestNavigateToMessages(TestContext& ctx) {
// Ensure we start from home screen
GoHome(ctx);
ctx.log.Clear();
// Click on Messages dock icon
if (!ClickById(ctx, "dock-messages")) {
return false;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
ctx.log.Reload();
return ctx.log.Contains("Loaded screen: apps/messages/messages.rml");
}
// Test: Navigate to contacts
bool TestNavigateToContacts(TestContext& ctx) {
// Ensure we start from home screen
GoHome(ctx);
ctx.log.Clear();
// Click on Contacts dock icon
if (!ClickById(ctx, "dock-contacts")) {
return false;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
ctx.log.Reload();
return ctx.log.Contains("Loaded screen: apps/contacts/contacts.rml");
}
// Test: Navigate to browser
bool TestNavigateToBrowser(TestContext& ctx) {
// Ensure we start from home screen
GoHome(ctx);
ctx.log.Clear();
// Click on Browser dock icon
if (!ClickById(ctx, "dock-browser")) {
return false;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
ctx.log.Reload();
return ctx.log.Contains("Loaded screen: apps/browser/browser.rml");
}
// Test: Navigate to settings (in app grid, not dock)
bool TestNavigateToSettings(TestContext& ctx) {
// Ensure we start from home screen
GoHome(ctx);
ctx.log.Clear();
// Settings is in the app grid - find by ID
if (!ClickById(ctx, "app-settings")) {
return false;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
ctx.log.Reload();
return ctx.log.Contains("Loaded screen: apps/settings/settings.rml");
}
// Test: Multiple navigation sequence from home
bool TestNavigationSequence(TestContext& ctx) {
// Ensure we start from home screen
GoHome(ctx);
ctx.log.Clear();
// Go to dialer from home
if (!ClickById(ctx, "dock-phone")) {
std::cerr << " Failed to click dock-phone" << std::endl;
return false;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
// Go back to home
GoHome(ctx);
// Go to messages from home
if (!ClickById(ctx, "dock-messages")) {
std::cerr << " Failed to click dock-messages" << std::endl;
return false;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
// Go back to home
GoHome(ctx);
// Go to contacts from home
if (!ClickById(ctx, "dock-contacts")) {
std::cerr << " Failed to click dock-contacts" << std::endl;
return false;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
ctx.log.Reload();
// Verify all navigations happened
bool hasDialer = ctx.log.Contains("apps/dialer");
bool hasMessages = ctx.log.Contains("apps/messages");
bool hasContacts = ctx.log.Contains("apps/contacts");
std::cout << " Dialer: " << (hasDialer ? "yes" : "no") << std::endl;
std::cout << " Messages: " << (hasMessages ? "yes" : "no") << std::endl;
std::cout << " Contacts: " << (hasContacts ? "yes" : "no") << std::endl;
return hasDialer && hasMessages && hasContacts;
}
int main(int argc, char* argv[]) {
std::cout << "Mosis Designer Test v0.2.0" << std::endl;
std::cout << "==========================" << std::endl;
std::cout << "Using UI hierarchy for element coordinates" << std::endl;
// Determine paths
// exe is at: designer-test/build/Debug/designer-test.exe
// project root is: MosisService/
std::filesystem::path exePath = std::filesystem::absolute(argv[0]).parent_path();
std::filesystem::path projectRoot = exePath.parent_path().parent_path().parent_path(); // Up 3 levels
std::filesystem::path designerPath = projectRoot / "designer" / "build" / "Debug" / "mosis-designer.exe";
std::filesystem::path documentPath = projectRoot / "src" / "main" / "assets" / "apps" / "home" / "home.rml";
std::filesystem::path logPath = exePath / "designer_test.log";
std::filesystem::path hierarchyPath = exePath / "ui_hierarchy.json";
std::filesystem::path resultsPath = exePath / "test_results.json";
// Allow command-line overrides
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "--designer" && i + 1 < argc) {
designerPath = argv[++i];
} else if (arg == "--document" && i + 1 < argc) {
documentPath = argv[++i];
} else if (arg == "--log" && i + 1 < argc) {
logPath = argv[++i];
} else if (arg == "--hierarchy" && i + 1 < argc) {
hierarchyPath = argv[++i];
} else if (arg == "--results" && i + 1 < argc) {
resultsPath = argv[++i];
} else if (arg == "--help") {
std::cout << "Usage: designer-test [options]" << std::endl;
std::cout << "Options:" << std::endl;
std::cout << " --designer PATH Path to mosis-designer.exe" << std::endl;
std::cout << " --document PATH Path to initial RML document" << std::endl;
std::cout << " --log PATH Path for log file" << std::endl;
std::cout << " --hierarchy PATH Path for UI hierarchy dump file" << std::endl;
std::cout << " --results PATH Path for JSON test results" << std::endl;
std::cout << " --help Show this help" << std::endl;
return 0;
}
}
std::cout << "Designer: " << designerPath << std::endl;
std::cout << "Document: " << documentPath << std::endl;
std::cout << "Log file: " << logPath << std::endl;
std::cout << "Hierarchy: " << hierarchyPath << std::endl;
// Check if designer exists
if (!std::filesystem::exists(designerPath)) {
std::cerr << "ERROR: Designer not found at: " << designerPath << std::endl;
std::cerr << "Build the designer first: cd designer && cmake --build build --config Debug" << std::endl;
return 1;
}
if (!std::filesystem::exists(documentPath)) {
std::cerr << "ERROR: Document not found at: " << documentPath << std::endl;
return 1;
}
// Create test runner
TestRunner runner;
runner.SetDesignerPath(designerPath);
runner.SetDocumentPath(documentPath);
runner.SetLogPath(logPath);
runner.SetHierarchyPath(hierarchyPath);
runner.SetStartupTimeoutMs(15000);
runner.SetActionDelayMs(500);
// Register tests
runner.AddTest("Navigate to Dialer", TestNavigateToDialer);
runner.AddTest("Navigate to Messages", TestNavigateToMessages);
runner.AddTest("Navigate to Contacts", TestNavigateToContacts);
runner.AddTest("Navigate to Browser", TestNavigateToBrowser);
runner.AddTest("Navigation Sequence", TestNavigationSequence);
// Start designer
std::cout << "\nStarting designer..." << std::endl;
if (!runner.StartDesigner()) {
std::cerr << "Failed to start designer" << std::endl;
return 1;
}
std::cout << "Designer started successfully" << std::endl;
std::cout << "Window info:" << std::endl;
const auto& info = runner.GetWindow().GetInfo();
std::cout << " Scale: " << info.scaleX << " x " << info.scaleY << std::endl;
// Run tests
auto results = runner.RunAll();
// Save results
results.SaveToFile(resultsPath);
// Stop designer
std::cout << "Stopping designer..." << std::endl;
runner.StopDesigner();
// Return exit code based on results
return (results.failed + results.errors) > 0 ? 1 : 0;
}