/** * @file dungeon_visual_verification_test.cc * @brief AI-powered visual verification tests for dungeon object rendering * * This test integrates ImGuiTestEngine with Gemini Vision API to perform * automated visual verification of dungeon rendering. The workflow: * 1. Use ImGuiTestEngine to navigate to specific dungeon rooms * 2. Capture screenshots of rendered content * 3. Send screenshots to Gemini Vision for analysis * 4. Verify AI response matches expected rendering criteria * * Requires: * - GEMINI_API_KEY environment variable * - ROM file for testing * - GUI test mode (--ui flag) */ #define IMGUI_DEFINE_MATH_OPERATORS #include #include #include "app/controller.h" #include "app/platform/window.h" #include "rom/rom.h" #include "gtest/gtest.h" #include "imgui.h" #include "imgui_test_engine/imgui_te_context.h" #include "imgui_test_engine/imgui_te_engine.h" #include "test_utils.h" #ifdef YAZE_AI_RUNTIME_AVAILABLE #include "cli/service/ai/gemini_ai_service.h" #endif namespace yaze { namespace test { // ============================================================================= // Visual Verification Test Functions (registered with ImGuiTestEngine) // ============================================================================= /** * @brief Basic room rendering verification test * Navigates to room 0 and verifies it renders correctly */ void E2ETest_VisualVerification_BasicRoomRendering(ImGuiTestContext* ctx) { ctx->LogInfo("=== Visual Verification: Basic Room Rendering ==="); // Load ROM ctx->LogInfo("Loading ROM..."); gui::LoadRomInTest(ctx, "zelda3.sfc"); // Open Dungeon Editor ctx->LogInfo("Opening Dungeon Editor..."); gui::OpenEditorInTest(ctx, "Dungeon"); ctx->Yield(10); // Wait for the dungeon controls to appear ctx->LogInfo("Waiting for Dungeon Controls..."); ctx->Yield(30); // Enable room selector if (ctx->WindowInfo("Dungeon Controls").Window != nullptr) { ctx->SetRef("Dungeon Controls"); ctx->ItemClick("Rooms"); ctx->Yield(5); } // Navigate to room 0 ctx->LogInfo("Navigating to Room 0..."); if (ctx->WindowInfo("Room Selector").Window != nullptr) { ctx->SetRef("Room Selector"); if (ctx->ItemExists("Room 0x00")) { ctx->ItemDoubleClick("Room 0x00"); ctx->Yield(30); ctx->LogInfo("Room 0 opened successfully"); } } // Verify room card exists and has content if (ctx->WindowInfo("Room 0x00").Window != nullptr) { ctx->LogInfo("Room 0x00 card is visible"); ctx->SetRef("Room 0x00"); // Check for canvas if (ctx->ItemExists("##RoomCanvas")) { ctx->LogInfo("Room canvas found - rendering appears successful"); } else { ctx->LogWarning("Room canvas not found - check rendering"); } } ctx->LogInfo("=== Basic Room Rendering Test Complete ==="); } /** * @brief Layer visibility verification test * Tests that toggling layer visibility changes the rendered output */ void E2ETest_VisualVerification_LayerVisibility(ImGuiTestContext* ctx) { ctx->LogInfo("=== Visual Verification: Layer Visibility ==="); // Load ROM and open editor gui::LoadRomInTest(ctx, "zelda3.sfc"); gui::OpenEditorInTest(ctx, "Dungeon"); ctx->Yield(20); // Enable controls if (ctx->WindowInfo("Dungeon Controls").Window != nullptr) { ctx->SetRef("Dungeon Controls"); ctx->ItemClick("Rooms"); ctx->Yield(5); } // Open a room if (ctx->WindowInfo("Room Selector").Window != nullptr) { ctx->SetRef("Room Selector"); if (ctx->ItemExists("Room 0x00")) { ctx->ItemDoubleClick("Room 0x00"); ctx->Yield(20); } } // Test layer visibility controls if (ctx->WindowInfo("Room 0x00").Window != nullptr) { ctx->SetRef("Room 0x00"); // Toggle BG1 visibility if (ctx->ItemExists("Show BG1")) { ctx->LogInfo("Testing BG1 layer toggle..."); ctx->ItemClick("Show BG1"); ctx->Yield(10); ctx->ItemClick("Show BG1"); ctx->Yield(5); ctx->LogInfo("BG1 layer toggle successful"); } // Toggle BG2 visibility if (ctx->ItemExists("Show BG2")) { ctx->LogInfo("Testing BG2 layer toggle..."); ctx->ItemClick("Show BG2"); ctx->Yield(10); ctx->ItemClick("Show BG2"); ctx->Yield(5); ctx->LogInfo("BG2 layer toggle successful"); } } ctx->LogInfo("=== Layer Visibility Test Complete ==="); } /** * @brief Object editor panel verification test * Verifies the object editor panel opens and displays correctly */ void E2ETest_VisualVerification_ObjectEditor(ImGuiTestContext* ctx) { ctx->LogInfo("=== Visual Verification: Object Editor ==="); gui::LoadRomInTest(ctx, "zelda3.sfc"); gui::OpenEditorInTest(ctx, "Dungeon"); ctx->Yield(20); // Open object editor if (ctx->WindowInfo("Dungeon Controls").Window != nullptr) { ctx->SetRef("Dungeon Controls"); ctx->ItemClick("Objects"); ctx->Yield(10); ctx->LogInfo("Object Editor toggled"); } // Verify object editor panel if (ctx->WindowInfo("Object Editor").Window != nullptr) { ctx->LogInfo("Object Editor panel is visible"); ctx->SetRef("Object Editor"); // Check for object list or selector if (ctx->ItemExists("##ObjectList")) { ctx->LogInfo("Object list found"); } } ctx->LogInfo("=== Object Editor Test Complete ==="); } /** * @brief Multi-room navigation verification test * Tests navigating between multiple rooms */ void E2ETest_VisualVerification_MultiRoomNavigation(ImGuiTestContext* ctx) { ctx->LogInfo("=== Visual Verification: Multi-Room Navigation ==="); gui::LoadRomInTest(ctx, "zelda3.sfc"); gui::OpenEditorInTest(ctx, "Dungeon"); ctx->Yield(20); // Enable room selector if (ctx->WindowInfo("Dungeon Controls").Window != nullptr) { ctx->SetRef("Dungeon Controls"); ctx->ItemClick("Rooms"); ctx->Yield(5); } // Test multiple rooms std::vector test_rooms = {"Room 0x00", "Room 0x01", "Room 0x02"}; for (const auto& room_name : test_rooms) { ctx->LogInfo("Opening %s...", room_name.c_str()); if (ctx->WindowInfo("Room Selector").Window != nullptr) { ctx->SetRef("Room Selector"); if (ctx->ItemExists(room_name.c_str())) { ctx->ItemDoubleClick(room_name.c_str()); ctx->Yield(20); ctx->LogInfo("%s opened", room_name.c_str()); } else { ctx->LogWarning("%s not found in selector", room_name.c_str()); } } } ctx->LogInfo("=== Multi-Room Navigation Test Complete ==="); } // ============================================================================= // GTest Integration - Unit Tests for verification infrastructure // ============================================================================= /** * @class DungeonVisualVerificationTest * @brief GTest fixture for visual verification infrastructure tests */ class DungeonVisualVerificationTest : public ::testing::Test { protected: void SetUp() override { // Check for Gemini API key const char* api_key = std::getenv("GEMINI_API_KEY"); if (!api_key || std::string(api_key).empty()) { skip_ai_tests_ = true; } else { api_key_ = api_key; } // Create test output directory test_dir_ = std::filesystem::temp_directory_path() / "yaze_visual_verification"; std::filesystem::create_directories(test_dir_); } void TearDown() override { // Keep test artifacts for debugging - cleanup manually if needed } std::filesystem::path test_dir_; std::string api_key_; bool skip_ai_tests_ = false; }; TEST_F(DungeonVisualVerificationTest, TestDirectoryCreated) { ASSERT_TRUE(std::filesystem::exists(test_dir_)); } TEST_F(DungeonVisualVerificationTest, ApiKeyCheck) { if (skip_ai_tests_) { GTEST_SKIP() << "GEMINI_API_KEY not set - skipping AI tests"; } EXPECT_FALSE(api_key_.empty()); } #ifdef YAZE_AI_RUNTIME_AVAILABLE TEST_F(DungeonVisualVerificationTest, GeminiServiceAvailable) { if (skip_ai_tests_) { GTEST_SKIP() << "GEMINI_API_KEY not set"; } cli::GeminiConfig config; config.api_key = api_key_; config.model = "gemini-2.5-flash"; cli::GeminiAIService service(config); auto status = service.CheckAvailability(); EXPECT_TRUE(status.ok()) << status.message(); } TEST_F(DungeonVisualVerificationTest, ImageAnalysisBasic) { if (skip_ai_tests_) { GTEST_SKIP() << "GEMINI_API_KEY not set"; } // Create a simple test image auto image_path = test_dir_ / "test_image.png"; // Minimal PNG (8x8 pixels) const unsigned char png_data[] = { // PNG signature 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, // IHDR chunk 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x08, 0x02, 0x00, 0x00, 0x00, 0x4B, 0x6D, 0x29, 0xDE, // IDAT chunk 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x08, 0x99, 0x63, 0xF8, 0xCF, 0xC0, 0x00, 0x00, 0x03, 0x01, 0x01, 0x00, 0x18, 0xDD, 0x8D, 0xB4, // IEND chunk 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82}; std::ofstream file(image_path, std::ios::binary); file.write(reinterpret_cast(png_data), sizeof(png_data)); file.close(); ASSERT_TRUE(std::filesystem::exists(image_path)); cli::GeminiConfig config; config.api_key = api_key_; config.model = "gemini-2.5-flash"; config.verbose = false; cli::GeminiAIService service(config); auto response = service.GenerateMultimodalResponse( image_path.string(), "What do you see in this image? Keep response brief."); ASSERT_TRUE(response.ok()) << response.status().message(); EXPECT_FALSE(response->text_response.empty()); std::cout << "AI Response: " << response->text_response << std::endl; } #endif // YAZE_AI_RUNTIME_AVAILABLE } // namespace test } // namespace yaze