Files
yaze/test/e2e/dungeon_visual_verification_test.cc
2025-12-22 14:49:04 -05:00

332 lines
9.8 KiB
C++

/**
* @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 <filesystem>
#include <fstream>
#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, yaze::test::TestRomManager::GetTestRomPath());
// 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, yaze::test::TestRomManager::GetTestRomPath());
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, yaze::test::TestRomManager::GetTestRomPath());
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, yaze::test::TestRomManager::GetTestRomPath());
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<std::string> 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<const char*>(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