Automating Dear ImGui Testing Using the Test Engine: A Complete Integration Guide

Dear ImGui provides a built-in Test Engine framework that hooks into the ImGui context through extension points in imgui_internal.h, enabling automated UI testing via item registration macros and runtime flags without impacting production binaries.

Dear ImGui (ocornut/imgui) includes a lightweight, pluggable Test Engine architecture designed specifically for automating UI testing. By leveraging hook points defined in the internal headers and a companion repository, developers can implement regression testing, CI validation, and scripted gameplay interactions while keeping the test infrastructure entirely optional. This guide explains how the Test Engine integrates with ImGui's core to capture widget data and drive automated interactions.

How the Test Engine Hooks into the ImGui Context

The Test Engine integrates directly with Dear ImGui's core through a series of extension points defined in imgui_internal.h. Rather than existing as a separate binary, the engine hooks into the existing ImGui context using callback structures and global flags that remain dormant until explicitly activated.

The ImGuiContextHook Structure

At the heart of the integration lies the ImGuiContextHook structure (defined in imgui_internal.h at line 155). This structure allows external modules to register callbacks that ImGui invokes during key lifecycle events. When the Test Engine initializes, it registers a hook that receives notifications for item creation, information queries, and custom log entries.

The hook mechanism ensures that the Test Engine remains decoupled from ImGui's rendering logic while maintaining real-time visibility into UI state changes.

Runtime Flags and Activation

Two critical runtime flags control the Test Engine's data capture:

  • ImGuiDebugLogFlags_OutputToTestEngine (line 2149): Directs ImGui's internal debug log stream to the Test Engine for centralized logging.
  • g.TestEngineHookItems (line 2253): Enables the per-item callbacks that power automated testing.

These flags are toggled either by user code or automatically during the engine's initialization sequence. When disabled, the Test Engine macros compile to no-ops, ensuring zero overhead in production builds.

Item Registration and Data Capture

Every time ImGui renders a widget, the Test Engine captures metadata through conditional macros that expand only when testing is enabled. This instrumentation allows the engine to build a searchable representation of the active UI.

The IMGUI_TEST_ENGINE_ITEM_ADD Macro

Whenever an ImGui widget renders, the IMGUI_TEST_ENGINE_ITEM_ADD macro expands to a conditional call to ImGuiTestEngineHook_ItemAdd. This function records the widget's ID, bounding box, and optional ImGuiLastItemData. The macro is gated by the g.TestEngineHookItems flag, ensuring that the overhead of item tracking only occurs during test execution.

Similarly, IMGUI_TEST_ENGINE_ITEM_INFO captures additional metadata about the item's status, while IMGUI_TEST_ENGINE_LOG handles debug output. All three macros reside in imgui_internal.h and provide the raw data that the Test Engine uses to locate and interact with widgets programmatically.

Capturing Data in Custom Widgets

For custom widgets not covered by standard ImGui macros, you can manually register items with the Test Engine:

bool MyCustomButton(const char* label)
{
    ImGuiWindow* window = ImGui::GetCurrentWindow();
    ImGuiID id = window->GetID(label);
    ImRect bb = ImGui::GetItemRect();

    // Register the item with the test engine (if enabled)
    IMGUI_TEST_ENGINE_ITEM_ADD(id, bb, nullptr);
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, ImGuiItemStatusFlags_None);

    // Normal button rendering
    return ImGui::Button(label);
}

Locating Widgets for Automated Interaction

The Test Engine relies on human-readable identifiers to locate specific widgets during test execution. The helper function ImGuiTestEngine_FindItemDebugLabel (defined at line 4093 in imgui_internal.h) resolves a widget's debug label from its ID and state.

This resolution mechanism enables the engine to generate descriptive logs and allows test scripts to reference UI elements by meaningful names rather than internal handles. When combined with the bounding box data captured during item registration, the engine can simulate precise mouse clicks and keyboard inputs at the correct screen coordinates.

Engine Data Store and Separation of Concerns

ImGui maintains a pointer to the Test Engine's state through g.TestEngine (line 2273), declared as a void* in the internal context structure. This opaque pointer stores the engine's complete state without requiring ImGui to know the implementation details.

The actual Test Engine implementation resides in the separate imgui_test_engine repository, which provides the scripting language, test runner, and UI snapshot validation logic. This architectural separation allows developers to include the engine only in test builds, preventing binary bloat or performance degradation in shipping applications.

Setting Up the Test Engine in Your Application

