feat: Implement auto-capture of screenshots and diagnostics on test failures

- Added a new helper function `CaptureHarnessScreenshot` to encapsulate SDL screenshot logic.
- Updated `ImGuiTestHarnessServiceImpl::Screenshot` to utilize the new screenshot helper.
- Enhanced `TestManager::CaptureFailureContext` to automatically capture screenshots and widget state on test failures.
- Introduced new fields in the `GetTestResultsResponse` proto for screenshot path, size, failure context, and widget state.
- Updated CLI and gRPC client to expose new diagnostic fields in test results.
- Ensured that screenshots are saved in a structured directory under the system's temp directory.
- Improved logging for auto-capture events, including success and failure messages.
This commit is contained in:
scawful
2025-10-02 23:36:09 -04:00
parent c348f7f91f
commit 0447d6f8a1
11 changed files with 509 additions and 419 deletions

View File

@@ -739,6 +739,30 @@ absl::Status HandleTestResultsCommand(const std::vector<std::string>& arg_vec) {
std::cout << "[],\n";
}
std::cout << " \"screenshot_path\": ";
if (details.screenshot_path.empty()) {
std::cout << "null,\n";
} else {
std::cout << "\"" << JsonEscape(details.screenshot_path) << "\",\n";
}
std::cout << " \"screenshot_size_bytes\": "
<< details.screenshot_size_bytes << ",\n";
std::cout << " \"failure_context\": ";
if (details.failure_context.empty()) {
std::cout << "null,\n";
} else {
std::cout << "\"" << JsonEscape(details.failure_context) << "\",\n";
}
std::cout << " \"widget_state\": ";
if (details.widget_state.empty()) {
std::cout << "null,\n";
} else {
std::cout << "\"" << JsonEscape(details.widget_state) << "\",\n";
}
std::cout << " \"metrics\": ";
if (!details.metrics.empty()) {
std::cout << "{\n";
@@ -807,6 +831,27 @@ absl::Status HandleTestResultsCommand(const std::vector<std::string>& arg_vec) {
std::cout << " " << key << ": " << value << "\n";
}
}
if (details.screenshot_path.empty()) {
std::cout << "screenshot_path: null\n";
} else {
std::cout << "screenshot_path: "
<< YamlQuote(details.screenshot_path) << "\n";
}
std::cout << "screenshot_size_bytes: " << details.screenshot_size_bytes
<< "\n";
if (details.failure_context.empty()) {
std::cout << "failure_context: null\n";
} else {
std::cout << "failure_context: "
<< YamlQuote(details.failure_context) << "\n";
}
if (details.widget_state.empty()) {
std::cout << "widget_state: null\n";
} else {
std::cout << "widget_state: " << YamlQuote(details.widget_state)
<< "\n";
}
}
return absl::OkStatus();

View File

@@ -463,6 +463,11 @@ absl::StatusOr<TestResultDetails> GuiAutomationClient::GetTestResults(
result.metrics.emplace(metric.first, metric.second);
}
result.screenshot_path = response.screenshot_path();
result.screenshot_size_bytes = response.screenshot_size_bytes();
result.failure_context = response.failure_context();
result.widget_state = response.widget_state();
return result;
#else
return absl::UnimplementedError("gRPC not available");

View File

@@ -119,6 +119,10 @@ struct TestResultDetails {
std::vector<AssertionOutcome> assertions;
std::vector<std::string> logs;
std::map<std::string, int> metrics;
std::string screenshot_path;
int64_t screenshot_size_bytes = 0;
std::string failure_context;
std::string widget_state;
};
enum class WidgetTypeFilter {