Skip to content
omar edited this page May 31, 2023 · 7 revisions

Index


Once the Test Engine is setup, most interactions are done via the ImGuiTextContext interface. Refer to imgui_te_context.h for API and comments.

You may refer to app_minimal_tests.cpp as a reference for basic tests. You may refer to our test suite imgui_test_suite/ for a larger amount of reference code. Please note that our test suite is written primarily with the intent of testing Dear ImGui, not with the intent of providing examples for using the API.


Registering Tests

Tests are registered via the IM_REGISTER_TEST() macro.

IM_REGISTER_TEST(ImGuiTestEngine* engine, const char* test_category, const char* test_name);

(This internally uses ImGuiTestEngine_RegisterTest() but we use a macro so we can record the source file/line of tests, making it convenient in the interactive UI to preview test source code. Please always use the macro!)

Registering an example test:

t = IM_REGISTER_TEST(e, "demo_tests", "test1");
t->GuiFunc = [](ImGuiTestContext* ctx) // Optionally provide a GUI function in addition to your application GUI
{
    ImGui::Begin("Test Window", NULL, ImGuiWindowFlags_NoSavedSettings);
    ImGui::Text("Hello, automation world");
    ImGui::Button("Click Me");
    if (ImGui::TreeNode("Node"))
    {
        static bool b = false;
        ImGui::Checkbox("Checkbox", &b);
        ImGui::TreePop();
    }
    ImGui::End();
};
t->TestFunc = [](ImGuiTestContext* ctx) // Generally provide a Test function which will drive the test.
{
    ctx->SetRef("Test Window");
    ctx->ItemClick("Click Me");
    ctx->ItemOpen("Node"); // Optional as ItemCheck("Node/Checkbox") can do it automatically
    ctx->ItemCheck("Node/Checkbox");
    ctx->ItemUncheck("Node/Checkbox");
};

demo_test_1


Check Macros

  • A failed check will:
    • Output an error and set the test status accordingly.
    • Optionally break in debugger (if test_io.ConfigBreakOnError is set).
    • Return from the function (unless _NORET() is used).
  • The main macro is IM_CHECK(), e.g. IM_CHECK(result == true);
  • Operator macros are preferred as they will display the VALUE of both operands in the log: IM_CHECK_EQ(), IM_CHECK_NE(), IM_CHECK_LT(), IM_CHECK_LE(), IM_CHECK_GT(), IM_CHECK_GE().
  • Add suffix _NORET e.g. IM_CHECK_NO_RET(result == true); to prevent the macro from returning from function on failure (which is the default).
  • Add suffix _RETV e.g. IM_CHECK_RETV(result == true, -1); to return a specific error is running from sub-functions.
  • Add suffix _SILENT e.g. IM_CHECK_SILENT(window != NULL); to omit appending to log on lower verbose levels (reduce spam).
  • Specialized checks for strings: IM_CHECK_STR_EQ(), IM_CHECK_STR_NE().
  • Specialized checks for floating point values: IM_CHECK_FLOAT_EQ_EPS(), IM_CHECK_FLOAT_NEAR().
t = IM_REGISTER_TEST(e, "demo_tests", "always_fail");
t->TestFunc = [](ImGuiTestContext* ctx)
{
    int value = 1;
    IM_CHECK_EQ(value, 2);
};

image


Accessing your Data

(1) One solution is to set the void* UserData field of the structure ImGuiTest:

t = IM_REGISTER_TEST(e, "myapp_tests", "test1"); 
t->UserData = (void*)my_app; // Associate application pointer when registering
t->TestFunc = [](ImGuiTestContext* ctx)
{
    MyApp* my_app = (MyApp*)ctx->Test->UserData; // Retrieve application pointer when running.
    [....]
    // access your data however you need, e.g.
    IM_CHECK_EQ(my_app.GetSettings()->GridEnabled, true);
};

(2) Depending on the nature of your tests, you may want to share variables between your application or your GuiFunc and your TestFunc. This is mostly only ever useful if you are using a GuiFunc (e.g. for testing your own widgets or dialogs independently from your main application). To do this, you can request the test engine to instantiate a custom structure during the duration of a test running:

struct TestVars { int MyInt = 42; };
t = IM_REGISTER_TEST(e, "demo_tests", "test2");
t->SetVarsDataType<TestVars2>();
t->GuiFunc = [](ImGuiTestContext* ctx)
{
    auto& vars = ctx->GetVars<TestVars>();
    ImGui::Begin("Test Window", NULL, ImGuiWindowFlags_NoSavedSettings);
    ImGui::SliderInt("Slider", &vars.MyInt, 0, 1000);
    ImGui::End();
};
t->TestFunc = [](ImGuiTestContext* ctx)
{
    auto& vars = ctx->GetVars<auto>();
    ctx->SetRef("Test Window");

    IM_CHECK_EQ(vars.MyInt, 42); // Check default value
    ctx->ItemInputValue("Slider", 123);
    IM_CHECK_EQ(vars.MyInt, 123); // Check value after our action
};

As a convenience, we also provide a structure called ImGuiTestGenericVars and always available in ctx->GenericVars. The structure hold variety of ready-to-use named fields.

(3) You can access common widget states via ctx->ItemIsChecked("my_checkbox"), ctx->ItemIsOpened("my_tree_node") and other status flags emitted by ctx->ItemInfo().

(4) We are working on a "screen reader" tool to read text and shapes emitted by widgets, so a future possibility would be to read data from the visual output.


Named References

See Named References page where all nice uses of ImGuiTestRef are explained:

ctx->SetRef("Window");                      // Set Base Reference
ctx->ItemClick("Button");                   // Relative path
ctx->ItemClick("Node/Item");                // Relative path (composite)
ctx->ItemClick("//Window/Button");          // Absolute path
ctx->ItemClick("//$FOCUSED/Button");        // Relative to focused window
ctx->ItemClick("//Window/List/$$0/Button"); // Encode literal integer in string, for PushID(int)
ctx->ItemClick("//Window/**/Button");       // Wildcard search by full label
ctx->WindowInfo("//Window/Child/SubChild"); // Find child window by short name

ImGuiTestContext API

...