feat: Implement widget discovery feature in GUI automation
- Added `DiscoverWidgets` RPC to the ImGuiTestHarness service for enumerating GUI widgets. - Introduced `WidgetDiscoveryService` to handle widget collection and filtering based on various criteria. - Updated `agent gui discover` command to support new options for filtering and output formats. - Enhanced `GuiAutomationClient` to facilitate widget discovery requests and responses. - Added necessary protobuf messages for widget discovery in `imgui_test_harness.proto`. - Updated CLI command handling to include new GUI discovery functionality. - Improved documentation for the `agent gui discover` command with examples and output formats.
This commit is contained in:
@@ -21,6 +21,35 @@ std::optional<absl::Time> OptionalTimeFromMillis(int64_t millis) {
|
||||
return absl::FromUnixMillis(millis);
|
||||
}
|
||||
|
||||
yaze::test::WidgetType ConvertWidgetTypeFilterToProto(WidgetTypeFilter filter) {
|
||||
using ProtoType = yaze::test::WidgetType;
|
||||
switch (filter) {
|
||||
case WidgetTypeFilter::kAll:
|
||||
return ProtoType::WIDGET_TYPE_ALL;
|
||||
case WidgetTypeFilter::kButton:
|
||||
return ProtoType::WIDGET_TYPE_BUTTON;
|
||||
case WidgetTypeFilter::kInput:
|
||||
return ProtoType::WIDGET_TYPE_INPUT;
|
||||
case WidgetTypeFilter::kMenu:
|
||||
return ProtoType::WIDGET_TYPE_MENU;
|
||||
case WidgetTypeFilter::kTab:
|
||||
return ProtoType::WIDGET_TYPE_TAB;
|
||||
case WidgetTypeFilter::kCheckbox:
|
||||
return ProtoType::WIDGET_TYPE_CHECKBOX;
|
||||
case WidgetTypeFilter::kSlider:
|
||||
return ProtoType::WIDGET_TYPE_SLIDER;
|
||||
case WidgetTypeFilter::kCanvas:
|
||||
return ProtoType::WIDGET_TYPE_CANVAS;
|
||||
case WidgetTypeFilter::kSelectable:
|
||||
return ProtoType::WIDGET_TYPE_SELECTABLE;
|
||||
case WidgetTypeFilter::kOther:
|
||||
return ProtoType::WIDGET_TYPE_OTHER;
|
||||
case WidgetTypeFilter::kUnspecified:
|
||||
default:
|
||||
return ProtoType::WIDGET_TYPE_UNSPECIFIED;
|
||||
}
|
||||
}
|
||||
|
||||
TestRunStatus ConvertStatusProto(
|
||||
yaze::test::GetTestStatusResponse::Status status) {
|
||||
using ProtoStatus = yaze::test::GetTestStatusResponse::Status;
|
||||
@@ -440,5 +469,73 @@ absl::StatusOr<TestResultDetails> GuiAutomationClient::GetTestResults(
|
||||
#endif
|
||||
}
|
||||
|
||||
absl::StatusOr<DiscoverWidgetsResult> GuiAutomationClient::DiscoverWidgets(
|
||||
const DiscoverWidgetsQuery& query) {
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
if (!stub_) {
|
||||
return absl::FailedPreconditionError("Not connected. Call Connect() first.");
|
||||
}
|
||||
|
||||
yaze::test::DiscoverWidgetsRequest request;
|
||||
if (!query.window_filter.empty()) {
|
||||
request.set_window_filter(query.window_filter);
|
||||
}
|
||||
request.set_type_filter(ConvertWidgetTypeFilterToProto(query.type_filter));
|
||||
if (!query.path_prefix.empty()) {
|
||||
request.set_path_prefix(query.path_prefix);
|
||||
}
|
||||
request.set_include_invisible(query.include_invisible);
|
||||
request.set_include_disabled(query.include_disabled);
|
||||
|
||||
yaze::test::DiscoverWidgetsResponse response;
|
||||
grpc::ClientContext context;
|
||||
grpc::Status status = stub_->DiscoverWidgets(&context, request, &response);
|
||||
|
||||
if (!status.ok()) {
|
||||
return absl::InternalError(
|
||||
absl::StrFormat("DiscoverWidgets RPC failed: %s",
|
||||
status.error_message()));
|
||||
}
|
||||
|
||||
DiscoverWidgetsResult result;
|
||||
result.total_widgets = response.total_widgets();
|
||||
if (response.generated_at_ms() > 0) {
|
||||
result.generated_at = OptionalTimeFromMillis(response.generated_at_ms());
|
||||
}
|
||||
|
||||
result.windows.reserve(response.windows_size());
|
||||
for (const auto& window_proto : response.windows()) {
|
||||
DiscoveredWindowInfo window_info;
|
||||
window_info.name = window_proto.name();
|
||||
window_info.visible = window_proto.visible();
|
||||
window_info.widgets.reserve(window_proto.widgets_size());
|
||||
|
||||
for (const auto& widget_proto : window_proto.widgets()) {
|
||||
WidgetDescriptor widget;
|
||||
widget.path = widget_proto.path();
|
||||
widget.label = widget_proto.label();
|
||||
widget.type = widget_proto.type();
|
||||
widget.description = widget_proto.description();
|
||||
widget.suggested_action = widget_proto.suggested_action();
|
||||
widget.visible = widget_proto.visible();
|
||||
widget.enabled = widget_proto.enabled();
|
||||
widget.bounds.min_x = widget_proto.bounds().min_x();
|
||||
widget.bounds.min_y = widget_proto.bounds().min_y();
|
||||
widget.bounds.max_x = widget_proto.bounds().max_x();
|
||||
widget.bounds.max_y = widget_proto.bounds().max_y();
|
||||
widget.widget_id = widget_proto.widget_id();
|
||||
window_info.widgets.push_back(std::move(widget));
|
||||
}
|
||||
|
||||
result.windows.push_back(std::move(window_info));
|
||||
}
|
||||
|
||||
return result;
|
||||
#else
|
||||
(void)query;
|
||||
return absl::UnimplementedError("gRPC not available");
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "absl/time/time.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@@ -120,6 +121,59 @@ struct TestResultDetails {
|
||||
std::map<std::string, int> metrics;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
struct DiscoveredWindowInfo {
|
||||
std::string name;
|
||||
bool visible = true;
|
||||
std::vector<WidgetDescriptor> 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<DiscoveredWindowInfo> windows;
|
||||
int total_widgets = 0;
|
||||
std::optional<absl::Time> generated_at;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Client for automating YAZE GUI through gRPC
|
||||
*
|
||||
@@ -225,6 +279,9 @@ class GuiAutomationClient {
|
||||
absl::StatusOr<TestResultDetails> GetTestResults(const std::string& test_id,
|
||||
bool include_logs = false);
|
||||
|
||||
absl::StatusOr<DiscoverWidgetsResult> DiscoverWidgets(
|
||||
const DiscoverWidgetsQuery& query);
|
||||
|
||||
/**
|
||||
* @brief Check if client is connected
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user