6#include "absl/strings/str_cat.h"
7#include "absl/strings/str_format.h"
8#include "absl/time/time.h"
18std::optional<absl::Time> OptionalTimeFromMillis(int64_t millis) {
22 return absl::FromUnixMillis(millis);
25yaze::test::WidgetType ConvertWidgetTypeFilterToProto(
WidgetTypeFilter filter) {
26 using ProtoType = yaze::test::WidgetType;
29 return ProtoType::WIDGET_TYPE_ALL;
31 return ProtoType::WIDGET_TYPE_BUTTON;
33 return ProtoType::WIDGET_TYPE_INPUT;
35 return ProtoType::WIDGET_TYPE_MENU;
37 return ProtoType::WIDGET_TYPE_TAB;
39 return ProtoType::WIDGET_TYPE_CHECKBOX;
41 return ProtoType::WIDGET_TYPE_SLIDER;
43 return ProtoType::WIDGET_TYPE_CANVAS;
45 return ProtoType::WIDGET_TYPE_SELECTABLE;
47 return ProtoType::WIDGET_TYPE_OTHER;
50 return ProtoType::WIDGET_TYPE_UNSPECIFIED;
55 yaze::test::GetTestStatusResponse::TestStatus status) {
56 using ProtoStatus = yaze::test::GetTestStatusResponse::TestStatus;
58 case ProtoStatus::GetTestStatusResponse_TestStatus_TEST_STATUS_QUEUED:
60 case ProtoStatus::GetTestStatusResponse_TestStatus_TEST_STATUS_RUNNING:
62 case ProtoStatus::GetTestStatusResponse_TestStatus_TEST_STATUS_PASSED:
64 case ProtoStatus::GetTestStatusResponse_TestStatus_TEST_STATUS_FAILED:
66 case ProtoStatus::GetTestStatusResponse_TestStatus_TEST_STATUS_TIMEOUT:
68 case ProtoStatus::GetTestStatusResponse_TestStatus_TEST_STATUS_UNSPECIFIED:
78 : server_address_(server_address) {}
83 grpc::CreateChannel(
server_address_, grpc::InsecureChannelCredentials());
85 return absl::InternalError(
"Failed to create gRPC channel");
88 stub_ = yaze::test::ImGuiTestHarness::NewStub(channel);
90 return absl::InternalError(
"Failed to create gRPC stub");
94 auto result =
Ping(
"connection_test");
96 return absl::UnavailableError(
97 absl::StrFormat(
"Failed to connect to test harness at %s: %s",
102 return absl::OkStatus();
104 return absl::UnimplementedError(
105 "GUI automation requires YAZE_WITH_GRPC=ON at build time");
110 const std::string& message) {
113 return absl::FailedPreconditionError(
114 "Not connected. Call Connect() first.");
117 yaze::test::PingRequest request;
118 request.set_message(message);
120 yaze::test::PingResponse response;
121 grpc::ClientContext context;
123 grpc::Status status = stub_->Ping(&context, request, &response);
126 return absl::InternalError(
127 absl::StrFormat(
"Ping RPC failed: %s", status.error_message()));
133 absl::StrFormat(
"Server version: %s (timestamp: %lld)",
134 response.yaze_version(), response.timestamp_ms());
139 return absl::UnimplementedError(
"gRPC not available");
144 const std::string& script_path,
bool ci_mode,
145 const std::map<std::string, std::string>& parameter_overrides) {
148 return absl::FailedPreconditionError(
149 "Not connected. Call Connect() first.");
152 yaze::test::ReplayTestRequest request;
153 request.set_script_path(script_path);
154 request.set_ci_mode(ci_mode);
155 for (
const auto& [key, value] : parameter_overrides) {
156 (*request.mutable_parameter_overrides())[key] = value;
159 yaze::test::ReplayTestResponse response;
160 grpc::ClientContext context;
162 grpc::Status status = stub_->ReplayTest(&context, request, &response);
164 return absl::InternalError(
165 absl::StrCat(
"ReplayTest RPC failed: ", status.error_message()));
169 result.
success = response.success();
170 result.
message = response.message();
173 result.
logs.assign(response.logs().begin(), response.logs().end());
174 result.
assertions.reserve(response.assertions_size());
175 for (
const auto& assertion_proto : response.assertions()) {
177 assertion.
description = assertion_proto.description();
178 assertion.
passed = assertion_proto.passed();
180 assertion.
actual_value = assertion_proto.actual_value();
182 result.
assertions.push_back(std::move(assertion));
187 return absl::UnimplementedError(
"gRPC not available");
192 const std::string& output_path,
const std::string& session_name,
193 const std::string& description) {
196 return absl::FailedPreconditionError(
197 "Not connected. Call Connect() first.");
200 yaze::test::StartRecordingRequest request;
201 request.set_output_path(output_path);
202 request.set_session_name(session_name);
203 request.set_description(description);
205 yaze::test::StartRecordingResponse response;
206 grpc::ClientContext context;
207 grpc::Status status = stub_->StartRecording(&context, request, &response);
210 return absl::InternalError(
211 absl::StrCat(
"StartRecording RPC failed: ", status.error_message()));
215 result.
success = response.success();
216 result.
message = response.message();
218 result.
started_at = OptionalTimeFromMillis(response.started_at_ms());
221 return absl::UnimplementedError(
"gRPC not available");
226 const std::string& recording_id,
bool discard) {
229 return absl::FailedPreconditionError(
230 "Not connected. Call Connect() first.");
232 if (recording_id.empty()) {
233 return absl::InvalidArgumentError(
"recording_id must not be empty");
236 yaze::test::StopRecordingRequest request;
237 request.set_recording_id(recording_id);
238 request.set_discard(discard);
240 yaze::test::StopRecordingResponse response;
241 grpc::ClientContext context;
242 grpc::Status status = stub_->StopRecording(&context, request, &response);
245 return absl::InternalError(
246 absl::StrCat(
"StopRecording RPC failed: ", status.error_message()));
250 result.
success = response.success();
251 result.
message = response.message();
254 result.
duration = std::chrono::milliseconds(response.duration_ms());
257 return absl::UnimplementedError(
"gRPC not available");
262 const std::string& target,
ClickType type) {
265 return absl::FailedPreconditionError(
266 "Not connected. Call Connect() first.");
269 yaze::test::ClickRequest request;
270 request.set_target(target);
274 request.set_type(yaze::test::ClickRequest::CLICK_TYPE_LEFT);
277 request.set_type(yaze::test::ClickRequest::CLICK_TYPE_RIGHT);
280 request.set_type(yaze::test::ClickRequest::CLICK_TYPE_MIDDLE);
283 request.set_type(yaze::test::ClickRequest::CLICK_TYPE_DOUBLE);
287 yaze::test::ClickResponse response;
288 grpc::ClientContext context;
290 grpc::Status status = stub_->Click(&context, request, &response);
293 return absl::InternalError(
294 absl::StrFormat(
"Click RPC failed: %s", status.error_message()));
298 result.
success = response.success();
299 result.
message = response.message();
301 std::chrono::milliseconds(response.execution_time_ms());
302 result.
test_id = response.test_id();
305 return absl::UnimplementedError(
"gRPC not available");
310 const std::string& target,
const std::string& text,
bool clear_first) {
313 return absl::FailedPreconditionError(
314 "Not connected. Call Connect() first.");
317 yaze::test::TypeRequest request;
318 request.set_target(target);
319 request.set_text(text);
320 request.set_clear_first(clear_first);
322 yaze::test::TypeResponse response;
323 grpc::ClientContext context;
325 grpc::Status status = stub_->Type(&context, request, &response);
328 return absl::InternalError(
329 absl::StrFormat(
"Type RPC failed: %s", status.error_message()));
333 result.
success = response.success();
334 result.
message = response.message();
336 std::chrono::milliseconds(response.execution_time_ms());
337 result.
test_id = response.test_id();
340 return absl::UnimplementedError(
"gRPC not available");
345 const std::string& condition,
int timeout_ms,
int poll_interval_ms) {
348 return absl::FailedPreconditionError(
349 "Not connected. Call Connect() first.");
352 yaze::test::WaitRequest request;
353 request.set_condition(condition);
354 request.set_timeout_ms(timeout_ms);
355 request.set_poll_interval_ms(poll_interval_ms);
357 yaze::test::WaitResponse response;
358 grpc::ClientContext context;
360 grpc::Status status = stub_->Wait(&context, request, &response);
363 return absl::InternalError(
364 absl::StrFormat(
"Wait RPC failed: %s", status.error_message()));
368 result.
success = response.success();
369 result.
message = response.message();
370 result.
execution_time = std::chrono::milliseconds(response.elapsed_ms());
371 result.
test_id = response.test_id();
374 return absl::UnimplementedError(
"gRPC not available");
379 const std::string& condition) {
382 return absl::FailedPreconditionError(
383 "Not connected. Call Connect() first.");
386 yaze::test::AssertRequest request;
387 request.set_condition(condition);
389 yaze::test::AssertResponse response;
390 grpc::ClientContext context;
392 grpc::Status status = stub_->Assert(&context, request, &response);
395 return absl::InternalError(
396 absl::StrFormat(
"Assert RPC failed: %s", status.error_message()));
400 result.
success = response.success();
401 result.
message = response.message();
405 result.
test_id = response.test_id();
408 return absl::UnimplementedError(
"gRPC not available");
413 const std::string& region,
const std::string& format) {
416 return absl::FailedPreconditionError(
417 "Not connected. Call Connect() first.");
420 yaze::test::ScreenshotRequest request;
421 request.set_window_title(
"");
422 request.set_output_path(
"/tmp/yaze_screenshot.png");
424 yaze::test::ScreenshotRequest::IMAGE_FORMAT_PNG);
426 yaze::test::ScreenshotResponse response;
427 grpc::ClientContext context;
429 grpc::Status status = stub_->Screenshot(&context, request, &response);
432 return absl::InternalError(
433 absl::StrFormat(
"Screenshot RPC failed: %s", status.error_message()));
437 result.
success = response.success();
438 result.
message = response.message();
443 return absl::UnimplementedError(
"gRPC not available");
448 const std::string& test_id) {
451 return absl::FailedPreconditionError(
452 "Not connected. Call Connect() first.");
455 yaze::test::GetTestStatusRequest request;
456 request.set_test_id(test_id);
458 yaze::test::GetTestStatusResponse response;
459 grpc::ClientContext context;
461 grpc::Status status = stub_->GetTestStatus(&context, request, &response);
464 return absl::InternalError(absl::StrFormat(
"GetTestStatus RPC failed: %s",
465 status.error_message()));
470 details.
status = ConvertStatusProto(response.status());
471 details.
queued_at = OptionalTimeFromMillis(response.queued_at_ms());
472 details.
started_at = OptionalTimeFromMillis(response.started_at_ms());
473 details.
completed_at = OptionalTimeFromMillis(response.completed_at_ms());
477 response.assertion_failures().end());
480 return absl::UnimplementedError(
"gRPC not available");
485 const std::string& category_filter,
int page_size,
486 const std::string& page_token) {
489 return absl::FailedPreconditionError(
490 "Not connected. Call Connect() first.");
493 yaze::test::ListTestsRequest request;
494 if (!category_filter.empty()) {
495 request.set_category_filter(category_filter);
498 request.set_page_size(page_size);
500 if (!page_token.empty()) {
501 request.set_page_token(page_token);
504 yaze::test::ListTestsResponse response;
505 grpc::ClientContext context;
507 grpc::Status status = stub_->ListTests(&context, request, &response);
510 return absl::InternalError(
511 absl::StrFormat(
"ListTests RPC failed: %s", status.error_message()));
517 result.
tests.reserve(response.tests_size());
519 for (
const auto& test_info : response.tests()) {
521 summary.
test_id = test_info.test_id();
522 summary.
name = test_info.name();
523 summary.
category = test_info.category();
525 OptionalTimeFromMillis(test_info.last_run_timestamp_ms());
530 result.
tests.push_back(std::move(summary));
535 return absl::UnimplementedError(
"gRPC not available");
540 const std::string& test_id,
bool include_logs) {
543 return absl::FailedPreconditionError(
544 "Not connected. Call Connect() first.");
547 yaze::test::GetTestResultsRequest request;
548 request.set_test_id(test_id);
549 request.set_include_logs(include_logs);
551 yaze::test::GetTestResultsResponse response;
552 grpc::ClientContext context;
554 grpc::Status status = stub_->GetTestResults(&context, request, &response);
557 return absl::InternalError(absl::StrFormat(
"GetTestResults RPC failed: %s",
558 status.error_message()));
563 result.
success = response.success();
565 result.
category = response.category();
566 result.
executed_at = OptionalTimeFromMillis(response.executed_at_ms());
569 result.
assertions.reserve(response.assertions_size());
570 for (
const auto& assertion : response.assertions()) {
573 outcome.
passed = assertion.passed();
577 result.
assertions.push_back(std::move(outcome));
581 result.
logs.assign(response.logs().begin(), response.logs().end());
584 for (
const auto& metric : response.metrics()) {
585 result.
metrics.emplace(metric.first, metric.second);
595 return absl::UnimplementedError(
"gRPC not available");
603 return absl::FailedPreconditionError(
604 "Not connected. Call Connect() first.");
607 yaze::test::DiscoverWidgetsRequest request;
611 request.set_type_filter(ConvertWidgetTypeFilterToProto(query.
type_filter));
618 yaze::test::DiscoverWidgetsResponse response;
619 grpc::ClientContext context;
620 grpc::Status status = stub_->DiscoverWidgets(&context, request, &response);
623 return absl::InternalError(absl::StrFormat(
"DiscoverWidgets RPC failed: %s",
624 status.error_message()));
629 if (response.generated_at_ms() > 0) {
630 result.
generated_at = OptionalTimeFromMillis(response.generated_at_ms());
633 result.
windows.reserve(response.windows_size());
634 for (
const auto& window_proto : response.windows()) {
636 window_info.
name = window_proto.name();
637 window_info.
visible = window_proto.visible();
638 window_info.
widgets.reserve(window_proto.widgets_size());
640 for (
const auto& widget_proto : window_proto.widgets()) {
642 widget.
path = widget_proto.path();
643 widget.
label = widget_proto.label();
644 widget.
type = widget_proto.type();
647 widget.
visible = widget_proto.visible();
648 widget.
enabled = widget_proto.enabled();
649 widget.
has_bounds = widget_proto.has_bounds();
651 widget.
bounds.
min_x = widget_proto.bounds().min_x();
652 widget.
bounds.
min_y = widget_proto.bounds().min_y();
653 widget.
bounds.
max_x = widget_proto.bounds().max_x();
654 widget.
bounds.
max_y = widget_proto.bounds().max_y();
658 widget.
widget_id = widget_proto.widget_id();
661 OptionalTimeFromMillis(widget_proto.last_seen_at_ms());
662 widget.
stale = widget_proto.stale();
663 window_info.
widgets.push_back(std::move(widget));
666 result.
windows.push_back(std::move(window_info));
672 return absl::UnimplementedError(
"gRPC not available");
absl::StatusOr< ReplayTestResult > ReplayTest(const std::string &script_path, bool ci_mode, const std::map< std::string, std::string > ¶meter_overrides={})
absl::StatusOr< AutomationResult > Screenshot(const std::string ®ion="full", const std::string &format="PNG")
Capture a screenshot.
absl::Status Connect()
Connect to the test harness server.
absl::StatusOr< StartRecordingResult > StartRecording(const std::string &output_path, const std::string &session_name, const std::string &description)
absl::StatusOr< AutomationResult > Ping(const std::string &message="ping")
Check if the server is reachable and responsive.
absl::StatusOr< TestStatusDetails > GetTestStatus(const std::string &test_id)
Fetch the current execution status for a harness test.
GuiAutomationClient(const std::string &server_address)
Construct a new GUI automation client.
absl::StatusOr< AutomationResult > Type(const std::string &target, const std::string &text, bool clear_first=false)
Type text into an input field.
absl::StatusOr< DiscoverWidgetsResult > DiscoverWidgets(const DiscoverWidgetsQuery &query)
absl::StatusOr< AutomationResult > Wait(const std::string &condition, int timeout_ms=5000, int poll_interval_ms=100)
Wait for a condition to be met.
absl::StatusOr< TestResultDetails > GetTestResults(const std::string &test_id, bool include_logs=false)
Retrieve detailed results for a harness test execution.
absl::StatusOr< StopRecordingResult > StopRecording(const std::string &recording_id, bool discard=false)
absl::StatusOr< AutomationResult > Assert(const std::string &condition)
Assert a GUI state condition.
absl::StatusOr< AutomationResult > Click(const std::string &target, ClickType type=ClickType::kLeft)
Click a GUI element.
absl::StatusOr< ListTestsResult > ListTests(const std::string &category_filter="", int page_size=100, const std::string &page_token="")
Enumerate harness tests with optional filtering.
std::string server_address_
TestRunStatus
Execution status codes returned by the harness.
ClickType
Type of click action to perform.
Main namespace for the application.
Individual assertion outcome within a harness test.
std::string error_message
std::string expected_value
Result of a GUI automation action.
std::chrono::milliseconds execution_time
std::string expected_value
std::vector< WidgetDescriptor > widgets
Aggregated metadata about a harness test.
std::optional< absl::Time > last_run_at
Result container for ListTests RPC.
std::string next_page_token
std::vector< HarnessTestSummary > tests
std::vector< AssertionOutcome > assertions
std::vector< std::string > logs
std::string replay_session_id
std::optional< absl::Time > started_at
std::chrono::milliseconds duration
Detailed execution results for a specific harness test.
std::optional< absl::Time > executed_at
std::map< std::string, int > metrics
std::vector< AssertionOutcome > assertions
std::vector< std::string > logs
int64_t screenshot_size_bytes
std::string failure_context
std::string screenshot_path
Detailed information about an individual test execution.
std::vector< std::string > assertion_failures
std::optional< absl::Time > completed_at
std::optional< absl::Time > started_at
std::string error_message
std::optional< absl::Time > queued_at