yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
gui_automation_client.cc
Go to the documentation of this file.
1// gui_automation_client.cc
2// Implementation of gRPC client for YAZE GUI automation
3
5
6#include <utility>
7
8#include "absl/strings/str_cat.h"
9#include "absl/strings/str_format.h"
10#include "absl/time/time.h"
11
12namespace yaze {
13namespace cli {
14
15namespace {
16
17#ifdef YAZE_WITH_GRPC
18std::optional<absl::Time> OptionalTimeFromMillis(int64_t millis) {
19 if (millis <= 0) {
20 return std::nullopt;
21 }
22 return absl::FromUnixMillis(millis);
23}
24
25yaze::test::WidgetType ConvertWidgetTypeFilterToProto(WidgetTypeFilter filter) {
26 using ProtoType = yaze::test::WidgetType;
27 switch (filter) {
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;
49 default:
50 return ProtoType::WIDGET_TYPE_UNSPECIFIED;
51 }
52}
53
54TestRunStatus ConvertStatusProto(
55 yaze::test::GetTestStatusResponse::TestStatus status) {
56 using ProtoStatus = yaze::test::GetTestStatusResponse::TestStatus;
57 switch (status) {
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:
69 default:
71 }
72}
73
74UiSyncStateSnapshot BuildUiSyncStateSnapshot(
75 int64_t frame_id, int32_t pending_editor_actions,
76 int32_t pending_layout_actions, bool layout_rebuild_pending,
77 int32_t pending_harness_tests, int32_t queued_harness_tests,
78 int32_t running_harness_tests, int64_t sampled_at_ms, bool idle) {
80 state.frame_id = frame_id;
81 state.pending_editor_actions = pending_editor_actions;
82 state.pending_layout_actions = pending_layout_actions;
83 state.layout_rebuild_pending = layout_rebuild_pending;
84 state.pending_harness_tests = pending_harness_tests;
85 state.queued_harness_tests = queued_harness_tests;
86 state.running_harness_tests = running_harness_tests;
87 state.sampled_at = OptionalTimeFromMillis(sampled_at_ms);
88 state.idle = idle;
89 return state;
90}
91#endif // YAZE_WITH_GRPC
92
93} // namespace
94
95GuiAutomationClient::GuiAutomationClient(const std::string& server_address)
96 : server_address_(server_address) {}
97
99#ifdef YAZE_WITH_GRPC
100 auto channel =
101 grpc::CreateChannel(server_address_, grpc::InsecureChannelCredentials());
102 if (!channel) {
103 return absl::InternalError("Failed to create gRPC channel");
104 }
105
106 stub_ = yaze::test::ImGuiTestHarness::NewStub(channel);
107 if (!stub_) {
108 return absl::InternalError("Failed to create gRPC stub");
109 }
110
111 // Test connection with a ping
112 auto result = Ping("connection_test");
113 if (!result.ok()) {
114 return absl::UnavailableError(
115 absl::StrFormat("Failed to connect to test harness at %s: %s",
116 server_address_, result.status().message()));
117 }
118
119 connected_ = true;
120 return absl::OkStatus();
121#else
122 return absl::UnimplementedError(
123 "GUI automation requires YAZE_WITH_GRPC=ON at build time");
124#endif
125}
126
127absl::StatusOr<AutomationResult> GuiAutomationClient::Ping(
128 const std::string& message) {
129#ifdef YAZE_WITH_GRPC
130 if (!stub_) {
131 return absl::FailedPreconditionError(
132 "Not connected. Call Connect() first.");
133 }
134
135 yaze::test::PingRequest request;
136 request.set_message(message);
137
138 yaze::test::PingResponse response;
139 grpc::ClientContext context;
140
141 grpc::Status status = stub_->Ping(&context, request, &response);
142
143 if (!status.ok()) {
144 return absl::InternalError(
145 absl::StrFormat("Ping RPC failed: %s", status.error_message()));
146 }
147
148 AutomationResult result;
149 result.success = true;
150 result.message =
151 absl::StrFormat("Server version: %s (timestamp: %lld)",
152 response.yaze_version(), response.timestamp_ms());
153 result.execution_time = std::chrono::milliseconds(0);
154 result.test_id.clear();
155 return result;
156#else
157 return absl::UnimplementedError("gRPC not available");
158#endif
159}
160
161absl::StatusOr<ReplayTestResult> GuiAutomationClient::ReplayTest(
162 const std::string& script_path, bool ci_mode,
163 const std::map<std::string, std::string>& parameter_overrides) {
164#ifdef YAZE_WITH_GRPC
165 if (!stub_) {
166 return absl::FailedPreconditionError(
167 "Not connected. Call Connect() first.");
168 }
169
170 yaze::test::ReplayTestRequest request;
171 request.set_script_path(script_path);
172 request.set_ci_mode(ci_mode);
173 for (const auto& [key, value] : parameter_overrides) {
174 (*request.mutable_parameter_overrides())[key] = value;
175 }
176
177 yaze::test::ReplayTestResponse response;
178 grpc::ClientContext context;
179
180 grpc::Status status = stub_->ReplayTest(&context, request, &response);
181 if (!status.ok()) {
182 return absl::InternalError(
183 absl::StrCat("ReplayTest RPC failed: ", status.error_message()));
184 }
185
186 ReplayTestResult result;
187 result.success = response.success();
188 result.message = response.message();
189 result.replay_session_id = response.replay_session_id();
190 result.steps_executed = response.steps_executed();
191 result.logs.assign(response.logs().begin(), response.logs().end());
192 result.assertions.reserve(response.assertions_size());
193 for (const auto& assertion_proto : response.assertions()) {
194 AssertionOutcome assertion;
195 assertion.description = assertion_proto.description();
196 assertion.passed = assertion_proto.passed();
197 assertion.expected_value = assertion_proto.expected_value();
198 assertion.actual_value = assertion_proto.actual_value();
199 assertion.error_message = assertion_proto.error_message();
200 result.assertions.push_back(std::move(assertion));
201 }
202
203 return result;
204#else
205 return absl::UnimplementedError("gRPC not available");
206#endif
207}
208
209absl::StatusOr<StartRecordingResult> GuiAutomationClient::StartRecording(
210 const std::string& output_path, const std::string& session_name,
211 const std::string& description) {
212#ifdef YAZE_WITH_GRPC
213 if (!stub_) {
214 return absl::FailedPreconditionError(
215 "Not connected. Call Connect() first.");
216 }
217
218 yaze::test::StartRecordingRequest request;
219 request.set_output_path(output_path);
220 request.set_session_name(session_name);
221 request.set_description(description);
222
223 yaze::test::StartRecordingResponse response;
224 grpc::ClientContext context;
225 grpc::Status status = stub_->StartRecording(&context, request, &response);
226
227 if (!status.ok()) {
228 return absl::InternalError(
229 absl::StrCat("StartRecording RPC failed: ", status.error_message()));
230 }
231
233 result.success = response.success();
234 result.message = response.message();
235 result.recording_id = response.recording_id();
236 result.started_at = OptionalTimeFromMillis(response.started_at_ms());
237 return result;
238#else
239 return absl::UnimplementedError("gRPC not available");
240#endif
241}
242
243absl::StatusOr<StopRecordingResult> GuiAutomationClient::StopRecording(
244 const std::string& recording_id, bool discard) {
245#ifdef YAZE_WITH_GRPC
246 if (!stub_) {
247 return absl::FailedPreconditionError(
248 "Not connected. Call Connect() first.");
249 }
250 if (recording_id.empty()) {
251 return absl::InvalidArgumentError("recording_id must not be empty");
252 }
253
254 yaze::test::StopRecordingRequest request;
255 request.set_recording_id(recording_id);
256 request.set_discard(discard);
257
258 yaze::test::StopRecordingResponse response;
259 grpc::ClientContext context;
260 grpc::Status status = stub_->StopRecording(&context, request, &response);
261
262 if (!status.ok()) {
263 return absl::InternalError(
264 absl::StrCat("StopRecording RPC failed: ", status.error_message()));
265 }
266
267 StopRecordingResult result;
268 result.success = response.success();
269 result.message = response.message();
270 result.output_path = response.output_path();
271 result.step_count = response.step_count();
272 result.duration = std::chrono::milliseconds(response.duration_ms());
273 return result;
274#else
275 return absl::UnimplementedError("gRPC not available");
276#endif
277}
278
279absl::StatusOr<AutomationResult> GuiAutomationClient::Click(
280 const std::string& target, ClickType type, const std::string& widget_key) {
281#ifdef YAZE_WITH_GRPC
282 if (!stub_) {
283 return absl::FailedPreconditionError(
284 "Not connected. Call Connect() first.");
285 }
286
287 yaze::test::ClickRequest request;
288 request.set_target(target);
289 if (!widget_key.empty()) {
290 request.set_widget_key(widget_key);
291 }
292
293 switch (type) {
294 case ClickType::kLeft:
295 request.set_type(yaze::test::ClickRequest::CLICK_TYPE_LEFT);
296 break;
298 request.set_type(yaze::test::ClickRequest::CLICK_TYPE_RIGHT);
299 break;
301 request.set_type(yaze::test::ClickRequest::CLICK_TYPE_MIDDLE);
302 break;
304 request.set_type(yaze::test::ClickRequest::CLICK_TYPE_DOUBLE);
305 break;
306 }
307
308 yaze::test::ClickResponse response;
309 grpc::ClientContext context;
310
311 grpc::Status status = stub_->Click(&context, request, &response);
312
313 if (!status.ok()) {
314 return absl::InternalError(
315 absl::StrFormat("Click RPC failed: %s", status.error_message()));
316 }
317
318 AutomationResult result;
319 result.success = response.success();
320 result.message = response.message();
321 result.execution_time =
322 std::chrono::milliseconds(response.execution_time_ms());
323 result.test_id = response.test_id();
324 result.resolved_widget_key = response.resolved_widget_key();
325 result.resolved_path = response.resolved_path();
326 return result;
327#else
328 return absl::UnimplementedError("gRPC not available");
329#endif
330}
331
332absl::StatusOr<AutomationResult> GuiAutomationClient::Type(
333 const std::string& target, const std::string& text, bool clear_first,
334 const std::string& widget_key) {
335#ifdef YAZE_WITH_GRPC
336 if (!stub_) {
337 return absl::FailedPreconditionError(
338 "Not connected. Call Connect() first.");
339 }
340
341 yaze::test::TypeRequest request;
342 request.set_target(target);
343 if (!widget_key.empty()) {
344 request.set_widget_key(widget_key);
345 }
346 request.set_text(text);
347 request.set_clear_first(clear_first);
348
349 yaze::test::TypeResponse response;
350 grpc::ClientContext context;
351
352 grpc::Status status = stub_->Type(&context, request, &response);
353
354 if (!status.ok()) {
355 return absl::InternalError(
356 absl::StrFormat("Type RPC failed: %s", status.error_message()));
357 }
358
359 AutomationResult result;
360 result.success = response.success();
361 result.message = response.message();
362 result.execution_time =
363 std::chrono::milliseconds(response.execution_time_ms());
364 result.test_id = response.test_id();
365 result.resolved_widget_key = response.resolved_widget_key();
366 result.resolved_path = response.resolved_path();
367 return result;
368#else
369 return absl::UnimplementedError("gRPC not available");
370#endif
371}
372
373absl::StatusOr<AutomationResult> GuiAutomationClient::Wait(
374 const std::string& condition, int timeout_ms, int poll_interval_ms,
375 const std::string& widget_key) {
376#ifdef YAZE_WITH_GRPC
377 if (!stub_) {
378 return absl::FailedPreconditionError(
379 "Not connected. Call Connect() first.");
380 }
381
382 yaze::test::WaitRequest request;
383 request.set_condition(condition);
384 if (!widget_key.empty()) {
385 request.set_widget_key(widget_key);
386 }
387 request.set_timeout_ms(timeout_ms);
388 request.set_poll_interval_ms(poll_interval_ms);
389
390 yaze::test::WaitResponse response;
391 grpc::ClientContext context;
392
393 grpc::Status status = stub_->Wait(&context, request, &response);
394
395 if (!status.ok()) {
396 return absl::InternalError(
397 absl::StrFormat("Wait RPC failed: %s", status.error_message()));
398 }
399
400 AutomationResult result;
401 result.success = response.success();
402 result.message = response.message();
403 result.execution_time = std::chrono::milliseconds(response.elapsed_ms());
404 result.test_id = response.test_id();
405 result.resolved_widget_key = response.resolved_widget_key();
406 result.resolved_path = response.resolved_path();
407 return result;
408#else
409 return absl::UnimplementedError("gRPC not available");
410#endif
411}
412
413absl::StatusOr<AutomationResult> GuiAutomationClient::Assert(
414 const std::string& condition, const std::string& widget_key) {
415#ifdef YAZE_WITH_GRPC
416 if (!stub_) {
417 return absl::FailedPreconditionError(
418 "Not connected. Call Connect() first.");
419 }
420
421 yaze::test::AssertRequest request;
422 request.set_condition(condition);
423 if (!widget_key.empty()) {
424 request.set_widget_key(widget_key);
425 }
426
427 yaze::test::AssertResponse response;
428 grpc::ClientContext context;
429
430 grpc::Status status = stub_->Assert(&context, request, &response);
431
432 if (!status.ok()) {
433 return absl::InternalError(
434 absl::StrFormat("Assert RPC failed: %s", status.error_message()));
435 }
436
437 AutomationResult result;
438 result.success = response.success();
439 result.message = response.message();
440 result.actual_value = response.actual_value();
441 result.expected_value = response.expected_value();
442 result.execution_time = std::chrono::milliseconds(0);
443 result.test_id = response.test_id();
444 result.resolved_widget_key = response.resolved_widget_key();
445 result.resolved_path = response.resolved_path();
446 return result;
447#else
448 return absl::UnimplementedError("gRPC not available");
449#endif
450}
451
452absl::StatusOr<UiSyncStateSnapshot> GuiAutomationClient::FlushUiActions(
453 int timeout_ms) {
454#ifdef YAZE_WITH_GRPC
455 if (!stub_) {
456 return absl::FailedPreconditionError(
457 "Not connected. Call Connect() first.");
458 }
459
460 yaze::test::FlushUiActionsRequest request;
461 request.set_timeout_ms(timeout_ms);
462
463 yaze::test::FlushUiActionsResponse response;
464 grpc::ClientContext context;
465 grpc::Status status = stub_->FlushUiActions(&context, request, &response);
466 if (!status.ok()) {
467 return absl::InternalError(absl::StrFormat("FlushUiActions RPC failed: %s",
468 status.error_message()));
469 }
470
471 const bool idle = response.pending_editor_actions() == 0 &&
472 response.pending_layout_actions() == 0 &&
473 !response.layout_rebuild_pending() &&
474 response.pending_harness_tests() == 0;
475 return BuildUiSyncStateSnapshot(
476 response.frame_id(), response.pending_editor_actions(),
477 response.pending_layout_actions(), response.layout_rebuild_pending(),
478 response.pending_harness_tests(), response.queued_harness_tests(),
479 response.running_harness_tests(), /*sampled_at_ms=*/0, idle);
480#else
481 (void)timeout_ms;
482 return absl::UnimplementedError("gRPC not available");
483#endif
484}
485
486absl::StatusOr<WaitForIdleResult> GuiAutomationClient::WaitForIdle(
487 int timeout_ms, int stable_frames, bool flush_first, int poll_interval_ms) {
488#ifdef YAZE_WITH_GRPC
489 if (!stub_) {
490 return absl::FailedPreconditionError(
491 "Not connected. Call Connect() first.");
492 }
493
494 yaze::test::WaitForIdleRequest request;
495 request.set_timeout_ms(timeout_ms);
496 request.set_stable_frames(stable_frames);
497 request.set_flush_first(flush_first);
498 request.set_poll_interval_ms(poll_interval_ms);
499
500 yaze::test::WaitForIdleResponse response;
501 grpc::ClientContext context;
502 grpc::Status status = stub_->WaitForIdle(&context, request, &response);
503 if (!status.ok()) {
504 return absl::InternalError(
505 absl::StrFormat("WaitForIdle RPC failed: %s", status.error_message()));
506 }
507
508 WaitForIdleResult result;
509 result.success = response.success();
510 result.message = response.message();
511 result.elapsed = std::chrono::milliseconds(response.elapsed_ms());
512 result.last_frame_id = response.last_frame_id();
513 result.stable_frames_observed = response.stable_frames_observed();
514 const bool idle = response.pending_editor_actions() == 0 &&
515 response.pending_layout_actions() == 0 &&
516 !response.layout_rebuild_pending() &&
517 response.pending_harness_tests() == 0;
518 result.state = BuildUiSyncStateSnapshot(
519 response.last_frame_id(), response.pending_editor_actions(),
520 response.pending_layout_actions(), response.layout_rebuild_pending(),
521 response.pending_harness_tests(), response.queued_harness_tests(),
522 response.running_harness_tests(), /*sampled_at_ms=*/0, idle);
523 result.timeout_reason = response.timeout_reason();
524 return result;
525#else
526 (void)timeout_ms;
527 (void)stable_frames;
528 (void)flush_first;
529 (void)poll_interval_ms;
530 return absl::UnimplementedError("gRPC not available");
531#endif
532}
533
534absl::StatusOr<UiSyncStateSnapshot> GuiAutomationClient::GetUiSyncState() {
535#ifdef YAZE_WITH_GRPC
536 if (!stub_) {
537 return absl::FailedPreconditionError(
538 "Not connected. Call Connect() first.");
539 }
540
541 yaze::test::GetUiSyncStateRequest request;
542 yaze::test::GetUiSyncStateResponse response;
543 grpc::ClientContext context;
544 grpc::Status status = stub_->GetUiSyncState(&context, request, &response);
545 if (!status.ok()) {
546 return absl::InternalError(absl::StrFormat("GetUiSyncState RPC failed: %s",
547 status.error_message()));
548 }
549
550 return BuildUiSyncStateSnapshot(
551 response.frame_id(), response.pending_editor_actions(),
552 response.pending_layout_actions(), response.layout_rebuild_pending(),
553 response.pending_harness_tests(), response.queued_harness_tests(),
554 response.running_harness_tests(), response.sampled_at_ms(),
555 response.idle());
556#else
557 return absl::UnimplementedError("gRPC not available");
558#endif
559}
560
561absl::StatusOr<AutomationResult> GuiAutomationClient::Screenshot(
562 const std::string& region, const std::string& format) {
563#ifdef YAZE_WITH_GRPC
564 if (!stub_) {
565 return absl::FailedPreconditionError(
566 "Not connected. Call Connect() first.");
567 }
568
569 yaze::test::ScreenshotRequest request;
570 request.set_window_title(""); // Empty = main window
571 // No hardcoded path here - let server decide default unless provided
572 request.set_format(
573 yaze::test::ScreenshotRequest::IMAGE_FORMAT_BMP); // Match SDL_SaveBMP
574
575 yaze::test::ScreenshotResponse response;
576 grpc::ClientContext context;
577
578 grpc::Status status = stub_->Screenshot(&context, request, &response);
579
580 if (!status.ok()) {
581 return absl::InternalError(
582 absl::StrFormat("Screenshot RPC failed: %s", status.error_message()));
583 }
584
585 AutomationResult result;
586 result.success = response.success();
587 result.message = response.message();
588 result.execution_time = std::chrono::milliseconds(0);
589 result.test_id.clear();
590 return result;
591#else
592 return absl::UnimplementedError("gRPC not available");
593#endif
594}
595
596absl::StatusOr<TestStatusDetails> GuiAutomationClient::GetTestStatus(
597 const std::string& test_id) {
598#ifdef YAZE_WITH_GRPC
599 if (!stub_) {
600 return absl::FailedPreconditionError(
601 "Not connected. Call Connect() first.");
602 }
603
604 yaze::test::GetTestStatusRequest request;
605 request.set_test_id(test_id);
606
607 yaze::test::GetTestStatusResponse response;
608 grpc::ClientContext context;
609
610 grpc::Status status = stub_->GetTestStatus(&context, request, &response);
611
612 if (!status.ok()) {
613 return absl::InternalError(absl::StrFormat("GetTestStatus RPC failed: %s",
614 status.error_message()));
615 }
616
617 TestStatusDetails details;
618 details.test_id = test_id;
619 details.status = ConvertStatusProto(response.status());
620 details.queued_at = OptionalTimeFromMillis(response.queued_at_ms());
621 details.started_at = OptionalTimeFromMillis(response.started_at_ms());
622 details.completed_at = OptionalTimeFromMillis(response.completed_at_ms());
623 details.execution_time_ms = response.execution_time_ms();
624 details.error_message = response.error_message();
625 details.assertion_failures.assign(response.assertion_failures().begin(),
626 response.assertion_failures().end());
627 return details;
628#else
629 return absl::UnimplementedError("gRPC not available");
630#endif
631}
632
633absl::StatusOr<ListTestsResult> GuiAutomationClient::ListTests(
634 const std::string& category_filter, int page_size,
635 const std::string& page_token) {
636#ifdef YAZE_WITH_GRPC
637 if (!stub_) {
638 return absl::FailedPreconditionError(
639 "Not connected. Call Connect() first.");
640 }
641
642 yaze::test::ListTestsRequest request;
643 if (!category_filter.empty()) {
644 request.set_category_filter(category_filter);
645 }
646 if (page_size > 0) {
647 request.set_page_size(page_size);
648 }
649 if (!page_token.empty()) {
650 request.set_page_token(page_token);
651 }
652
653 yaze::test::ListTestsResponse response;
654 grpc::ClientContext context;
655
656 grpc::Status status = stub_->ListTests(&context, request, &response);
657
658 if (!status.ok()) {
659 return absl::InternalError(
660 absl::StrFormat("ListTests RPC failed: %s", status.error_message()));
661 }
662
663 ListTestsResult result;
664 result.total_count = response.total_count();
665 result.next_page_token = response.next_page_token();
666 result.tests.reserve(response.tests_size());
667
668 for (const auto& test_info : response.tests()) {
669 HarnessTestSummary summary;
670 summary.test_id = test_info.test_id();
671 summary.name = test_info.name();
672 summary.category = test_info.category();
673 summary.last_run_at =
674 OptionalTimeFromMillis(test_info.last_run_timestamp_ms());
675 summary.total_runs = test_info.total_runs();
676 summary.pass_count = test_info.pass_count();
677 summary.fail_count = test_info.fail_count();
678 summary.average_duration_ms = test_info.average_duration_ms();
679 result.tests.push_back(std::move(summary));
680 }
681
682 return result;
683#else
684 return absl::UnimplementedError("gRPC not available");
685#endif
686}
687
688absl::StatusOr<TestResultDetails> GuiAutomationClient::GetTestResults(
689 const std::string& test_id, bool include_logs) {
690#ifdef YAZE_WITH_GRPC
691 if (!stub_) {
692 return absl::FailedPreconditionError(
693 "Not connected. Call Connect() first.");
694 }
695
696 yaze::test::GetTestResultsRequest request;
697 request.set_test_id(test_id);
698 request.set_include_logs(include_logs);
699
700 yaze::test::GetTestResultsResponse response;
701 grpc::ClientContext context;
702
703 grpc::Status status = stub_->GetTestResults(&context, request, &response);
704
705 if (!status.ok()) {
706 return absl::InternalError(absl::StrFormat("GetTestResults RPC failed: %s",
707 status.error_message()));
708 }
709
710 TestResultDetails result;
711 result.test_id = test_id;
712 result.success = response.success();
713 result.test_name = response.test_name();
714 result.category = response.category();
715 result.executed_at = OptionalTimeFromMillis(response.executed_at_ms());
716 result.duration_ms = response.duration_ms();
717
718 result.assertions.reserve(response.assertions_size());
719 for (const auto& assertion : response.assertions()) {
720 AssertionOutcome outcome;
721 outcome.description = assertion.description();
722 outcome.passed = assertion.passed();
723 outcome.expected_value = assertion.expected_value();
724 outcome.actual_value = assertion.actual_value();
725 outcome.error_message = assertion.error_message();
726 result.assertions.push_back(std::move(outcome));
727 }
728
729 if (include_logs) {
730 result.logs.assign(response.logs().begin(), response.logs().end());
731 }
732
733 for (const auto& metric : response.metrics()) {
734 result.metrics.emplace(metric.first, metric.second);
735 }
736
737 result.screenshot_path = response.screenshot_path();
738 result.screenshot_size_bytes = response.screenshot_size_bytes();
739 result.failure_context = response.failure_context();
740 result.widget_state = response.widget_state();
741
742 return result;
743#else
744 return absl::UnimplementedError("gRPC not available");
745#endif
746}
747
748absl::StatusOr<DiscoverWidgetsResult> GuiAutomationClient::DiscoverWidgets(
749 const DiscoverWidgetsQuery& query) {
750#ifdef YAZE_WITH_GRPC
751 if (!stub_) {
752 return absl::FailedPreconditionError(
753 "Not connected. Call Connect() first.");
754 }
755
756 yaze::test::DiscoverWidgetsRequest request;
757 if (!query.window_filter.empty()) {
758 request.set_window_filter(query.window_filter);
759 }
760 request.set_type_filter(ConvertWidgetTypeFilterToProto(query.type_filter));
761 if (!query.path_prefix.empty()) {
762 request.set_path_prefix(query.path_prefix);
763 }
764 request.set_include_invisible(query.include_invisible);
765 request.set_include_disabled(query.include_disabled);
766
767 yaze::test::DiscoverWidgetsResponse response;
768 grpc::ClientContext context;
769 grpc::Status status = stub_->DiscoverWidgets(&context, request, &response);
770
771 if (!status.ok()) {
772 return absl::InternalError(absl::StrFormat("DiscoverWidgets RPC failed: %s",
773 status.error_message()));
774 }
775
777 result.total_widgets = response.total_widgets();
778 if (response.generated_at_ms() > 0) {
779 result.generated_at = OptionalTimeFromMillis(response.generated_at_ms());
780 }
781
782 result.windows.reserve(response.windows_size());
783 for (const auto& window_proto : response.windows()) {
784 DiscoveredWindowInfo window_info;
785 window_info.name = window_proto.name();
786 window_info.visible = window_proto.visible();
787 window_info.widgets.reserve(window_proto.widgets_size());
788
789 for (const auto& widget_proto : window_proto.widgets()) {
790 WidgetDescriptor widget;
791 widget.path = widget_proto.path();
792 widget.widget_key = widget_proto.widget_key();
793 widget.legacy_path = widget_proto.legacy_path();
794 widget.alias_of = widget_proto.alias_of();
795 widget.label = widget_proto.label();
796 widget.type = widget_proto.type();
797 widget.description = widget_proto.description();
798 widget.suggested_action = widget_proto.suggested_action();
799 widget.visible = widget_proto.visible();
800 widget.enabled = widget_proto.enabled();
801 widget.has_bounds = widget_proto.has_bounds();
802 if (widget.has_bounds) {
803 widget.bounds.min_x = widget_proto.bounds().min_x();
804 widget.bounds.min_y = widget_proto.bounds().min_y();
805 widget.bounds.max_x = widget_proto.bounds().max_x();
806 widget.bounds.max_y = widget_proto.bounds().max_y();
807 } else {
808 widget.bounds = WidgetBoundingBox();
809 }
810 widget.widget_id = widget_proto.widget_id();
811 widget.last_seen_frame = widget_proto.last_seen_frame();
812 widget.last_seen_at =
813 OptionalTimeFromMillis(widget_proto.last_seen_at_ms());
814 widget.stale = widget_proto.stale();
815 window_info.widgets.push_back(std::move(widget));
816 }
817
818 result.windows.push_back(std::move(window_info));
819 }
820
821 return result;
822#else
823 (void)query;
824 return absl::UnimplementedError("gRPC not available");
825#endif
826}
827
828} // namespace cli
829} // namespace yaze
absl::StatusOr< ReplayTestResult > ReplayTest(const std::string &script_path, bool ci_mode, const std::map< std::string, std::string > &parameter_overrides={})
absl::StatusOr< AutomationResult > Type(const std::string &target, const std::string &text, bool clear_first=false, const std::string &widget_key="")
Type text into an input field.
absl::StatusOr< AutomationResult > Screenshot(const std::string &region="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.
absl::StatusOr< UiSyncStateSnapshot > GetUiSyncState()
Fetch low-level UI synchronization counters for diagnostics.
GuiAutomationClient(const std::string &server_address)
Construct a new GUI automation client.
absl::StatusOr< DiscoverWidgetsResult > DiscoverWidgets(const DiscoverWidgetsQuery &query)
absl::StatusOr< AutomationResult > Assert(const std::string &condition, const std::string &widget_key="")
Assert a GUI state condition.
absl::StatusOr< TestResultDetails > GetTestResults(const std::string &test_id, bool include_logs=false)
Retrieve detailed results for a harness test execution.
absl::StatusOr< UiSyncStateSnapshot > FlushUiActions(int timeout_ms=2000)
Force a frame-boundary flush for deferred UI actions.
absl::StatusOr< StopRecordingResult > StopRecording(const std::string &recording_id, bool discard=false)
absl::StatusOr< WaitForIdleResult > WaitForIdle(int timeout_ms=5000, int stable_frames=2, bool flush_first=true, int poll_interval_ms=25)
Wait until UI reaches deterministic idle criteria.
absl::StatusOr< AutomationResult > Wait(const std::string &condition, int timeout_ms=5000, int poll_interval_ms=100, const std::string &widget_key="")
Wait for a condition to be met.
absl::StatusOr< AutomationResult > Click(const std::string &target, ClickType type=ClickType::kLeft, const std::string &widget_key="")
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.
TestRunStatus
Execution status codes returned by the harness.
ClickType
Type of click action to perform.
Individual assertion outcome within a harness test.
Result of a GUI automation action.
std::chrono::milliseconds execution_time
std::vector< DiscoveredWindowInfo > windows
std::optional< absl::Time > generated_at
std::vector< WidgetDescriptor > widgets
Aggregated metadata about a harness test.
std::optional< absl::Time > last_run_at
Result container for ListTests RPC.
std::vector< HarnessTestSummary > tests
std::vector< AssertionOutcome > assertions
std::vector< std::string > logs
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
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::optional< absl::Time > queued_at
std::optional< absl::Time > sampled_at
std::chrono::milliseconds elapsed
std::optional< absl::Time > last_seen_at