feat: Enhance widget state capture with fallback diagnostics and update test harness interactions
This commit is contained in:
@@ -112,7 +112,7 @@ run_test "Ping (Health Check)" "Ping" '{"message":"test"}'
|
|||||||
|
|
||||||
# 2. Click - Menu Item (Open Overworld Editor)
|
# 2. Click - Menu Item (Open Overworld Editor)
|
||||||
# Note: Menu items in YAZE use format "menuitem:<Icon> Name"
|
# Note: Menu items in YAZE use format "menuitem:<Icon> Name"
|
||||||
run_test "Click (Open Overworld Editor)" "Click" '{"target":"menuitem: Overworld Editor","type":"LEFT"}'
|
run_test "Click (Open Overworld Editor)" "Click" '{"target":"menuitem: Overworld Editor","type":"CLICK_TYPE_LEFT"}'
|
||||||
|
|
||||||
# 3. Wait - Window Visible (Overworld Editor should open)
|
# 3. Wait - Window Visible (Overworld Editor should open)
|
||||||
run_test "Wait (Overworld Editor Window)" "Wait" '{"condition":"window_visible:Overworld","timeout_ms":15000,"poll_interval_ms":100}'
|
run_test "Wait (Overworld Editor Window)" "Wait" '{"condition":"window_visible:Overworld","timeout_ms":15000,"poll_interval_ms":100}'
|
||||||
@@ -121,7 +121,7 @@ run_test "Wait (Overworld Editor Window)" "Wait" '{"condition":"window_visible:O
|
|||||||
run_test "Assert (Overworld Editor Visible)" "Assert" '{"condition":"visible:Overworld"}'
|
run_test "Assert (Overworld Editor Visible)" "Assert" '{"condition":"visible:Overworld"}'
|
||||||
|
|
||||||
# 5. Click - Another menu item (Dungeon Editor)
|
# 5. Click - Another menu item (Dungeon Editor)
|
||||||
run_test "Click (Open Dungeon Editor)" "Click" '{"target":"menuitem: Dungeon Editor","type":"LEFT"}'
|
run_test "Click (Open Dungeon Editor)" "Click" '{"target":"menuitem: Dungeon Editor","type":"CLICK_TYPE_LEFT"}'
|
||||||
|
|
||||||
# 6. Screenshot - Not Implemented (stub)
|
# 6. Screenshot - Not Implemented (stub)
|
||||||
echo -e "${YELLOW}Test 6: Screenshot (Not Implemented - Stub)${NC}"
|
echo -e "${YELLOW}Test 6: Screenshot (Not Implemented - Stub)${NC}"
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
#include "app/core/widget_state_capture.h"
|
#include "app/core/widget_state_capture.h"
|
||||||
|
|
||||||
#include "absl/strings/str_format.h"
|
#include "absl/strings/str_format.h"
|
||||||
|
#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
|
||||||
|
#include "imgui.h"
|
||||||
|
#include "imgui_internal.h"
|
||||||
|
#else
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#endif
|
||||||
#include "nlohmann/json.hpp"
|
#include "nlohmann/json.hpp"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
@@ -9,6 +15,7 @@ namespace core {
|
|||||||
std::string CaptureWidgetState() {
|
std::string CaptureWidgetState() {
|
||||||
WidgetState state;
|
WidgetState state;
|
||||||
|
|
||||||
|
#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
|
||||||
// Check if ImGui context is available
|
// Check if ImGui context is available
|
||||||
ImGuiContext* ctx = ImGui::GetCurrentContext();
|
ImGuiContext* ctx = ImGui::GetCurrentContext();
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
@@ -70,6 +77,13 @@ std::string CaptureWidgetState() {
|
|||||||
state.shift_pressed = io.KeyShift;
|
state.shift_pressed = io.KeyShift;
|
||||||
state.alt_pressed = io.KeyAlt;
|
state.alt_pressed = io.KeyAlt;
|
||||||
|
|
||||||
|
#else
|
||||||
|
// When UI test engine / ImGui internals aren't available, provide a minimal
|
||||||
|
// payload so downstream systems still receive structured JSON. This keeps
|
||||||
|
// builds that exclude the UI test engine (e.g., Windows release) working.
|
||||||
|
return R"({"warning": "Widget state capture unavailable (UI test engine disabled)"})";
|
||||||
|
#endif
|
||||||
|
|
||||||
return SerializeWidgetStateToJson(state);
|
return SerializeWidgetStateToJson(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,12 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
|
||||||
|
#include "imgui.h"
|
||||||
|
#include "imgui_internal.h"
|
||||||
|
#else
|
||||||
#include "imgui/imgui.h"
|
#include "imgui/imgui.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace core {
|
namespace core {
|
||||||
@@ -36,6 +41,8 @@ struct WidgetState {
|
|||||||
|
|
||||||
// Capture current ImGui widget state for debugging
|
// Capture current ImGui widget state for debugging
|
||||||
// Returns JSON-formatted string representing the widget hierarchy and state
|
// Returns JSON-formatted string representing the widget hierarchy and state
|
||||||
|
// When ImGui internals are unavailable (UI test engine disabled), returns a
|
||||||
|
// short diagnostic JSON payload.
|
||||||
std::string CaptureWidgetState();
|
std::string CaptureWidgetState();
|
||||||
|
|
||||||
// Serialize widget state to JSON format
|
// Serialize widget state to JSON format
|
||||||
|
|||||||
@@ -14,7 +14,12 @@
|
|||||||
#include "app/core/platform/file_dialog.h"
|
#include "app/core/platform/file_dialog.h"
|
||||||
#include "app/gfx/arena.h"
|
#include "app/gfx/arena.h"
|
||||||
#include "app/gui/icons.h"
|
#include "app/gui/icons.h"
|
||||||
|
#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
|
||||||
#include "imgui.h"
|
#include "imgui.h"
|
||||||
|
#include "imgui_internal.h"
|
||||||
|
#else
|
||||||
|
#include "imgui.h"
|
||||||
|
#endif
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
// Forward declaration to avoid circular dependency
|
// Forward declaration to avoid circular dependency
|
||||||
@@ -1479,6 +1484,11 @@ void TestManager::MarkHarnessTestCompleted(
|
|||||||
const std::vector<std::string>& assertion_failures,
|
const std::vector<std::string>& assertion_failures,
|
||||||
const std::vector<std::string>& logs,
|
const std::vector<std::string>& logs,
|
||||||
const std::map<std::string, int32_t>& metrics) {
|
const std::map<std::string, int32_t>& metrics) {
|
||||||
|
bool capture_failure_context =
|
||||||
|
status == HarnessTestStatus::kFailed ||
|
||||||
|
status == HarnessTestStatus::kTimeout;
|
||||||
|
|
||||||
|
{
|
||||||
absl::MutexLock lock(&harness_history_mutex_);
|
absl::MutexLock lock(&harness_history_mutex_);
|
||||||
|
|
||||||
auto it = harness_history_.find(test_id);
|
auto it = harness_history_.find(test_id);
|
||||||
@@ -1504,16 +1514,6 @@ void TestManager::MarkHarnessTestCompleted(
|
|||||||
execution.metrics.insert(metrics.begin(), metrics.end());
|
execution.metrics.insert(metrics.begin(), metrics.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
// IT-08b: Auto-capture failure context for failed/timeout tests
|
|
||||||
if (status == HarnessTestStatus::kFailed ||
|
|
||||||
status == HarnessTestStatus::kTimeout) {
|
|
||||||
// Release lock before calling CaptureFailureContext to avoid deadlock
|
|
||||||
// TODO: FIXME
|
|
||||||
// lock.Release();
|
|
||||||
CaptureFailureContext(test_id);
|
|
||||||
// lock.Acquire();
|
|
||||||
}
|
|
||||||
|
|
||||||
HarnessAggregate& aggregate = harness_aggregates_[execution.name];
|
HarnessAggregate& aggregate = harness_aggregates_[execution.name];
|
||||||
if (aggregate.category.empty()) {
|
if (aggregate.category.empty()) {
|
||||||
aggregate.category = execution.category;
|
aggregate.category = execution.category;
|
||||||
@@ -1530,6 +1530,11 @@ void TestManager::MarkHarnessTestCompleted(
|
|||||||
aggregate.latest_execution = execution;
|
aggregate.latest_execution = execution;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (capture_failure_context) {
|
||||||
|
CaptureFailureContext(test_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TestManager::AppendHarnessTestLog(const std::string& test_id,
|
void TestManager::AppendHarnessTestLog(const std::string& test_id,
|
||||||
const std::string& log_entry) {
|
const std::string& log_entry) {
|
||||||
absl::MutexLock lock(&harness_history_mutex_);
|
absl::MutexLock lock(&harness_history_mutex_);
|
||||||
@@ -1642,15 +1647,31 @@ void TestManager::CaptureFailureContext(const std::string& test_id) {
|
|||||||
HarnessTestExecution& execution = it->second;
|
HarnessTestExecution& execution = it->second;
|
||||||
|
|
||||||
// 1. Capture execution context (frame count, active window, etc.)
|
// 1. Capture execution context (frame count, active window, etc.)
|
||||||
if (ImGui::GetCurrentContext() != nullptr) {
|
ImGuiContext* ctx = ImGui::GetCurrentContext();
|
||||||
// TODO: FIXME
|
if (ctx != nullptr) {
|
||||||
// ImGuiWindow* current_window = ImGui::GetCurrentWindow();
|
#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
|
||||||
// const char* window_name = current_window ? current_window->Name : "none";
|
ImGuiWindow* current_window = ctx->CurrentWindow;
|
||||||
// ImGuiID active_id = ImGui::GetActiveID();
|
ImGuiWindow* nav_window = ctx->NavWindow;
|
||||||
|
ImGuiWindow* hovered_window = ctx->HoveredWindow;
|
||||||
|
|
||||||
// execution.failure_context =
|
const char* current_name =
|
||||||
// absl::StrFormat("Frame: %d, Active Window: %s, Focused Widget: 0x%08X",
|
(current_window && current_window->Name) ? current_window->Name : "none";
|
||||||
// ImGui::GetFrameCount(), window_name, active_id);
|
const char* nav_name =
|
||||||
|
(nav_window && nav_window->Name) ? nav_window->Name : "none";
|
||||||
|
const char* hovered_name =
|
||||||
|
(hovered_window && hovered_window->Name) ? hovered_window->Name : "none";
|
||||||
|
|
||||||
|
ImGuiID active_id = ImGui::GetActiveID();
|
||||||
|
ImGuiID hovered_id = ImGui::GetHoveredID();
|
||||||
|
execution.failure_context =
|
||||||
|
absl::StrFormat(
|
||||||
|
"frame=%d current_window=%s nav_window=%s hovered_window=%s active_id=0x%08X hovered_id=0x%08X",
|
||||||
|
ImGui::GetFrameCount(), current_name, nav_name, hovered_name,
|
||||||
|
active_id, hovered_id);
|
||||||
|
#else
|
||||||
|
execution.failure_context =
|
||||||
|
absl::StrFormat("frame=%d", ImGui::GetFrameCount());
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
execution.failure_context = "ImGui context not available";
|
execution.failure_context = "ImGui context not available";
|
||||||
}
|
}
|
||||||
@@ -1659,12 +1680,20 @@ void TestManager::CaptureFailureContext(const std::string& test_id) {
|
|||||||
// Note: Screenshot RPC implementation is in ImGuiTestHarnessServiceImpl
|
// Note: Screenshot RPC implementation is in ImGuiTestHarnessServiceImpl
|
||||||
// The screenshot_path will be set by the RPC handler when it completes
|
// The screenshot_path will be set by the RPC handler when it completes
|
||||||
// For now, we just set a placeholder path to indicate where it should be saved
|
// For now, we just set a placeholder path to indicate where it should be saved
|
||||||
|
if (execution.screenshot_path.empty()) {
|
||||||
execution.screenshot_path =
|
execution.screenshot_path =
|
||||||
absl::StrFormat("/tmp/yaze_test_%s_failure.bmp", test_id);
|
absl::StrFormat("/tmp/yaze_test_%s_failure.bmp", test_id);
|
||||||
|
execution.screenshot_size_bytes = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// 3. Widget state capture (IT-08c)
|
// 3. Widget state capture (IT-08c)
|
||||||
// TODO: FINISHME
|
execution.widget_state = core::CaptureWidgetState();
|
||||||
// execution.widget_state = core::CaptureWidgetState();
|
|
||||||
|
// Keep aggregate cache in sync with the latest execution snapshot.
|
||||||
|
auto aggregate_it = harness_aggregates_.find(execution.name);
|
||||||
|
if (aggregate_it != harness_aggregates_.end()) {
|
||||||
|
aggregate_it->second.latest_execution = execution;
|
||||||
|
}
|
||||||
|
|
||||||
util::logf("[TestManager] Captured failure context for test %s: %s",
|
util::logf("[TestManager] Captured failure context for test %s: %s",
|
||||||
test_id.c_str(), execution.failure_context.c_str());
|
test_id.c_str(), execution.failure_context.c_str());
|
||||||
|
|||||||
Reference in New Issue
Block a user