// gui_automation_client.h // gRPC client for automating YAZE GUI through ImGuiTestHarness service #ifndef YAZE_CLI_SERVICE_GUI_AUTOMATION_CLIENT_H #define YAZE_CLI_SERVICE_GUI_AUTOMATION_CLIENT_H #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/time/time.h" #include #include #include #include #include #include #include #ifdef YAZE_WITH_GRPC #include #include "protos/imgui_test_harness.grpc.pb.h" #endif namespace yaze { namespace cli { /** * @brief Type of click action to perform */ enum class ClickType { kLeft, kRight, kMiddle, kDouble }; /** * @brief Result of a GUI automation action */ struct AutomationResult { bool success; std::string message; std::chrono::milliseconds execution_time; std::string actual_value; // For assertions std::string expected_value; // For assertions std::string test_id; // Test execution identifier (for introspection) }; /** * @brief Execution status codes returned by the harness */ enum class TestRunStatus { kUnknown, kQueued, kRunning, kPassed, kFailed, kTimeout }; /** * @brief Detailed information about an individual test execution */ struct TestStatusDetails { std::string test_id; TestRunStatus status = TestRunStatus::kUnknown; std::optional queued_at; std::optional started_at; std::optional completed_at; int execution_time_ms = 0; std::string error_message; std::vector assertion_failures; }; /** * @brief Aggregated metadata about a harness test */ struct HarnessTestSummary { std::string test_id; std::string name; std::string category; std::optional last_run_at; int total_runs = 0; int pass_count = 0; int fail_count = 0; int average_duration_ms = 0; }; /** * @brief Result container for ListTests RPC */ struct ListTestsResult { std::vector tests; std::string next_page_token; int total_count = 0; }; /** * @brief Individual assertion outcome within a harness test */ struct AssertionOutcome { std::string description; bool passed = false; std::string expected_value; std::string actual_value; std::string error_message; }; /** * @brief Detailed execution results for a specific harness test */ struct TestResultDetails { std::string test_id; bool success = false; std::string test_name; std::string category; std::optional executed_at; int duration_ms = 0; std::vector assertions; std::vector logs; std::map metrics; std::string screenshot_path; int64_t screenshot_size_bytes = 0; std::string failure_context; std::string widget_state; }; struct ReplayTestResult { bool success = false; std::string message; std::string replay_session_id; int steps_executed = 0; std::vector assertions; std::vector logs; }; struct StartRecordingResult { bool success = false; std::string message; std::string recording_id; std::optional started_at; }; struct StopRecordingResult { bool success = false; std::string message; std::string output_path; int step_count = 0; std::chrono::milliseconds duration{0}; }; enum class WidgetTypeFilter { kUnspecified, kAll, kButton, kInput, kMenu, kTab, kCheckbox, kSlider, kCanvas, kSelectable, kOther, }; struct WidgetBoundingBox { float min_x = 0.0f; float min_y = 0.0f; float max_x = 0.0f; float max_y = 0.0f; }; struct WidgetDescriptor { std::string path; std::string label; std::string type; std::string description; std::string suggested_action; bool visible = true; bool enabled = true; WidgetBoundingBox bounds; uint32_t widget_id = 0; bool has_bounds = false; int64_t last_seen_frame = -1; std::optional last_seen_at; bool stale = false; }; struct DiscoveredWindowInfo { std::string name; bool visible = true; std::vector widgets; }; struct DiscoverWidgetsQuery { std::string window_filter; WidgetTypeFilter type_filter = WidgetTypeFilter::kUnspecified; std::string path_prefix; bool include_invisible = false; bool include_disabled = false; }; struct DiscoverWidgetsResult { std::vector windows; int total_widgets = 0; std::optional generated_at; }; /** * @brief Client for automating YAZE GUI through gRPC * * This client wraps the ImGuiTestHarness gRPC service and provides * a C++ API for CLI commands to drive the YAZE GUI remotely. * * Example usage: * @code * GuiAutomationClient client("localhost:50052"); * RETURN_IF_ERROR(client.Connect()); * * auto result = client.Click("button:Overworld", ClickType::kLeft); * if (!result.ok()) return result.status(); * * if (!result->success) { * return absl::InternalError(result->message); * } * @endcode */ class GuiAutomationClient { public: /** * @brief Construct a new GUI automation client * @param server_address Address of the test harness server (e.g., "localhost:50052") */ explicit GuiAutomationClient(const std::string& server_address); /** * @brief Connect to the test harness server * @return Status indicating success or failure */ absl::Status Connect(); /** * @brief Check if the server is reachable and responsive * @param message Optional message to send in ping * @return Result with server version and timestamp */ absl::StatusOr Ping(const std::string& message = "ping"); /** * @brief Click a GUI element * @param target Target element (format: "button:Label" or "window:Name") * @param type Type of click (left, right, middle, double) * @return Result indicating success/failure and execution time */ absl::StatusOr Click(const std::string& target, ClickType type = ClickType::kLeft); /** * @brief Type text into an input field * @param target Target input field (format: "input:Label") * @param text Text to type * @param clear_first Whether to clear existing text before typing * @return Result indicating success/failure and execution time */ absl::StatusOr Type(const std::string& target, const std::string& text, bool clear_first = false); /** * @brief Wait for a condition to be met * @param condition Condition to wait for (e.g., "window_visible:Editor") * @param timeout_ms Maximum time to wait in milliseconds * @param poll_interval_ms How often to check the condition * @return Result indicating whether condition was met */ absl::StatusOr Wait(const std::string& condition, int timeout_ms = 5000, int poll_interval_ms = 100); /** * @brief Assert a GUI state condition * @param condition Condition to assert (e.g., "visible:Window Name") * @return Result with actual vs expected values */ absl::StatusOr Assert(const std::string& condition); /** * @brief Capture a screenshot * @param region Region to capture ("full", "window", "element") * @param format Image format ("PNG", "JPEG") * @return Result with file path if successful */ absl::StatusOr Screenshot(const std::string& region = "full", const std::string& format = "PNG"); /** * @brief Fetch the current execution status for a harness test */ absl::StatusOr GetTestStatus(const std::string& test_id); /** * @brief Enumerate harness tests with optional filtering */ absl::StatusOr ListTests(const std::string& category_filter = "", int page_size = 100, const std::string& page_token = ""); /** * @brief Retrieve detailed results for a harness test execution */ absl::StatusOr GetTestResults(const std::string& test_id, bool include_logs = false); absl::StatusOr DiscoverWidgets( const DiscoverWidgetsQuery& query); absl::StatusOr ReplayTest( const std::string& script_path, bool ci_mode, const std::map& parameter_overrides = {}); absl::StatusOr StartRecording( const std::string& output_path, const std::string& session_name, const std::string& description); absl::StatusOr StopRecording( const std::string& recording_id, bool discard = false); /** * @brief Check if client is connected */ bool IsConnected() const { return connected_; } /** * @brief Get the server address */ const std::string& ServerAddress() const { return server_address_; } private: #ifdef YAZE_WITH_GRPC std::unique_ptr stub_; #endif std::string server_address_; bool connected_ = false; }; } // namespace cli } // namespace yaze #endif // YAZE_CLI_SERVICE_GUI_AUTOMATION_CLIENT_H