Files
MosisService/docs/TESTING-FRAMEWORK.md

2.1 KiB

Automated Testing Framework

The designer-test (designer-test/) provides automated UI testing.

Test Architecture

  1. WindowController: Finds designer window, sends mouse/keyboard events via Windows SendInput API
  2. HierarchyReader: Parses UI hierarchy JSON to find elements by ID/class (with retry logic and exponential backoff)
  3. LogParser: Monitors log file for navigation events
  4. TestRunner: Orchestrates test execution, reports results
  5. UIInspector: Dumps UI hierarchy with atomic writes (temp file + rename pattern)

Key Implementation Details

  • Path Normalization: RmlUi uses | instead of : in Windows paths (e.g., D|\Dev\...). The UIInspector normalizes paths for correct document matching.
  • Atomic File Writes: Hierarchy files are written to .tmp then renamed to prevent partial reads.
  • Retry with Backoff: HierarchyReader retries up to 10 times with exponential backoff (30ms base) and validates JSON completeness.
  • Dynamic Back Button: GoHome() finds back buttons from hierarchy by class (app-bar-nav or browser-nav-btn) instead of fixed coordinates.

Writing Tests

// Find element by ID and click it
bool ClickById(TestContext& ctx, const std::string& id) {
    ctx.hierarchy.Reload();
    auto element = ctx.hierarchy.FindById(id);
    if (!element) return false;

    int x = element->bounds.centerX();
    int y = element->bounds.centerY();
    ScaleToPhysical(ctx, x, y);  // Convert logical to physical coords
    ctx.window.SendClick(x, y);
    return true;
}

// Test example
bool TestNavigateToDialer(TestContext& ctx) {
    GoHome(ctx);
    ctx.log.Clear();

    if (!ClickById(ctx, "dock-phone")) return false;

    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    ctx.log.Reload();
    return ctx.log.Contains("Loaded screen: apps/dialer/dialer.rml");
}

Test Output

Tests produce JSON results at test_results.json:

{
  "name": "Mosis Designer UI Tests",
  "summary": {"passed": 5, "failed": 0, "total": 5},
  "tests": [
    {"name": "Navigate to Dialer", "status": "passed", "duration_ms": 3500}
  ]
}