Spec + plan + metadata + state for the ImGui Test Engine integration. Enables the test engine via --enable-test-engine flag, bridges it through the existing API hooks layer (4 new /api/test_engine/* endpoints + 4 new ApiHookClient methods), and proves the full bridge with a smoke test. The test engine enables high-fidelity simulation of docking, window focus, panel visibility, drag-and-drop, and keyboard input that the current Hook API cannot express. The API hooks remain the single communication boundary; the test engine is integrated behind it. This is Track 1 of a 3-track campaign: Track 1: bridge + smoke test (this track) Track 2: migrate docking/focus/panel tests Track 3: visual regression via screenshot capture Key risk: R1 (GIL-transfer crash) mitigated by Phase 1 Task 1.4 manual verification checkpoint. Parallel-safe against the running tier2 taxonomy branch and the enforcement_gap_closure track (zero file overlap).
13 KiB
Plan: ImGui Test Engine Integration (Bridge via API Hooks)
Track: test_engine_integration_20260627
Branch: master (parallel-safe; touches sloppy.py, src/gui_2.py, src/app_controller.py, src/api_hooks.py, src/api_hook_client.py, tests/conftest.py, new tests/test_test_engine_smoke.py — zero overlap with the running tier2 taxonomy branch or the enforcement_gap_closure track)
Spec: conductor/tracks/test_engine_integration_20260627/spec.md
All Python edits use 1-space indentation. No comments in body. CRLF preserved.
Phase 1: Enable the Test Engine in the App
Focus: Add --enable-test-engine CLI flag, set runner_params.use_imgui_test_engine, add the register_tests callback with a placeholder smoke test.
-
Task 1.1: Write failing test for
--enable-test-engineflag + engine activation- WHERE:
tests/test_test_engine_smoke.py(NEW file) - WHAT: Test 1:
test_engine_enabled— startlive_gui(which will pass--enable-test-engine), verify the engine is active by callingclient.get_test_status()(new method, implemented in Phase 3) and assertingqueue_empty == True(engine is running, no tests queued). This test will FAIL before Phase 1 + Phase 3 land (the endpoint doesn't exist yet). - HOW: Use the
live_guifixture. Callclient.get_test_status(). Assert the response has aqueue_emptyfield. (The method is added in Phase 3; the test is written first per TDD.) - SAFETY: No
live_guistate mutation; just a GET request. - COMMIT:
test(smoke): add failing test for test engine activation - GIT NOTE: Red-phase test for the
--enable-test-engineflag + engine activation.
- WHERE:
-
Task 1.2: Add
--enable-test-engineCLI flag tosloppy.py+AppController- WHERE:
sloppy.py:35(add arg),src/app_controller.py:1042(addtest_engine_enabledfield) - WHAT:
sloppy.py: addparser.add_argument("--enable-test-engine", action="store_true", help="Enable Dear ImGui Test Engine for automated UI testing")after the--enable-test-hooksline.src/app_controller.py:1042: addself.test_engine_enabled: bool = ("--enable-test-engine" in sys.argv)after thetest_hooks_enabledline.
- HOW: Use
manual-slop_edit_fileMCP tool. 1-space indent. - SAFETY: The flag is opt-in; normal runs are unaffected.
- COMMIT:
feat(cli): add --enable-test-engine flag - GIT NOTE: CLI flag for test engine; mirrors the --enable-test-hooks pattern at app_controller.py:1042.
- WHERE:
-
Task 1.3: Enable the engine in
App.run()+ add_register_imgui_testscallback- WHERE:
src/gui_2.py:641(afterRunnerParams()construction) +src/gui_2.py:~700(new_register_imgui_testsmethod) - WHAT:
- In
App.run()between line 641 (self.runner_params = _hi.RunnerParams()) and line 684 (callbacks.show_gui = ...), add:if getattr(self.controller, "test_engine_enabled", False): self.runner_params.use_imgui_test_engine = True self.runner_params.callbacks.register_tests = self._register_imgui_tests - Add
_register_imgui_tests(self)method onApp(after_post_init, ~line 700):The exactdef _register_imgui_tests(self) -> None: from imgui_bundle import hello_imgui from imgui_bundle.imgui import test_engine engine = hello_imgui.get_imgui_test_engine() test = test_engine.register_test(engine, "Smoke Tests", "Tab Switch") def smoke_func(ctx) -> None: from imgui_bundle.imgui.test_engine_checks import CHECK ctx.set_ref("###manual slop") ctx.item_click("**/Session") CHECK(True) test.test_func = smoke_funcset_ref+item_clicktargets are determined during implementation by inspecting the running GUI's label tree. The smoke test should click a harmless tab (e.g., switch to "Session" tab) andCHECK(True)as a placeholder assertion. The real assertion (verify the tab actually switched) is added once the label path is confirmed.
- In
- HOW: Use
manual-slop_edit_file/manual-slop_py_update_definitionMCP tool. 1-space indent. - SAFETY: Guarded by
test_engine_enabled; normal runs skip this entirely. Theregister_testscallback is only called byhello_imguiwhenuse_imgui_test_engine = True. - COMMIT:
feat(gui): enable test engine + register smoke test via callbacks.register_tests - GIT NOTE: Activates the test engine when --enable-test-engine is set; registers a placeholder smoke test.
- WHERE:
-
Task 1.4: Verify the engine activates (manual)
- WHAT: Run
uv run python sloppy.py --enable-test-hooks --enable-test-enginelocally. Verify the app starts without crashing (the GIL-transfer mechanism works). Verifyhello_imgui.get_imgui_test_engine()returns a non-None engine. This is a manual checkpoint before proceeding to Phase 2. - COMMIT: (no commit; manual verification checkpoint)
- GIT NOTE: Manual verification that the engine + GIL transfer works with the app's existing thread layout.
- WHAT: Run
Phase 2: Build the API Hooks Bridge
Focus: Add the 4 /api/test_engine/* endpoints to HookServer + the 4 methods to ApiHookClient.
-
Task 2.1: Write failing tests for the 4 new
ApiHookClientmethods- WHERE:
tests/test_test_engine_smoke.py(append to the file from Task 1.1) - WHAT: 2 more tests:
test_queue_and_run_smoke_test: queue the smoke test viaclient.queue_test("Smoke Tests", "Tab Switch"), poll viaclient.wait_for_test_results(timeout=30), assertresults["count_success"] >= 1andresults["count_tested"] >= 1.test_engine_results_shape: callclient.get_test_results(), assert the response dict has keyscount_tested,count_success,count_in_queue.
- HOW: Use
live_guifixture. These tests fail until Phase 2 + Phase 3 land (the client methods + endpoints don't exist yet). - SAFETY: The smoke test queues a harmless tab-switch; no destructive state change.
- COMMIT:
test(smoke): add failing tests for queue_test + wait_for_test_results + get_test_results - GIT NOTE: Red-phase tests for the 4 new ApiHookClient methods.
- WHERE:
-
Task 2.2: Add the 4
/api/test_engine/*endpoints toHookServer- WHERE:
src/api_hooks.py—do_GET(line 157) +do_POST(line 490) - WHAT: Add 4 new
elifbranches:do_GET:elif self.path == "/api/test_engine/status":— lazy-importhello_imgui+test_engine; get engine viahello_imgui.get_imgui_test_engine(); calltest_engine.is_test_queue_empty(engine); respond{"queue_empty": bool}.do_GET:elif self.path == "/api/test_engine/results":— get engine; createTestEngineResultSummary(); calltest_engine.get_result_summary(engine, out_results); respond{"count_tested": N, "count_success": N, "count_in_queue": N}.do_POST:elif self.path == "/api/test_engine/queue":— body{"group": "...", "name": "..."}; get engine; find test viatest_engine.find_test_by_name(engine, group, name); if found,test_engine.queue_test(engine, test); respond{"status": "queued"}or{"error": "test not found"}(404).do_POST:elif self.path == "/api/test_engine/abort":— get engine;test_engine.abort_current_test(engine); respond{"status": "aborted"}.
- HOW: Follow the existing endpoint pattern (lines 499-505 for POST, lines 231-241 for GET). Use
_get_app_attr(app, "controller")to checktest_engine_enabled; if not enabled, respond 503. Usejson.dumps(...)for the response body. 1-space indent. - SAFETY: The endpoints run on the HTTP handler thread.
hello_imgui.get_imgui_test_engine()is a C++ accessor (thread-safe).queue_test/is_test_queue_empty/get_result_summaryare thread-safe C++ engine operations (the engine is designed for cross-thread test scheduling).abort_current_testis also thread-safe. - COMMIT:
feat(api_hooks): add /api/test_engine/* bridge endpoints - GIT NOTE: 4 new endpoints: queue, status, results, abort; bridge the test process to the engine via HTTP.
- WHERE:
-
Task 2.3: Add the 4 new methods to
ApiHookClient- WHERE:
src/api_hook_client.py(after the existing methods, ~line 500) - WHAT: 4 new methods:
queue_test(self, group: str, name: str) -> dict— POST/api/test_engine/queuewith{"group": group, "name": name}; return the response dict.get_test_status(self) -> dict— GET/api/test_engine/status; return{"queue_empty": bool}.get_test_results(self) -> dict— GET/api/test_engine/results; return{"count_tested": N, "count_success": N, "count_in_queue": N}.wait_for_test_results(self, timeout: float = 30.0) -> dict— pollget_test_status()every 0.5s untilqueue_empty == Trueor timeout; then returnget_test_results(). On timeout, return the last results (with atimed_out: Truefield).
- HOW: Follow the existing method pattern (e.g.,
get_statusat line 105,push_eventat line 156). Userequests.get/post+ retry. 1-space indent. - SAFETY: Pure HTTP client; no thread safety concerns.
- COMMIT:
feat(api_hook_client): add queue_test + get_test_status + get_test_results + wait_for_test_results - GIT NOTE: 4 new client methods mirroring the 4 new endpoints; wait_for_test_results replaces time.sleep+get_value polling.
- WHERE:
-
Task 2.4: Run Phase 2 tests (Green phase)
- WHAT:
uv run pytest tests/test_test_engine_smoke.py -v --timeout=60. All 3 tests must pass. If the smoke test (test_queue_and_run_smoke_test) fails, the most likely cause is theset_ref/item_clicklabel path being wrong — debug by usingimgui.show_id_stack_tool_window()orctx.window_info("manual slop")to find the correct label. If the GIL transfer fails, the app will crash — that's a hard blocker; report to user. - COMMIT:
conductor(state): Phase 2 green-phase verification(or skip if no code changes) - GIT NOTE: Green-phase verification for the 4 new endpoints + 4 new client methods.
- WHAT:
Phase 3: Live_gui Fixture + Full Smoke Test
Focus: Pass --enable-test-engine in the live_gui fixture + verify the full bridge works end-to-end.
-
Task 3.1: Update
live_guifixture to pass--enable-test-engine- WHERE:
tests/conftest.py:792 - WHAT: Change
gui_args = ["uv", "run", "python", "-u", gui_script, "--enable-test-hooks"]to include"--enable-test-engine":gui_args = ["uv", "run", "python", "-u", gui_script, "--enable-test-hooks", "--enable-test-engine"] - HOW:
manual-slop_edit_fileMCP tool. 1-space indent. - SAFETY: The engine is idle when no tests are queued. Existing
live_guitests that don't use the test engine are unaffected (the engine adds sub-ms per-frame overhead). - COMMIT:
test(conftest): pass --enable-test-engine in live_gui fixture - GIT NOTE: Engine activates on every live_gui run; idle when no tests queued.
- WHERE:
-
Task 3.2: Run the full smoke test suite (Green phase)
- WHAT:
uv run pytest tests/test_test_engine_smoke.py -v --timeout=60. All 3 tests pass. Then run a small batch of existinglive_guitests to verify no regression:uv run pytest tests/test_workspace_profiles_restoration.py tests/test_undo_redo_lifecycle.py -v --timeout=120. - COMMIT:
conductor(state): Phase 3 green-phase verification - GIT NOTE: Full bridge verified: pytest → HTTP → HookServer → engine → scenario thread → ctx.item_click → GUI thread → CHECK → results → HTTP → pytest assert.
- WHAT:
Phase 4: End-of-Track Report + State Update
-
Task 4.1: Write end-of-track report
- WHERE:
docs/reports/TRACK_COMPLETION_test_engine_integration_20260627.md(NEW file) - WHAT: Report following the precedent:
- TL;DR
- Phase summary (each phase + commits + status)
- Verification Criteria status (mapped to spec G1-G7)
- Threading model verification (did the GIL transfer work? any crashes? any state-access issues from the scenario thread?)
- The 4 new endpoints + 4 new client methods documented
- The smoke test result
- Handoff to Track 2 (docking test migration) — what's now possible that wasn't before
- Known limitations (engine requires a live window; not headless; the interactive panel is not shown)
- COMMIT:
docs(reports): TRACK_COMPLETION_test_engine_integration_20260627 - GIT NOTE: End-of-track report; documents the bridge + threading model verification + Track 2 handoff.
- WHERE:
-
Task 4.2: Update
conductor/tracks.md+conductor/chronology.md+state.toml- WHAT:
state.toml: mark all phases "completed" with checkpoint SHA;status = "completed".conductor/tracks.md: add row for this track (status "shipped").conductor/chronology.md: prepend row for2026-06-27 | test_engine_integration_20260627 | shipped | ....
- COMMIT:
conductor(state): test_engine_integration_20260627 SHIPPED + TRACK_COMPLETION - GIT NOTE: Track state + chronology + tracks.md closed out.
- WHAT:
-
Task 4.3: Conductor - User Manual Verification
- WHAT: Present the results: the smoke test pass, the threading model verification, the 4 new endpoints, the 4 new client methods. PAUSE for user sign-off.
- COMMIT: (no commit; user-confirmation gate)
- GIT NOTE: User sign-off record.