ktsu.ImGui.App.Testing
3.54.0
Prefix Reserved
See the version list below for details.
dotnet add package ktsu.ImGui.App.Testing --version 3.54.0
NuGet\Install-Package ktsu.ImGui.App.Testing -Version 3.54.0
<PackageReference Include="ktsu.ImGui.App.Testing" Version="3.54.0" />
<PackageVersion Include="ktsu.ImGui.App.Testing" Version="3.54.0" />
<PackageReference Include="ktsu.ImGui.App.Testing" />
paket add ktsu.ImGui.App.Testing --version 3.54.0
#r "nuget: ktsu.ImGui.App.Testing, 3.54.0"
#:package ktsu.ImGui.App.Testing@3.54.0
#addin nuget:?package=ktsu.ImGui.App.Testing&version=3.54.0
#tool nuget:?package=ktsu.ImGui.App.Testing&version=3.54.0
ktsu.ImGui.App.Testing
Headless test harness for ktsu.ImGui.App applications. Renders through a CPU rasterizer with no
window, no GPU and no graphics driver, injects input directly into ImGui rather than through the
operating system, and advances frames under the test's control.
Because nothing reaches the operating system, tests neither steal focus nor disturb anything else on the machine, and the same suite runs on a busy desktop and on a continuous integration runner with no display attached.
A Worked Example
using ImGuiAppHarness harness = ImGuiAppHarness.Start(app.BuildConfig(), new HarnessOptions
{
Width = 1280,
Height = 720,
});
harness.Click("file.open");
bool ready = harness.StepUntil(() => session.IsSettled, maxFrames: 300);
Assert.IsTrue(ready, "The application never settled.");
CapturedFrame frame = harness.Capture();
Rectangle? image = frame.FindBounds(p => p.A > 0);
Assert.IsNotNull(image, "Something should have been drawn.");
frame.SavePng("artifact.png");
Pass the same ImGuiAppConfig the application gives ImGuiApp.Start, so a test exercises the real
configuration rather than one written for testing.
Driving the Application
Step() advances exactly one frame. StepUntil(predicate, maxFrames) advances until a condition
holds or a frame budget runs out, returning false rather than throwing so the caller decides whether a
timeout is a failure. The budget counts frames rather than milliseconds, so a loaded machine takes
longer in real time without changing the outcome.
Input is injected into ImGui's event queue:
harness.Mouse.MoveTo(x, y);
harness.Mouse.Click(x, y);
harness.Mouse.Drag(fromX, fromY, toX, toY, steps: 24);
harness.Mouse.Wheel(x, y, clicks: 4);
harness.Keyboard.Press(ImGuiKey.Z, ctrl: true);
harness.Keyboard.Type("export.png");
The high-level helpers advance frames where the interaction requires it. ImGui activates a button on release and only notices a press that was visible during a completed frame, so a press and release inside one frame would do nothing.
Addressing Widgets by Name
Prefer names over coordinates. ktsu.ImGui.Widgets and ktsu.ImGui.Popups mark their interactive
items automatically, and an application marks anything else through ImGuiProbes.MarkItem:
harness.Click("filesystem-browser/a.png");
Assert.IsNull(harness.Probe.Rect("filesystem-browser/notes.txt"), "The codec filter should exclude it.");
Names are recorded fully qualified, as the ImGui window followed by any pushed scopes and then the item's own name. Lookups match trailing segments, so a test writes the shortest name that identifies one item. A name matching several items, or one marked twice in a single frame, is reported as ambiguous with its candidates listed rather than resolving to whichever was drawn last. Clicking an item that was not drawn in the most recent frame fails as well, since its recorded position is stale and clicking it would hit whatever has since moved there.
Determinism
HarnessOptions pins everything that would otherwise vary between runs: display size, DPI scale, and
a fixed frame delta independent of real elapsed time. Frame rate limiting is off and ImGui's layout
file is never read or written, so one test cannot inherit state from another.
Two runs of the same scenario produce byte-identical frames. That property is what makes pixel measurements worth asserting on, and it is covered by a test.
Extensions and Docking
The harness stands in for ImGuiController, so it performs the same setup that a windowed
application gets: ImGuizmo, ImNodes and ImPlot are detected, handed the ImGui context, given their
own contexts, and ticked once per frame. An application that draws with any of them renders under
the harness rather than faulting inside native code.
ImGuiAppConfig.EnableDocking is honored too, and applied before the first frame, which is the only
point ImGui accepts it. A configuration that sets it can therefore use
ImGuiWidgets.DrawDeferredDocked() in a test exactly as it does in the application.
Known Limitations
Rendering is a CPU rasterizer, not the OpenGL backend the application ships with. That is what makes results identical on every machine, and it is also why a defect confined to the GL renderer will not be caught here.
Only marked items can be addressed by name. Two identically labeled widgets in the same window with no scope between them collide, and are refused rather than guessed at. ImGui has the same limitation and the same remedy, which is to give them distinct identifiers.
Acknowledgments
- Dear ImGui - The immediate mode GUI library the harness drives
- Hexa.NET.ImGui - The .NET bindings for Dear ImGui that the harness renders through
Contributing
Contributions are welcome! For feature requests, bug reports, or questions, please open an issue on the GitHub repository. If you would like to contribute code, please open a pull request with your changes.
License
ImGui.App.Testing is licensed under the MIT License. See LICENSE.md for more information.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Hexa.NET.ImGui (>= 2.2.9)
- ktsu.ImGui.App (>= 3.54.0)
- ktsu.ImGui.Probes (>= 3.54.0)
- ktsu.Invoker (>= 1.3.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.59.1 | 31 | 10/1/2026 |
| 3.59.0 | 46 | 9/30/2026 |
| 3.58.0 | 47 | 9/30/2026 |
| 3.57.0 | 62 | 9/30/2026 |
| 3.56.2 | 62 | 9/30/2026 |
| 3.56.1 | 74 | 9/30/2026 |
| 3.56.0 | 77 | 9/30/2026 |
| 3.55.0 | 77 | 9/29/2026 |
| 3.54.0 | 71 | 9/29/2026 |
| 3.53.0 | 72 | 9/29/2026 |
| 3.52.0 | 75 | 9/29/2026 |
| 3.51.0 | 71 | 9/29/2026 |
| 3.50.1-pre.1 | 47 | 9/28/2026 |
| 3.50.0 | 109 | 9/26/2026 |
| 3.49.2 | 100 | 9/25/2026 |
| 3.49.1 | 103 | 9/25/2026 |
| 3.49.0 | 103 | 9/25/2026 |
| 3.48.0 | 101 | 9/25/2026 |
| 3.47.0 | 103 | 9/25/2026 |
| 3.46.1 | 107 | 9/24/2026 |
## v3.54.0 (minor)
Changes since v3.53.0:
- Merge main into the data table branch and give DataTable a gallery tile ([@matt-edmondson](https://github.com/matt-edmondson))
- Check the curve editor's drawn bounds without a nullable dereference ([@Claude](https://github.com/Claude))
- Compare floats with tolerances where the code-quality bot flagged them ([@Claude](https://github.com/Claude))
- [patch] Show the data table demo once, with the advanced demos ([@matt-edmondson](https://github.com/matt-edmondson))
- Regenerate the widget gallery with the fixed renderer and widgets ([@Claude](https://github.com/Claude))
- Fix four widgets that drew wrongly in the gallery ([@Claude](https://github.com/Claude))
- Make the software rasterizer follow the GPU rules ImGui relies on ([@Claude](https://github.com/Claude))
- [patch] Read an empty clipboard without throwing and address code quality findings ([@matt-edmondson](https://github.com/matt-edmondson))
- Merge main into the data table branch ([@matt-edmondson](https://github.com/matt-edmondson))
- Merge main into the data table branch ([@Claude](https://github.com/Claude))
- [patch] Drop a semicolon from the data table's Key Files line ([@matt-edmondson](https://github.com/matt-edmondson))
- [patch] Skip parts already in stock when the demo marks them in stock ([@matt-edmondson](https://github.com/matt-edmondson))
- [patch] Drive the data table through a keymap in the UI tests ([@matt-edmondson](https://github.com/matt-edmondson))
- [patch] Require Shift for a chord that names it even with Shift ignored ([@matt-edmondson](https://github.com/matt-edmondson))
- [patch] Begin a data table edit from a character typed with AltGr ([@matt-edmondson](https://github.com/matt-edmondson))
- [patch] Report no edit for a null string committed unchanged ([@matt-edmondson](https://github.com/matt-edmondson))
- [minor] Let a caller set a data table's selection and active cell ([@matt-edmondson](https://github.com/matt-edmondson))
- [patch] Rebuild a data table over a list replaced after Refresh ([@matt-edmondson](https://github.com/matt-edmondson))
- [patch] Commit a data table edit on keypad Enter and keep the edited row drawn ([@matt-edmondson](https://github.com/matt-edmondson))
- [patch] Give the data table's clipper the row pitch with cell padding ([@matt-edmondson](https://github.com/matt-edmondson))
- [patch] State which frameworks ktsu.Keybinding brings System.Text.Json to ([@matt-edmondson](https://github.com/matt-edmondson))
- [patch] Show the data table in the widgets demo and document it ([@matt-edmondson](https://github.com/matt-edmondson))
- [minor] Open a caller-drawn context menu on a data table cell ([@matt-edmondson](https://github.com/matt-edmondson))
- [patch] Leave the clipboard as found and ignore characters a cell can't start from ([@matt-edmondson](https://github.com/matt-edmondson))
- [minor] Drive a data table from the keyboard and edit cells in place ([@matt-edmondson](https://github.com/matt-edmondson))
- [minor] Filter a data table's columns from a row under the headers ([@matt-edmondson](https://github.com/matt-edmondson))
- [minor] Draw a data table with sortable headers and clickable cells ([@matt-edmondson](https://github.com/matt-edmondson))
- [minor] Declare the data table's keyboard commands for ktsu.Keybinding ([@matt-edmondson](https://github.com/matt-edmondson))
- [minor] Edit one data table cell at a time and report each edit ([@matt-edmondson](https://github.com/matt-edmondson))
- [minor] Move a data table's active cell and select its rows ([@matt-edmondson](https://github.com/matt-edmondson))
- [minor] Sort and filter a data table's rows into a view ([@matt-edmondson](https://github.com/matt-edmondson))
- [patch] Document the data table edit sessions' members ([@matt-edmondson](https://github.com/matt-edmondson))
- [minor] Add typed data table columns and their edit sessions ([@matt-edmondson](https://github.com/matt-edmondson))
- [patch] Share the key chord matcher and let held chords repeat ([@matt-edmondson](https://github.com/matt-edmondson))
- [patch] Plan the data table widget implementation ([@matt-edmondson](https://github.com/matt-edmondson))
- [patch] Design a data table widget with sorting, filtering, and cell editing ([@matt-edmondson](https://github.com/matt-edmondson))