Integrating the Test Engine requires linking the companion repository and activating the hooks during ImGui initialization.

Initialization Example

// Include the engine header (provided by imgui_test_engine)
#include "imgui_test_engine.h"

int main()
{
    // Initialize ImGui first
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO();

    // Create the test engine instance
    ImGuiTestEngine* engine = ImGuiTestEngine_Create();
    ImGui::GetCurrentContext()->TestEngine = engine;          // Store pointer in ImGui context
    ImGui::GetCurrentContext()->TestEngineHookItems = true;  // Enable item callbacks

    // Load a test script (example: my_test.imt)
    ImGuiTestEngine_AddTestFromFile(engine, "my_test.imt");

    // Run the engine each frame
    while (!done)
    {
        // ... ImGui frame start/end ...

        ImGuiTestEngine_Run(engine);  // Executes pending test steps
    }

    // Cleanup
    ImGuiTestEngine_Destroy(engine);
    ImGui::DestroyContext();
}

Writing Test Scripts

Test scripts use a domain-specific language to describe interactions. Save the following as my_test.imt:


# Simple click test

TEST MyWindowTest
{
    # Find the window titled "Demo"

    FIND_WINDOW "Demo"

    # Click the button labelled "OK"

    FIND_ITEM "OK"
    CLICK

    # Verify that a label "Success" appears

    FIND_ITEM "Success"
    ASSERT_VISIBLE
}

Key Files in the Test Engine Architecture

Understanding the file structure helps navigate the integration between ImGui core and the Test Engine:

  • imgui_internal.h: Defines the ImGuiContextHook structure and the IMGUI_TEST_ENGINE_* macros (lines 155, 2253, 4093).
  • imgui.cpp: Contains the runtime toggles for TestEngineHookItems and the TestEngine pointer storage.
  • imgui_widgets.cpp: Widget implementations showing conditional compilation of Test Engine data.
  • imgui_tables.cpp: Table widget code with Test Engine integration points.
  • imgui_test_engine repository: Complete implementation including the scripting language and test runner.

Summary

  • Dear ImGui's Test Engine hooks into the core library via ImGuiContextHook structures and runtime flags defined in imgui_internal.h.
  • Item registration macros (IMGUI_TEST_ENGINE_ITEM_ADD, IMGUI_TEST_ENGINE_ITEM_INFO) capture widget metadata only when g.TestEngineHookItems is enabled.
  • The g.TestEngine void pointer maintains the engine's state while keeping the implementation separate in the imgui_test_engine repository.
  • Debug label resolution via ImGuiTestEngine_FindItemDebugLabel enables human-readable test scripts.
  • Zero overhead in production is achieved through conditional compilation and runtime flags that disable all test infrastructure when not needed.

Frequently Asked Questions

How do I enable the Test Engine without modifying ImGui source code?

Enable the Test Engine by setting the TestEngineHookItems flag on your ImGui context after creating the engine instance. According to the source code in imgui_internal.h (line 2253), this flag gates all IMGUI_TEST_ENGINE_* macros, allowing you to toggle testing at runtime without recompiling ImGui. Simply link against the imgui_test_engine repository and assign the engine pointer to ImGui::GetCurrentContext()->TestEngine.

What performance impact does the Test Engine have on my application?

The Test Engine imposes zero overhead when disabled. All item registration macros expand to no-ops unless g.TestEngineHookItems is true, and the g.TestEngine pointer remains unused in standard builds. When enabled, the impact is limited to the cost of recording item IDs and bounding boxes via ImGuiTestEngineHook_ItemAdd, which is typically negligible for CI and testing scenarios.

Can I use the Test Engine with custom widgets that don't use standard ImGui functions?

Yes. Custom widgets can integrate with the Test Engine by manually calling IMGUI_TEST_ENGINE_ITEM_ADD with the widget's ID and bounding box, followed by IMGUI_TEST_ENGINE_ITEM_INFO to provide the label and status flags. As shown in imgui_internal.h, these macros accept the same parameters ImGui uses internally, ensuring your custom widgets are fully discoverable by test scripts.

Where is the actual Test Engine implementation located?

The Test Engine implementation resides in the imgui_test_engine companion repository, not in the main ocornut/imgui repository. The core ImGui library only provides the instrumentation hooks (the ImGuiContextHook structure and registration macros) and the g.TestEngine void pointer for storage. This separation allows you to include the full engine only in test builds, keeping production binaries lean.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →