# 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-engine` flag + engine activation - **WHERE:** `tests/test_test_engine_smoke.py` (NEW file) - **WHAT:** Test 1: `test_engine_enabled` — start `live_gui` (which will pass `--enable-test-engine`), verify the engine is active by calling `client.get_test_status()` (new method, implemented in Phase 3) and asserting `queue_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_gui` fixture. Call `client.get_test_status()`. Assert the response has a `queue_empty` field. (The method is added in Phase 3; the test is written first per TDD.) - **SAFETY:** No `live_gui` state mutation; just a GET request. - **COMMIT:** `test(smoke): add failing test for test engine activation` - **GIT NOTE:** Red-phase test for the `--enable-test-engine` flag + engine activation. - [ ] Task 1.2: Add `--enable-test-engine` CLI flag to `sloppy.py` + `AppController` - **WHERE:** `sloppy.py:35` (add arg), `src/app_controller.py:1042` (add `test_engine_enabled` field) - **WHAT:** 1. `sloppy.py`: add `parser.add_argument("--enable-test-engine", action="store_true", help="Enable Dear ImGui Test Engine for automated UI testing")` after the `--enable-test-hooks` line. 2. `src/app_controller.py:1042`: add `self.test_engine_enabled: bool = ("--enable-test-engine" in sys.argv)` after the `test_hooks_enabled` line. - **HOW:** Use `manual-slop_edit_file` MCP 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. - [ ] Task 1.3: Enable the engine in `App.run()` + add `_register_imgui_tests` callback - **WHERE:** `src/gui_2.py:641` (after `RunnerParams()` construction) + `src/gui_2.py:~700` (new `_register_imgui_tests` method) - **WHAT:** 1. In `App.run()` between line 641 (`self.runner_params = _hi.RunnerParams()`) and line 684 (`callbacks.show_gui = ...`), add: ```python 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 ``` 2. Add `_register_imgui_tests(self)` method on `App` (after `_post_init`, ~line 700): ```python def _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_func ``` The exact `set_ref` + `item_click` targets 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) and `CHECK(True)` as a placeholder assertion. The real assertion (verify the tab actually switched) is added once the label path is confirmed. - **HOW:** Use `manual-slop_edit_file` / `manual-slop_py_update_definition` MCP tool. 1-space indent. - **SAFETY:** Guarded by `test_engine_enabled`; normal runs skip this entirely. The `register_tests` callback is only called by `hello_imgui` when `use_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. - [ ] Task 1.4: Verify the engine activates (manual) - **WHAT:** Run `uv run python sloppy.py --enable-test-hooks --enable-test-engine` locally. Verify the app starts without crashing (the GIL-transfer mechanism works). Verify `hello_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. --- ## 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 `ApiHookClient` methods - **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 via `client.queue_test("Smoke Tests", "Tab Switch")`, poll via `client.wait_for_test_results(timeout=30)`, assert `results["count_success"] >= 1` and `results["count_tested"] >= 1`. - `test_engine_results_shape`: call `client.get_test_results()`, assert the response dict has keys `count_tested`, `count_success`, `count_in_queue`. - **HOW:** Use `live_gui` fixture. 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. - [ ] Task 2.2: Add the 4 `/api/test_engine/*` endpoints to `HookServer` - **WHERE:** `src/api_hooks.py` — `do_GET` (line 157) + `do_POST` (line 490) - **WHAT:** Add 4 new `elif` branches: 1. `do_GET`: `elif self.path == "/api/test_engine/status":` — lazy-import `hello_imgui` + `test_engine`; get engine via `hello_imgui.get_imgui_test_engine()`; call `test_engine.is_test_queue_empty(engine)`; respond `{"queue_empty": bool}`. 2. `do_GET`: `elif self.path == "/api/test_engine/results":` — get engine; create `TestEngineResultSummary()`; call `test_engine.get_result_summary(engine, out_results)`; respond `{"count_tested": N, "count_success": N, "count_in_queue": N}`. 3. `do_POST`: `elif self.path == "/api/test_engine/queue":` — body `{"group": "...", "name": "..."}`; get engine; find test via `test_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). 4. `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 check `test_engine_enabled`; if not enabled, respond 503. Use `json.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_summary` are thread-safe C++ engine operations (the engine is designed for cross-thread test scheduling). `abort_current_test` is 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. - [ ] 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: 1. `queue_test(self, group: str, name: str) -> dict` — POST `/api/test_engine/queue` with `{"group": group, "name": name}`; return the response dict. 2. `get_test_status(self) -> dict` — GET `/api/test_engine/status`; return `{"queue_empty": bool}`. 3. `get_test_results(self) -> dict` — GET `/api/test_engine/results`; return `{"count_tested": N, "count_success": N, "count_in_queue": N}`. 4. `wait_for_test_results(self, timeout: float = 30.0) -> dict` — poll `get_test_status()` every 0.5s until `queue_empty == True` or timeout; then return `get_test_results()`. On timeout, return the last results (with a `timed_out: True` field). - **HOW:** Follow the existing method pattern (e.g., `get_status` at line 105, `push_event` at line 156). Use `requests.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. - [ ] 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 the `set_ref` / `item_click` label path being wrong — debug by using `imgui.show_id_stack_tool_window()` or `ctx.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. --- ## 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_gui` fixture 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"`: ```python gui_args = ["uv", "run", "python", "-u", gui_script, "--enable-test-hooks", "--enable-test-engine"] ``` - **HOW:** `manual-slop_edit_file` MCP tool. 1-space indent. - **SAFETY:** The engine is idle when no tests are queued. Existing `live_gui` tests 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. - [ ] 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 existing `live_gui` tests 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. --- ## 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. - [ ] Task 4.2: Update `conductor/tracks.md` + `conductor/chronology.md` + `state.toml` - **WHAT:** 1. `state.toml`: mark all phases "completed" with checkpoint SHA; `status = "completed"`. 2. `conductor/tracks.md`: add row for this track (status "shipped"). 3. `conductor/chronology.md`: prepend row for `2026-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. - [ ] 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.