.. _stdlib_imgui_playwright: ======================================================================= Playwright — block-form test harness for daslang-live + dasImgui apps ======================================================================= .. das:module:: imgui_playwright Block-form test harness for daslang-live + dasImgui apps. Spawns a host process, configures a transport (HTTP by default), and runs a test block against the live registry — click a widget by path, set a value, await quiescence, snapshot the surface, expect a value or render state. The ``with_imgui_app(...) { ... }`` lifecycle handles launch / handshake / shutdown so each test stays a single block. The live-API port is derived from ``DEFAULT_LIVE_PORT + worker_index`` (dastest's isolated-mode flag ``--worker-index N``), so N parallel workers land on distinct ports and never collide. Locators (``find_widget``, ``widget_exists``, ``widget_payload_field``, ``widget_rendered``) resolve via the same path keys the boost registry uses; actions (``click``, ``type_text``, ``force_set_value``, ``drag``, ``focus``, ``open_widget``, ``close_widget``, ``reload``) post to the live-command surface in :ref:`imgui_boost_runtime ` and apply the matching ``AwaitModifiers``. Polling helpers (``wait_until``, ``wait_for_payload_value``, ``await_quiescent``) wrap the timeout/retry loop so tests don't reinvent it. +++++++++ Constants +++++++++ .. _global-imgui_playwright-DEFAULT_LIVE_PORT: .. das:attribute:: DEFAULT_LIVE_PORT = 9090 DEFAULT_LIVE_PORT:int const .. _global-imgui_playwright-DEFAULT_DEADLOCK_SANITY_SEC: .. das:attribute:: DEFAULT_DEADLOCK_SANITY_SEC = 120f DEFAULT_DEADLOCK_SANITY_SEC:float const .. _global-imgui_playwright-DEFAULT_READY_TIMEOUT_SEC: .. das:attribute:: DEFAULT_READY_TIMEOUT_SEC = 30f DEFAULT_READY_TIMEOUT_SEC:float const .. _global-imgui_playwright-MACOS_READY_TIMEOUT_SEC: .. das:attribute:: MACOS_READY_TIMEOUT_SEC = 90f MACOS_READY_TIMEOUT_SEC:float const .. _global-imgui_playwright-ATTACH_PROBE_SEC: .. das:attribute:: ATTACH_PROBE_SEC = 0.3f ATTACH_PROBE_SEC:float const .. _global-imgui_playwright-DEFAULT_TEST_TIMEOUT_SEC: .. das:attribute:: DEFAULT_TEST_TIMEOUT_SEC = 120f DEFAULT_TEST_TIMEOUT_SEC:float const .. _global-imgui_playwright-DEFAULT_RELOAD_TIMEOUT_SEC: .. das:attribute:: DEFAULT_RELOAD_TIMEOUT_SEC = 30f DEFAULT_RELOAD_TIMEOUT_SEC:float const .. _global-imgui_playwright-DEFAULT_FRAME_WAIT_SEC: .. das:attribute:: DEFAULT_FRAME_WAIT_SEC = 10f DEFAULT_FRAME_WAIT_SEC:float const .. _global-imgui_playwright-READY_POLL_INTERVAL_MS: .. das:attribute:: READY_POLL_INTERVAL_MS = 0x64 READY_POLL_INTERVAL_MS:uint const .. _global-imgui_playwright-READY_POLL_REQUEST_TIMEOUT_SEC: .. das:attribute:: READY_POLL_REQUEST_TIMEOUT_SEC = 2 READY_POLL_REQUEST_TIMEOUT_SEC:int const .. _global-imgui_playwright-FRAME_POLL_INTERVAL_MS: .. das:attribute:: FRAME_POLL_INTERVAL_MS = 0x64 FRAME_POLL_INTERVAL_MS:uint const .. _global-imgui_playwright-RECORD_FPS: .. das:attribute:: RECORD_FPS = 30 RECORD_FPS:int const .. _global-imgui_playwright-NARRATE_READ_MS: .. das:attribute:: NARRATE_READ_MS = 0x1194 NARRATE_READ_MS:uint const .. _global-imgui_playwright-SETTLE_MS: .. das:attribute:: SETTLE_MS = 0x4b0 SETTLE_MS:uint const .. _global-imgui_playwright-CLICK_DWELL_MS: .. das:attribute:: CLICK_DWELL_MS = 0x5dc CLICK_DWELL_MS:uint const .. _global-imgui_playwright-KEY_TAB: .. das:attribute:: KEY_TAB = 512 KEY_TAB:int const .. _global-imgui_playwright-KEY_ENTER: .. das:attribute:: KEY_ENTER = 525 KEY_ENTER:int const .. _global-imgui_playwright-TYPE_LEAD_MIN_MS: .. das:attribute:: TYPE_LEAD_MIN_MS = 0x2bc TYPE_LEAD_MIN_MS:uint const .. _global-imgui_playwright-TYPE_LEAD_MAX_MS: .. das:attribute:: TYPE_LEAD_MAX_MS = 0xbb8 TYPE_LEAD_MAX_MS:uint const .. _global-imgui_playwright-TYPE_PER_CHAR_S: .. das:attribute:: TYPE_PER_CHAR_S = 0.16f TYPE_PER_CHAR_S:float const .. _global-imgui_playwright-TYPE_MIN_FIELD_S: .. das:attribute:: TYPE_MIN_FIELD_S = 0.9f TYPE_MIN_FIELD_S:float const .. _global-imgui_playwright-TYPE_FIELD_BEAT_MS: .. das:attribute:: TYPE_FIELD_BEAT_MS = 0xb4 TYPE_FIELD_BEAT_MS:uint const .. _global-imgui_playwright-CLICK_RELEASE_MS: .. das:attribute:: CLICK_RELEASE_MS = 130 CLICK_RELEASE_MS:int const .. _global-imgui_playwright-TYPE_EFFECT_FRAMES: .. das:attribute:: TYPE_EFFECT_FRAMES = 120 TYPE_EFFECT_FRAMES:int const .. _global-imgui_playwright-COMBO_LEAD_MIN_MS: .. das:attribute:: COMBO_LEAD_MIN_MS = 0x2bc COMBO_LEAD_MIN_MS:uint const .. _global-imgui_playwright-COMBO_LEAD_MAX_MS: .. das:attribute:: COMBO_LEAD_MAX_MS = 0xbb8 COMBO_LEAD_MAX_MS:uint const .. _global-imgui_playwright-COMBO_OPEN_INSET: .. das:attribute:: COMBO_OPEN_INSET = 40f COMBO_OPEN_INSET:float const .. _global-imgui_playwright-COMBO_TRAVEL_MS: .. das:attribute:: COMBO_TRAVEL_MS = 450 COMBO_TRAVEL_MS:int const .. _global-imgui_playwright-COMBO_POLL_FRAMES: .. das:attribute:: COMBO_POLL_FRAMES = 120 COMBO_POLL_FRAMES:int const .. _global-imgui_playwright-TREE_LEAD_MIN_MS: .. das:attribute:: TREE_LEAD_MIN_MS = 0x2bc TREE_LEAD_MIN_MS:uint const .. _global-imgui_playwright-TREE_LEAD_MAX_MS: .. das:attribute:: TREE_LEAD_MAX_MS = 0xbb8 TREE_LEAD_MAX_MS:uint const .. _global-imgui_playwright-TREE_ARROW_FRAC: .. das:attribute:: TREE_ARROW_FRAC = 0.6f TREE_ARROW_FRAC:float const .. _global-imgui_playwright-TREE_TRAVEL_MS: .. das:attribute:: TREE_TRAVEL_MS = 450 TREE_TRAVEL_MS:int const .. _global-imgui_playwright-TREE_POLL_FRAMES: .. das:attribute:: TREE_POLL_FRAMES = 120 TREE_POLL_FRAMES:int const .. _global-imgui_playwright-CLOSE_BTN_FRAC: .. das:attribute:: CLOSE_BTN_FRAC = 0.5f CLOSE_BTN_FRAC:float const .. _global-imgui_playwright-RECORD_ASSET_REL: .. das:attribute:: RECORD_ASSET_REL = "doc/source/_static/tutorials" RECORD_ASSET_REL:string const ++++++++++ Structures ++++++++++ .. _struct-imgui_playwright-ImguiApp: .. das:attribute:: ImguiApp struct ImguiApp .. _struct-imgui_playwright-VoEntry: .. das:attribute:: VoEntry :Fields: * **hash** : string - < say_hash(spoken) — links a narration line to its wav * **text** : string - < on-screen caption (the say() text arg) * **voice** : string - < spoken line fed to TTS (say() voice arg, or text if none) * **wav** : string - < wav basename under the voiceover/ dir * **dur_s** : float - < measured wav duration, seconds .. _struct-imgui_playwright-VoManifest: .. das:attribute:: VoManifest struct VoManifest .. _struct-imgui_playwright-VoSidecarHit: .. das:attribute:: VoSidecarHit :Fields: * **frame** : int - < recorded APNG frame index at the caption's appearance — the sync anchor * **t_s** : float - < wall-clock seconds from record_start (reference/debug; convert uses frame) * **wav** : string - < wav basename to mux * **text** : string - < narration line (for reference) .. _struct-imgui_playwright-VoSidecar: .. das:attribute:: VoSidecar struct VoSidecar +++++++++++++ App lifecycle +++++++++++++ * :ref:`with_imgui_app (feature_path: string; body: block\<(app:ImguiApp):void\>) ` * :ref:`with_imgui_app (feature_path: string; extra_module_roots: array\; feature_root: string; feature_prefix: string; body: block\<(app:ImguiApp):void\>) ` * :ref:`with_recording_app (feature_path: string; output_apng_basename: string; max_seconds: int; body: block\<(app:ImguiApp):void\>) ` * :ref:`with_recording_app (feature_path: string; output_apng_basename: string; max_seconds: int; fps: int; body: block\<(app:ImguiApp):void\>) ` * :ref:`with_recording_app (feature_path: string; output_apng_basename: string; max_seconds: int; fps: int; extra_module_roots: array\; feature_root: string; asset_root: string; body: block\<(app:ImguiApp):void\>) ` with_imgui_app ^^^^^^^^^^^^^^ .. _function-imgui_playwright_with_imgui_app_string_block_ls_app_c_ImguiApp_c_void_gr_: .. das:function:: with_imgui_app(feature_path: string; body: block<(app:ImguiApp):void>) Spawn ``daslang-live `` with dasImgui loaded; run ``body`` while alive, then POST ``/shutdown`` and drain stdout. Panics on non-zero exit / ready-timeout / test-timeout. The common case (require chain needs ONLY dasImgui); delegates to the 5-arg overload. :Arguments: * **feature_path** : string * **body** : block<(app: :ref:`ImguiApp `):void> .. _function-imgui_playwright_with_imgui_app_string_array_ls_string_gr__string_string_block_ls_app_c_ImguiApp_c_void_gr_: .. das:function:: with_imgui_app(feature_path: string; extra_module_roots: array; feature_root: string; feature_prefix: string; body: block<(app:ImguiApp):void>) ---- with_recording_app ^^^^^^^^^^^^^^^^^^ .. _function-imgui_playwright_with_recording_app_string_string_int_block_ls_app_c_ImguiApp_c_void_gr_: .. das:function:: with_recording_app(feature_path: string; output_apng_basename: string; max_seconds: int; body: block<(app:ImguiApp):void>) Native-fps form (the common case). Delegates to the 4-arg overload — gen2's trailing-block sugar can't see through a default parameter between the last positional and the block, so the overload pair keeps the common call site clean. :Arguments: * **feature_path** : string * **output_apng_basename** : string * **max_seconds** : int * **body** : block<(app: :ref:`ImguiApp `):void> .. _function-imgui_playwright_with_recording_app_string_string_int_int_block_ls_app_c_ImguiApp_c_void_gr_: .. das:function:: with_recording_app(feature_path: string; output_apng_basename: string; max_seconds: int; fps: int; body: block<(app:ImguiApp):void>) .. _function-imgui_playwright_with_recording_app_string_string_int_int_array_ls_string_gr__string_string_block_ls_app_c_ImguiApp_c_void_gr_: .. das:function:: with_recording_app(feature_path: string; output_apng_basename: string; max_seconds: int; fps: int; extra_module_roots: array; feature_root: string; asset_root: string; body: block<(app:ImguiApp):void>) ++++++++++++++++++ Locators / queries ++++++++++++++++++ * :ref:`find_widget (var snap: JsonValue?; ident: string) : JsonValue? ` * :ref:`get_text (app: ImguiApp; uri: string) : string ` * :ref:`widget_click_point (var snap: JsonValue?; ident: string) : tuple\ ` * :ref:`widget_component_point (var snap: JsonValue?; ident: string; component: int) : tuple\ ` * :ref:`widget_exists (var snap: JsonValue?; ident: string) : bool ` * :ref:`widget_payload_field (var snap: JsonValue?; ident: string; field: string) : JsonValue? ` * :ref:`widget_rendered (var snap: JsonValue?; ident: string) : bool ` .. _function-imgui_playwright_find_widget_JsonValue_q__string: .. das:function:: find_widget(snap: JsonValue?; ident: string) : JsonValue? Navigate to ``snapshot.globals[ident]``; may return a ``JV(null)`` placeholder for missing keys (daslib/json ``?[]`` quirk). For an existence check use ``widget_exists``. :Arguments: * **snap** : :ref:`JsonValue `? * **ident** : string .. _function-imgui_playwright_get_text_ImguiApp_string: .. das:function:: get_text(app: ImguiApp; uri: string) : string GET `uri`; return the raw response body or "" (bounded by the per-request timeout). :Arguments: * **app** : :ref:`ImguiApp ` * **uri** : string .. _function-imgui_playwright_widget_click_point_JsonValue_q__string: .. das:function:: widget_click_point(snap: JsonValue?; ident: string) : tuple The point ``imgui_click`` aims at for ``ident``: the left glyph for the toggle family (checkbox / radio_button / radio_button_int), the bbox center otherwise. Mirrors ``resolve_target_bbox`` in imgui_boost_runtime — keep the kind list in sync with that resolver. :Arguments: * **snap** : :ref:`JsonValue `? * **ident** : string .. _function-imgui_playwright_widget_component_point_JsonValue_q__string_int: .. das:function:: widget_component_point(snap: JsonValue?; ident: string; component: int) : tuple Center of multi-component widget ``ident``'s sub-rect ``component`` (slider/drag _2/3/4, drag_*_range2), read from ``sub_bboxes`` telemetry — the deterministic way to target one handle of a vector/range widget. Falls back to ``widget_click_point`` when ``sub_bboxes`` is absent. :Arguments: * **snap** : :ref:`JsonValue `? * **ident** : string * **component** : int .. _function-imgui_playwright_widget_exists_JsonValue_q__string: .. das:function:: widget_exists(snap: JsonValue?; ident: string) : bool True iff ``globals[ident]`` is a real widget entry — works around the daslib/json ``?[]`` quirk by checking the always-present ``kind`` field. :Arguments: * **snap** : :ref:`JsonValue `? * **ident** : string .. _function-imgui_playwright_widget_payload_field_JsonValue_q__string_string: .. das:function:: widget_payload_field(snap: JsonValue?; ident: string; field: string) : JsonValue? Navigate to ``snapshot.globals[ident].payload[field]``; returns null if any link is missing. :Arguments: * **snap** : :ref:`JsonValue `? * **ident** : string * **field** : string .. _function-imgui_playwright_widget_rendered_JsonValue_q__string: .. das:function:: widget_rendered(snap: JsonValue?; ident: string) : bool True iff ``globals[ident]`` EXISTS and painted this frame. Unpainted entries carry ``"rendered": false``; painted widgets omit it (``?? true``). The leading existence check is load-bearing: an absent/misspelled path yields ``null ?? true`` — a false pass — so it must report not-rendered. :Arguments: * **snap** : :ref:`JsonValue `? * **ident** : string +++++++ Actions +++++++ * :ref:`click (app: ImguiApp; target: string) : JsonValue? ` * :ref:`click_at (var events: array\; t_ms: int; pos: tuple\; travel_ms: int = 500; button: int = 0) ` * :ref:`close_widget (app: ImguiApp; target: string) : JsonValue? ` * :ref:`double_click (app: ImguiApp; target: string) : JsonValue? ` * :ref:`drag (app: ImguiApp; target: string; dx: float; dy: float; steps: int = 4; button: int = 0) : JsonValue? ` * :ref:`drag_along (var events: array\; t_ms: int; from_pos: tuple\; to_pos: tuple\; drag_ms: int; approach_ms: int = 400; button: int = 0) ` * :ref:`drag_to (app: ImguiApp; source: string; target: string; steps: int = 6; button: int = 0) : JsonValue? ` * :ref:`focus (app: ImguiApp; target: string) : JsonValue? ` * :ref:`force_set_value (app: ImguiApp; target: string; var value: JsonValue?) : JsonValue? ` * :ref:`force_set_verified (app: ImguiApp; target: string; value: auto(T); max_frames: int = 120) : auto ` * :ref:`key_tap (app: ImguiApp; key: int; hold_frames: int = 2) ` * :ref:`move_to (app: ImguiApp; pos: tuple\; duration_ms: int = 600) ` * :ref:`open_widget (app: ImguiApp; target: string) : JsonValue? ` * :ref:`reload (app: ImguiApp; timeout_sec: float = DEFAULT_RELOAD_TIMEOUT_SEC) ` * :ref:`reset (app: ImguiApp; timeout_sec: float = DEFAULT_RELOAD_TIMEOUT_SEC) ` * :ref:`reset_cursor_pos (pos: tuple\ = tuple\\>(0f,0f)) ` * :ref:`resize_window (app: ImguiApp; target: string; w: float; h: float) : JsonValue? ` * :ref:`right_click (app: ImguiApp; target: string) : JsonValue? ` * :ref:`select_widget (app: ImguiApp; target: string) : JsonValue? ` * :ref:`type_text (app: ImguiApp; target: string; text: string; focus_max_frames: int = 120; duration_s: float = 0f) : JsonValue? ` .. _function-imgui_playwright_click_ImguiApp_string: .. das:function:: click(app: ImguiApp; target: string) : JsonValue? Synthesize a left-click on ``target`` (``imgui_click`` verb). :Arguments: * **app** : :ref:`ImguiApp ` * **target** : string .. _function-imgui_playwright_click_at_array_ls_JsonValue_q__gr__int_tuple_ls_float;float_gr__int_int: .. das:function:: click_at(events: array; t_ms: int; pos: tuple; travel_ms: int = 500; button: int = 0) Append lerp-from-last + press + release + dwell to ``events``: cursor travels from the tracked previous position to ``pos`` over ``travel_ms``, then button ``button`` is pressed and released. Updates the tracked position. :Arguments: * **events** : array< :ref:`JsonValue `?> * **t_ms** : int * **pos** : tuple * **travel_ms** : int * **button** : int .. _function-imgui_playwright_close_widget_ImguiApp_string: .. das:function:: close_widget(app: ImguiApp; target: string) : JsonValue? Flip ``target.state.open = false`` (``imgui_close`` verb) — closes a window/tree node from the test driver. :Arguments: * **app** : :ref:`ImguiApp ` * **target** : string .. _function-imgui_playwright_double_click_ImguiApp_string: .. das:function:: double_click(app: ImguiApp; target: string) : JsonValue? Faithful double-click on ``target``'s bbox center: two press/release taps so ``IsMouseDoubleClicked`` fires. Frame-paced (``t_ms`` = frame index): the host advances one event per frame, so the two taps land on distinct frames — never collapsed into one the way a wall-clock timeline is on a slow host (which drops the double-click). :Arguments: * **app** : :ref:`ImguiApp ` * **target** : string .. _function-imgui_playwright_drag_ImguiApp_string_float_float_int_int: .. das:function:: drag(app: ImguiApp; target: string; dx: float; dy: float; steps: int = 4; button: int = 0) : JsonValue? Synthesize a mouse-drag on ``target``'s bbox center: warp → press → drag to ``center+(dx,dy)`` → release, as an ``imgui_mouse_play`` timeline (bbox resolved client-side). Returns ``{ok, value}`` (``ok=false`` if the target is absent/unrendered — else it would drag from a defaulted (0,0) bbox). ``steps`` scales duration. :Arguments: * **app** : :ref:`ImguiApp ` * **target** : string * **dx** : float * **dy** : float * **steps** : int * **button** : int .. _function-imgui_playwright_drag_along_array_ls_JsonValue_q__gr__int_tuple_ls_float;float_gr__tuple_ls_float;float_gr__int_int_int: .. das:function:: drag_along(events: array; t_ms: int; from_pos: tuple; to_pos: tuple; drag_ms: int; approach_ms: int = 400; button: int = 0) Append lerp-to-start + press + drag + release + dwell to ``events``: cursor travels from the tracked position to ``from_pos`` over ``approach_ms``, then presses and drags to ``to_pos`` over ``drag_ms``. Updates the tracked position to ``to_pos``. :Arguments: * **events** : array< :ref:`JsonValue `?> * **t_ms** : int * **from_pos** : tuple * **to_pos** : tuple * **drag_ms** : int * **approach_ms** : int * **button** : int .. _function-imgui_playwright_drag_to_ImguiApp_string_string_int_int: .. das:function:: drag_to(app: ImguiApp; source: string; target: string; steps: int = 6; button: int = 0) : JsonValue? Drag from ``source`` widget center to ``target`` widget center. Reads both bboxes from a fresh snapshot, computes the delta, and dispatches the L1 drag coroutine (press at source → interpolated moves → release). :Arguments: * **app** : :ref:`ImguiApp ` * **source** : string * **target** : string * **steps** : int * **button** : int .. _function-imgui_playwright_focus_ImguiApp_string: .. das:function:: focus(app: ImguiApp; target: string) : JsonValue? Force keyboard focus to the next render of ``target`` (``imgui_focus`` verb); display-only widgets reply ``Result.err("not focusable")``. :Arguments: * **app** : :ref:`ImguiApp ` * **target** : string .. _function-imgui_playwright_force_set_value_ImguiApp_string_JsonValue_q_: .. das:function:: force_set_value(app: ImguiApp; target: string; value: JsonValue?) : JsonValue? Set ``target``'s payload value remotely (``imgui_force_set`` verb); ``value`` is the JSON form expected by the widget kind. :Arguments: * **app** : :ref:`ImguiApp ` * **target** : string * **value** : :ref:`JsonValue `? .. _function-imgui_playwright_force_set_verified_ImguiApp_string_autoT_int_0x208: .. das:function:: force_set_verified(app: ImguiApp; target: string; value: auto(T); max_frames: int = 120) : auto Recording-as-test ``force_set``: set ``target.value`` to the typed ``value`` and verify it took, accumulating a miss (surfaced at ``with_recording_app`` teardown) rather than panicking mid-body. Use only inside a ``with_recording_app`` body — raw tests want the panicking ``expect_value``. :Arguments: * **app** : :ref:`ImguiApp ` * **target** : string * **value** : auto(T) * **max_frames** : int .. _function-imgui_playwright_key_tap_ImguiApp_int_int: .. das:function:: key_tap(app: ImguiApp; key: int; hold_frames: int = 2) Synthesize a discrete press+release of an ImGuiKey by integer code, FRAME-PACED: held for ``hold_frames`` frames then released via ``imgui_key_play``, so it fires exactly once regardless of frame rate (a wall-clock hold crosses ImGui's key-repeat threshold on a slow frame and over-fires). Use ``int(ImGuiKey.Tab)`` etc. :Arguments: * **app** : :ref:`ImguiApp ` * **key** : int * **hold_frames** : int .. _function-imgui_playwright_move_to_ImguiApp_tuple_ls_float;float_gr__int: .. das:function:: move_to(app: ImguiApp; pos: tuple; duration_ms: int = 600) Post a lerped cursor move from the last tracked position to ``pos`` (updates the tracked position). ``duration_ms`` is a FLOOR: actual duration scales with distance (≈0.325 px/ms, constant cursor speed) so a long move doesn't cover its span as fast as a short one and read as a teleport. :Arguments: * **app** : :ref:`ImguiApp ` * **pos** : tuple * **duration_ms** : int .. _function-imgui_playwright_open_widget_ImguiApp_string: .. das:function:: open_widget(app: ImguiApp; target: string) : JsonValue? Flip ``target.state.open = true`` (``imgui_open`` verb) — reopens a window/tree node from the test driver. :Arguments: * **app** : :ref:`ImguiApp ` * **target** : string .. _function-imgui_playwright_reload_ImguiApp_float: .. das:function:: reload(app: ImguiApp; timeout_sec: float = DEFAULT_RELOAD_TIMEOUT_SEC) POST ``/reload`` then poll ``/status`` until ``has_error == false``; panics on timeout. Stays on raw HTTP — reload is daslang-live process control, not a dasImgui command verb. :Arguments: * **app** : :ref:`ImguiApp ` * **timeout_sec** : float .. _function-imgui_playwright_reset_ImguiApp_float: .. das:function:: reset(app: ImguiApp; timeout_sec: float = DEFAULT_RELOAD_TIMEOUT_SEC) POST ``/reset`` — re-simulate the already-compiled program into a fresh context (no parse/infer), clearing ``@live`` state. ~100x cheaper than ``reload``; use between subtests instead of respawning. Polls ``/status`` until ``generation`` advances; panics if no baseline generation, has_error, or timeout. :Arguments: * **app** : :ref:`ImguiApp ` * **timeout_sec** : float .. _function-imgui_playwright_reset_cursor_pos_tuple_ls_float;float_gr_: .. das:function:: reset_cursor_pos(pos: tuple = tuple>(0f,0f)) Reset the tracked previous cursor position. Call between recordings if the host's synth cursor state was already non-(0,0). :Arguments: * **pos** : tuple .. _function-imgui_playwright_resize_window_ImguiApp_string_float_float: .. das:function:: resize_window(app: ImguiApp; target: string; w: float; h: float) : JsonValue? Resize the window named by ``target`` to (``w``, ``h``) on the next frame, via ``imgui_set_window_size`` (feeds ``state.pending_size`` → ``SetNextWindowSize``). Named ``resize_window`` (not ``set_window_size``) to avoid the overload collision with imgui_containers_builtin's in-frame form. :Arguments: * **app** : :ref:`ImguiApp ` * **target** : string * **w** : float * **h** : float .. _function-imgui_playwright_right_click_ImguiApp_string: .. das:function:: right_click(app: ImguiApp; target: string) : JsonValue? Synthesize a right-click on ``target`` (``imgui_click`` verb with ``button = 1``). Used by popup_context_item / popup_context_window drivers. :Arguments: * **app** : :ref:`ImguiApp ` * **target** : string .. _function-imgui_playwright_select_widget_ImguiApp_string: .. das:function:: select_widget(app: ImguiApp; target: string) : JsonValue? Activate ``target`` tab_item (``imgui_select`` verb) — the automation counterpart of clicking a tab (ImGui owns active-tab selection, so an inactive tab can't be reached by ``click``). :Arguments: * **app** : :ref:`ImguiApp ` * **target** : string .. _function-imgui_playwright_type_text_ImguiApp_string_string_int_float: .. das:function:: type_text(app: ImguiApp; target: string; text: string; focus_max_frames: int = 120; duration_s: float = 0f) : JsonValue? Auto-focus ``target``, wait up to ``focus_max_frames`` for focus, then stream ``text`` as synthetic key+char events via ``imgui_key_type`` (into whichever widget holds focus). Returns ``{ok, value}`` (``ok=false`` on empty text or if focus never settles). ``duration_s > 0`` paces typing over wall-clock. :Arguments: * **app** : :ref:`ImguiApp ` * **target** : string * **text** : string * **focus_max_frames** : int * **duration_s** : float +++++++++++++++++++++ Recording / narration +++++++++++++++++++++ * :ref:`click_render_voice (app: ImguiApp; dwell: uint; click_target: string; watch_target: string; expect_rendered: bool) ` * :ref:`close_button_voice (app: ImguiApp; dwell: uint; header_target: string; child_target: string) ` * :ref:`combo_pick_voice (app: ImguiApp; dwell: uint; target: string; item: int) ` * :ref:`drag_through_voice (app: ImguiApp; dwell: uint; target: string; to_pos: tuple\; drag_ms: int = 0; field: string = "value") ` * :ref:`hold_through_voice (app: ImguiApp; dwell: uint; clicks: array\) ` * :ref:`menu_pick_voice (app: ImguiApp; dwell: uint; header_target: string; item_target: string) ` * :ref:`record_check (app: ImguiApp; desc: string; ok: bool) : bool ` * :ref:`record_check_changed (app: ImguiApp; target: string; field: string; before: string; max_frames: int = 120) : bool ` * :ref:`record_check_kind_count (app: ImguiApp; kind_prefix: string; min_count: int; max_frames: int = 120) : bool ` * :ref:`record_check_rendered (app: ImguiApp; target: string; expected: bool; max_frames: int = 120) : bool ` * :ref:`record_check_unchanged (app: ImguiApp; target: string; field: string; before: string) : bool ` * :ref:`record_check_value (app: ImguiApp; target: string; field: string; expected: auto(T); max_frames: int = 120) : bool ` * :ref:`say (app: ImguiApp; text: string; target: string = ""; read_ms: uint = NARRATE_READ_MS; voice: string = "") ` * :ref:`say (app: ImguiApp; text: string; targets: array\; read_ms: uint = NARRATE_READ_MS; voice: string = "") ` * :ref:`say_begin (app: ImguiApp; text: string; target: string = ""; read_ms: uint = NARRATE_READ_MS; voice: string = "") : uint ` * :ref:`say_begin (app: ImguiApp; text: string; targets: array\; read_ms: uint = NARRATE_READ_MS; voice: string = "") : uint ` * :ref:`say_hash (text: string) : string ` * :ref:`toggle_tree_voice (app: ImguiApp; dwell: uint; header_target: string; on_arrow: bool; child_target: string; expect_child_rendered: bool) ` * :ref:`type_into_voice (app: ImguiApp; dwell: uint; target: string; click_pos: tuple\; cells: array\; expected: auto(T)) : auto ` .. _function-imgui_playwright_click_render_voice_ImguiApp_uint_string_string_bool: .. das:function:: click_render_voice(app: ImguiApp; dwell: uint; click_target: string; watch_target: string; expect_rendered: bool) Real-click ``click_target`` under a voice line, then assert ``watch_target``'s rendered state == ``expect_rendered`` — the reveal (true) / dismiss (false) rail. A button whose own click closes the popup can't be verified by click-count (gone next frame); the watch-target's render state is the durable signal. :Arguments: * **app** : :ref:`ImguiApp ` * **dwell** : uint * **click_target** : string * **watch_target** : string * **expect_rendered** : bool .. _function-imgui_playwright_close_button_voice_ImguiApp_uint_string_string: .. das:function:: close_button_voice(app: ImguiApp; dwell: uint; header_target: string; child_target: string) Click the X-button of a CLOSABLE container header under a voice line, then assert the strip vanished (``child_target`` no longer rendered). The X sits a half header-height in from the header's right edge (mirrors ImGui's layout). Self-verifying at teardown; caller must ``move_to`` the header first. :Arguments: * **app** : :ref:`ImguiApp ` * **dwell** : uint * **header_target** : string * **child_target** : string .. _function-imgui_playwright_combo_pick_voice_ImguiApp_uint_string_int: .. das:function:: combo_pick_voice(app: ImguiApp; dwell: uint; target: string; item: int) Open combo ``target`` and click item ``item`` from its popup under a voice line, then assert ``target.value == item``. Clicks the box (inset from the left, off the label), POLLS until the popup renders per-item ``sub_bboxes``, then clicks item ``item``. Self-verifying at teardown. :Arguments: * **app** : :ref:`ImguiApp ` * **dwell** : uint * **target** : string * **item** : int .. _function-imgui_playwright_drag_through_voice_ImguiApp_uint_string_tuple_ls_float;float_gr__int_string: .. das:function:: drag_through_voice(app: ImguiApp; dwell: uint; target: string; to_pos: tuple; drag_ms: int = 0; field: string = "value") Drag analog of ``hold_through_voice``: scrub from the tracked cursor to ``to_pos`` under a voice line, then assert the drag CHANGED ``target.payload[field]`` (a no-op drag aborts at teardown). The caller must ``move_to`` the handle first. ``field`` defaults to ``"value"``; docking passes ``"size"``/``"dock_id"``. :Arguments: * **app** : :ref:`ImguiApp ` * **dwell** : uint * **target** : string * **to_pos** : tuple * **drag_ms** : int * **field** : string .. _function-imgui_playwright_hold_through_voice_ImguiApp_uint_array_ls_string_gr_: .. das:function:: hold_through_voice(app: ImguiApp; dwell: uint; clicks: array) Pace the click(s) in ``clicks`` UNDER an already-posted voice line: a short lead, then each click lands (verified, or the recording aborts), then hold the caption for the rest. ``dwell`` is ``say_begin``'s return; the caller must have moved the cursor onto the first target. :Arguments: * **app** : :ref:`ImguiApp ` * **dwell** : uint * **clicks** : array .. _function-imgui_playwright_menu_pick_voice_ImguiApp_uint_string_string: .. das:function:: menu_pick_voice(app: ImguiApp; dwell: uint; header_target: string; item_target: string) Open a menu-bar menu (``header_target``) and pick ``item_target`` under a voice line, navigating like a human: click the header, then drop STRAIGHT DOWN the column. A diagonal click would cross a SIBLING header mid-travel and ImGui hover-switches the open menu, landing the click in the wrong one. Self-verifying at teardown. :Arguments: * **app** : :ref:`ImguiApp ` * **dwell** : uint * **header_target** : string * **item_target** : string .. _function-imgui_playwright_record_check_ImguiApp_string_bool: .. das:function:: record_check(app: ImguiApp; desc: string; ok: bool) : bool Recording-safe verifier for a condition the CALLER already evaluated — the escape hatch for an effect the field-based record_check_* can't reach (a value nested in a payload ARRAY, a cross-widget invariant). Pass the bool you computed; on false it accumulates ``desc`` into the failure ledger. Returns ``ok``. :Arguments: * **app** : :ref:`ImguiApp ` * **desc** : string * **ok** : bool .. _function-imgui_playwright_record_check_changed_ImguiApp_string_string_string_int: .. das:function:: record_check_changed(app: ImguiApp; target: string; field: string; before: string; max_frames: int = 120) : bool Recording-safe "the gesture moved it" verifier: assert ``target.payload[field]``'s JSON now DIFFERS from ``before`` within ``max_frames`` — the honest self-test for a continuous drag (a human drag lands on no exact pixel, so assert CHANGED not equals). Accumulate-on-miss; returns true once it differs. :Arguments: * **app** : :ref:`ImguiApp ` * **target** : string * **field** : string * **before** : string * **max_frames** : int .. _function-imgui_playwright_record_check_kind_count_ImguiApp_string_int_int: .. das:function:: record_check_kind_count(app: ImguiApp; kind_prefix: string; min_count: int; max_frames: int = 120) : bool Recording-safe "did the rail paint" verifier for entries under SYNTHESIZED path keys (drawlist primitives, kind ``"add_*"``): assert the registry holds ≥ ``min_count`` entries whose ``kind`` starts with ``kind_prefix`` — the only honest signal when the call sites have no stable ident. Accumulate-on-miss. :Arguments: * **app** : :ref:`ImguiApp ` * **kind_prefix** : string * **min_count** : int * **max_frames** : int .. _function-imgui_playwright_record_check_rendered_ImguiApp_string_bool_int: .. das:function:: record_check_rendered(app: ImguiApp; target: string; expected: bool; max_frames: int = 120) : bool Recording-safe "did the gesture show/hide it" verifier: assert ``target``'s rendered-this-frame state (``widget_rendered``) equals ``expected`` — the robust open/close signal for a container (collapsing a tree flips its child to ``rendered:false``), readable even when ``opened``/``flags`` are absent. Accumulate-on-miss. :Arguments: * **app** : :ref:`ImguiApp ` * **target** : string * **expected** : bool * **max_frames** : int .. _function-imgui_playwright_record_check_unchanged_ImguiApp_string_string_string: .. das:function:: record_check_unchanged(app: ImguiApp; target: string; field: string; before: string) : bool Recording-safe "the gesture left it alone" verifier — mirror of ``record_check_changed``: assert ``target.payload[field]`` still EQUALS ``before``, the honest self-test for a NEGATIVE claim. No event to wait for, so the caller must settle first (``wait_for_mouse_idle``); compares once. Accumulate-on-miss. :Arguments: * **app** : :ref:`ImguiApp ` * **target** : string * **field** : string * **before** : string .. _function-imgui_playwright_record_check_value_ImguiApp_string_string_autoT_int_0x2a3: .. das:function:: record_check_value(app: ImguiApp; target: string; field: string; expected: auto(T); max_frames: int = 120) : bool Recording-safe verifier: assert ``target.payload[field] == expected`` within ``max_frames``, accumulating a miss instead of panicking (a mid-body panic would skip record_stop + /shutdown and orphan the host). The public rail for self-verifying a NON-CLICK effect; returns true on match. :Arguments: * **app** : :ref:`ImguiApp ` * **target** : string * **field** : string * **expected** : auto(T) * **max_frames** : int say ^^^ .. _function-imgui_playwright_say_ImguiApp_string_string_uint_string: .. das:function:: say(app: ImguiApp; text: string; target: string = ""; read_ms: uint = NARRATE_READ_MS; voice: string = "") Pop a narration overlay (``imgui_narrate`` verb), then hold the dwell so the recorder captures the window. Named ``say`` (not ``narrate``) to avoid overload churn with imgui_visual_aids::narrate. For a click/drag that must land under the voice, use ``say_begin`` + a manual dwell split. :Arguments: * **app** : :ref:`ImguiApp ` * **text** : string * **target** : string * **read_ms** : uint * **voice** : string .. _function-imgui_playwright_say_ImguiApp_string_array_ls_string_gr__uint_string: .. das:function:: say(app: ImguiApp; text: string; targets: array; read_ms: uint = NARRATE_READ_MS; voice: string = "") ---- say_begin ^^^^^^^^^ .. _function-imgui_playwright_say_begin_ImguiApp_string_string_uint_string: .. das:function:: say_begin(app: ImguiApp; text: string; target: string = ""; read_ms: uint = NARRATE_READ_MS; voice: string = "") : uint Non-blocking ``say``: post the narration overlay (+ record the voiceover anchor) and RETURN the dwell in ms WITHOUT holding — the caller splits it around a ``click``/``drag`` so the action lands under the voice. ``text`` is the caption; ``voice`` (when set) is the spoken line; use ``say`` to post+hold in one call. :Arguments: * **app** : :ref:`ImguiApp ` * **text** : string * **target** : string * **read_ms** : uint * **voice** : string .. _function-imgui_playwright_say_begin_ImguiApp_string_array_ls_string_gr__uint_string: .. das:function:: say_begin(app: ImguiApp; text: string; targets: array; read_ms: uint = NARRATE_READ_MS; voice: string = "") : uint ---- .. _function-imgui_playwright_say_hash_string: .. das:function:: say_hash(text: string) : string Stable wav key shared by prepare_recording.das and say(); hash() is deterministic. :Arguments: * **text** : string .. _function-imgui_playwright_toggle_tree_voice_ImguiApp_uint_string_bool_string_bool: .. das:function:: toggle_tree_voice(app: ImguiApp; dwell: uint; header_target: string; on_arrow: bool; child_target: string; expect_child_rendered: bool) Click a ``tree_node``/``collapsing_header`` header under a voice line, then assert the toggle via ``child_target``'s rendered state (a collapsed node stops drawing children). ``on_arrow=true`` clicks the chevron, ``on_arrow=false`` the label — the ``OpenOnArrow`` distinction (arrow toggles, label no-op). Self-verifying at teardown. :Arguments: * **app** : :ref:`ImguiApp ` * **dwell** : uint * **header_target** : string * **on_arrow** : bool * **child_target** : string * **expect_child_rendered** : bool .. _function-imgui_playwright_type_into_voice_ImguiApp_uint_string_tuple_ls_float;float_gr__array_ls_string_gr__autoT_0x41a: .. das:function:: type_into_voice(app: ImguiApp; dwell: uint; target: string; click_pos: tuple; cells: array; expected: auto(T)) : auto Type analog of drag_through_voice: REAL keyboard edit of ``target``, assert committed == ``expected``. CLICKS ``click_pos`` (not imgui_focus) because InputScalar's AutoSelectAll fires only on activation, so the click focuses AND selects → typing REPLACES. Per ``cells``: type, Tab between, Enter; caller must ``move_to`` ``click_pos`` first. :Arguments: * **app** : :ref:`ImguiApp ` * **dwell** : uint * **target** : string * **click_pos** : tuple * **cells** : array * **expected** : auto(T) +++++++++++++++++++ Content-time gating +++++++++++++++++++ * :ref:`hold_content (app: ImguiApp; ms: uint) ` * :ref:`hold_remainder_content (app: ImguiApp; dwell_ms: uint; start_frames: int) ` * :ref:`record_frame_count (app: ImguiApp) : int ` * :ref:`wait_content_frames (app: ImguiApp; frames: int) ` .. _function-imgui_playwright_hold_content_ImguiApp_uint: .. das:function:: hold_content(app: ImguiApp; ms: uint) Hold for `ms` of CONTENT time: under lockstep recording wait for ms/1000*fps captured frames; otherwise a plain wall-clock sleep(ms). :Arguments: * **app** : :ref:`ImguiApp ` * **ms** : uint .. _function-imgui_playwright_hold_remainder_content_ImguiApp_uint_int: .. das:function:: hold_remainder_content(app: ImguiApp; dwell_ms: uint; start_frames: int) Pad a beat to `dwell_ms` of CONTENT time: wait until the recorder reaches start_frames + dwell_ms/1000*fps (start_frames from record_frame_count right after say_begin). Work done mid-dwell consumes content frames; this holds the rest. No-op when idle. :Arguments: * **app** : :ref:`ImguiApp ` * **dwell_ms** : uint * **start_frames** : int .. _function-imgui_playwright_record_frame_count_ImguiApp: .. das:function:: record_frame_count(app: ImguiApp) : int Current captured-frame count (content-time index). Capture right after say_begin to anchor a hold_remainder_content. Returns 0 outside a recording session. :Arguments: * **app** : :ref:`ImguiApp ` .. _function-imgui_playwright_wait_content_frames_ImguiApp_int: .. das:function:: wait_content_frames(app: ImguiApp; frames: int) Block until the recorder captures `frames` more frames (= frames/fps s of video); no-op outside a recording session. Anchored on the first VALID status read (a null keeps polling, never ends early). If capture stalls for RECORD_STALL_US, degrade to a wall-clock sleep so the beat lands and CI never hangs. :Arguments: * **app** : :ref:`ImguiApp ` * **frames** : int +++++++++ Snapshots +++++++++ * :ref:`expect_render (app: ImguiApp; widget_ident: string; timeout_sec: float = DEFAULT_FRAME_WAIT_SEC) ` * :ref:`expect_value (app: ImguiApp; target: string; field: string; expected: auto(T); max_frames: int = 300) ` * :ref:`log_snapshot (app: ImguiApp; note: string = "") ` * :ref:`snapshot (app: ImguiApp) : JsonValue? ` .. _function-imgui_playwright_expect_render_ImguiApp_string_float: .. das:function:: expect_render(app: ImguiApp; widget_ident: string; timeout_sec: float = DEFAULT_FRAME_WAIT_SEC) Assertion form of ``wait_for_render``; panics on timeout with the list of currently-registered widget identifiers (so typos in ``widget_ident`` surface). :Arguments: * **app** : :ref:`ImguiApp ` * **widget_ident** : string * **timeout_sec** : float .. _function-imgui_playwright_expect_value_ImguiApp_string_string_autoT_int_0x1b8: .. das:function:: expect_value(app: ImguiApp; target: string; field: string; expected: auto(T); max_frames: int = 300) Assertion form of ``wait_for_payload_value``; panics with a focused want/got/kind/rendered message on timeout. :Arguments: * **app** : :ref:`ImguiApp ` * **target** : string * **field** : string * **expected** : auto(T) * **max_frames** : int .. _function-imgui_playwright_log_snapshot_ImguiApp_string: .. das:function:: log_snapshot(app: ImguiApp; note: string = "") Dump the live UI state to the log (LOG_WARNING) — per registered widget: ident, kind, ``rendered`` flag, bbox, and ``payload.value``. The way to see what a headless/CI run actually painted; pair it with a failed assertion so a red run is self-explaining. :Arguments: * **app** : :ref:`ImguiApp ` * **note** : string .. _function-imgui_playwright_snapshot_ImguiApp: .. das:function:: snapshot(app: ImguiApp) : JsonValue? Fetch the full widget snapshot (``imgui_snapshot`` command) as a parsed ``JsonValue``. :Arguments: * **app** : :ref:`ImguiApp ` +++++++++++++++ Polling / await +++++++++++++++ * :ref:`auto_dump_on_timeout_enabled () : bool ` * :ref:`await_probe (app: ImguiApp; until: string = "quiescent") : JsonValue? ` * :ref:`await_quiescent (app: ImguiApp; timeout_sec: float = 5f) ` * :ref:`set_auto_dump_on_timeout (enabled: bool) ` * :ref:`wait_cond (app: ImguiApp; timeout_sec: float; pred: block\<():void\>) : bool ` * :ref:`wait_for_bool_value (app: ImguiApp; target: string; field: string; expected: bool; max_frames: int = 300) : bool ` * :ref:`wait_for_hover (app: ImguiApp; widget_ident: string; expected: bool = true; timeout_sec: float = DEFAULT_FRAME_WAIT_SEC) : JsonValue? ` * :ref:`wait_for_int_value (app: ImguiApp; target: string; field: string; expected: int; max_frames: int = 300) : bool ` * :ref:`wait_for_key_idle (app: ImguiApp; timeout_sec: float = DEFAULT_FRAME_WAIT_SEC) : bool ` * :ref:`wait_for_mouse_idle (app: ImguiApp; timeout_sec: float = DEFAULT_FRAME_WAIT_SEC) : bool ` * :ref:`wait_for_payload_value (app: ImguiApp; target: string; field: string; expected: auto(T); max_frames: int = 300) : bool ` * :ref:`wait_for_render (app: ImguiApp; widget_ident: string; timeout_sec: float = DEFAULT_FRAME_WAIT_SEC) : JsonValue? ` * :ref:`wait_for_string_value (app: ImguiApp; target: string; field: string; expected: string; max_frames: int = 300) : bool ` * :ref:`wait_for_visible (app: ImguiApp; widget_ident: string; timeout_sec: float = DEFAULT_FRAME_WAIT_SEC) : JsonValue? ` * :ref:`wait_for_widget (app: ImguiApp; widget_ident: string; timeout_sec: float = DEFAULT_FRAME_WAIT_SEC) : JsonValue? ` * :ref:`wait_until (app: ImguiApp; max_frames: int; pred: block\<(var snap:JsonValue?):bool\>) : JsonValue? ` * :ref:`wait_until_ready (app: ImguiApp; timeout_sec: float) : bool ` * :ref:`wait_until_sec (app: ImguiApp; timeout_sec: float; pred: block\<(var snap:JsonValue?):bool\>) : JsonValue? ` .. _function-imgui_playwright_auto_dump_on_timeout_enabled: .. das:function:: auto_dump_on_timeout_enabled() : bool def auto_dump_on_timeout_enabled () : bool .. _function-imgui_playwright_await_probe_ImguiApp_string: .. das:function:: await_probe(app: ImguiApp; until: string = "quiescent") : JsonValue? Single ``imgui_await`` POST; the ``AwaitResult`` lives under ``response.value``. For poll-until-quiescent use ``await_quiescent``. :Arguments: * **app** : :ref:`ImguiApp ` * **until** : string .. _function-imgui_playwright_await_quiescent_ImguiApp_float: .. das:function:: await_quiescent(app: ImguiApp; timeout_sec: float = 5f) Assertion wrapper over ``await_imgui(transport, "quiescent", timeout)``; panics on timeout. For a non-asserting poll, call ``await_imgui`` directly. :Arguments: * **app** : :ref:`ImguiApp ` * **timeout_sec** : float .. _function-imgui_playwright_set_auto_dump_on_timeout_bool: .. das:function:: set_auto_dump_on_timeout(enabled: bool) Toggle the wait-family auto-dump (default **on**): any ``wait_*`` that gives up logs the UI state once via ``log_snapshot``, so a red run is self-explaining. Turn it **off** around a *negative* wait (the timeout is the expected outcome) so the log isn't spammed with an expected miss. :Arguments: * **enabled** : bool .. _function-imgui_playwright_wait_cond_ImguiApp_float_block_ls__c_void_gr_: .. das:function:: wait_cond(app: ImguiApp; timeout_sec: float; pred: block<():void>) : bool Bare wall-clock condition poll — no snapshot, no widget coupling. Polls ``pred()`` until true, up to ``timeout_sec`` real seconds; returns true on match, false on timeout. Use for state gated on a command round-trip; the frame-count busy-poll it replaces expires prematurely on the multi-kHz headless server. :Arguments: * **app** : :ref:`ImguiApp ` * **timeout_sec** : float * **pred** : block .. _function-imgui_playwright_wait_for_bool_value_ImguiApp_string_string_bool_int: .. das:function:: wait_for_bool_value(app: ImguiApp; target: string; field: string; expected: bool; max_frames: int = 300) : bool Poll snapshot until `globals[target].payload[field] == expected` (typed bool). :Arguments: * **app** : :ref:`ImguiApp ` * **target** : string * **field** : string * **expected** : bool * **max_frames** : int .. _function-imgui_playwright_wait_for_hover_ImguiApp_string_bool_float: .. das:function:: wait_for_hover(app: ImguiApp; widget_ident: string; expected: bool = true; timeout_sec: float = DEFAULT_FRAME_WAIT_SEC) : JsonValue? Poll until widget ``widget_ident``'s ``hover`` (``IsItemHovered``) equals ``expected``. Sync gate for synthetic clicks: without it a batched ``move``+``press`` fires before ``HoveredId`` is set (the press-before-hover race real mice never hit). The ``widget_exists`` guard stops an absent ident false-passing ``expected=false``. :Arguments: * **app** : :ref:`ImguiApp ` * **widget_ident** : string * **expected** : bool * **timeout_sec** : float .. _function-imgui_playwright_wait_for_int_value_ImguiApp_string_string_int_int: .. das:function:: wait_for_int_value(app: ImguiApp; target: string; field: string; expected: int; max_frames: int = 300) : bool Poll snapshot until `globals[target].payload[field] == expected` (typed int). Returns true on match, false after `max_frames` frames elapse. :Arguments: * **app** : :ref:`ImguiApp ` * **target** : string * **field** : string * **expected** : int * **max_frames** : int .. _function-imgui_playwright_wait_for_key_idle_ImguiApp_float: .. das:function:: wait_for_key_idle(app: ImguiApp; timeout_sec: float = DEFAULT_FRAME_WAIT_SEC) : bool Poll ``imgui_key_status`` until ``playing == false`` or timeout; returns true on idle. Uses one HTTP request per poll (unlike a ``wait_until`` body that pairs a snapshot with a status call), keeping the per-subprocess connection count low on Windows. :Arguments: * **app** : :ref:`ImguiApp ` * **timeout_sec** : float .. _function-imgui_playwright_wait_for_mouse_idle_ImguiApp_float: .. das:function:: wait_for_mouse_idle(app: ImguiApp; timeout_sec: float = DEFAULT_FRAME_WAIT_SEC) : bool Poll ``imgui_mouse_status`` until ``playing == false`` or timeout; returns true on idle. Uses one HTTP request per poll (unlike a ``wait_until`` body that pairs a snapshot with a status call), keeping the per-subprocess connection count low on Windows. :Arguments: * **app** : :ref:`ImguiApp ` * **timeout_sec** : float .. _function-imgui_playwright_wait_for_payload_value_ImguiApp_string_string_autoT_int_0x1a9: .. das:function:: wait_for_payload_value(app: ImguiApp; target: string; field: string; expected: auto(T); max_frames: int = 300) : bool Generic poll: snapshot until ``globals[target].payload[field] == expected``; ``T`` is inferred from ``expected``. Use for any roundtrippable type (float2/3/4, int2/3/4, etc.) where the typed sugars above don't apply. :Arguments: * **app** : :ref:`ImguiApp ` * **target** : string * **field** : string * **expected** : auto(T) * **max_frames** : int .. _function-imgui_playwright_wait_for_render_ImguiApp_string_float: .. das:function:: wait_for_render(app: ImguiApp; widget_ident: string; timeout_sec: float = DEFAULT_FRAME_WAIT_SEC) : JsonValue? First-paint gate — alias of :func:`wait_for_visible`. ``widget_rendered`` now requires existence, so this no longer false-passes for an absent path. Prefer ``wait_for_visible`` at call sites that target a widget which can hide and re-show. :Arguments: * **app** : :ref:`ImguiApp ` * **widget_ident** : string * **timeout_sec** : float .. _function-imgui_playwright_wait_for_string_value_ImguiApp_string_string_string_int: .. das:function:: wait_for_string_value(app: ImguiApp; target: string; field: string; expected: string; max_frames: int = 300) : bool Poll snapshot until `globals[target].payload[field] == expected` (typed string). :Arguments: * **app** : :ref:`ImguiApp ` * **target** : string * **field** : string * **expected** : string * **max_frames** : int .. _function-imgui_playwright_wait_for_visible_ImguiApp_string_float: .. das:function:: wait_for_visible(app: ImguiApp; widget_ident: string; timeout_sec: float = DEFAULT_FRAME_WAIT_SEC) : JsonValue? Poll snapshot until ``widget_ident`` is registered AND painted this frame (``widget_rendered``). The robust gate for a widget that can hide and re-show: ``wait_for_widget`` returns on a stale registry entry (re-opened popup/menu) while it's off-screen, so a click fired right after can be silently dropped. :Arguments: * **app** : :ref:`ImguiApp ` * **widget_ident** : string * **timeout_sec** : float .. _function-imgui_playwright_wait_for_widget_ImguiApp_string_float: .. das:function:: wait_for_widget(app: ImguiApp; widget_ident: string; timeout_sec: float = DEFAULT_FRAME_WAIT_SEC) : JsonValue? Poll snapshot until ``globals[widget_ident]`` is present (registered at module init OR rendered at least once). Use for subprocess-startup gating; for first-paint semantics use ``wait_for_render``. :Arguments: * **app** : :ref:`ImguiApp ` * **widget_ident** : string * **timeout_sec** : float .. _function-imgui_playwright_wait_until_ImguiApp_int_block_ls_var_snap_c_JsonValue_q__c_bool_gr_: .. das:function:: wait_until(app: ImguiApp; max_frames: int; pred: block<(var snap:JsonValue?):bool>) : JsonValue? Poll ``pred(snap)`` up to ``max_frames`` 60fps-equivalent server frames (``max_frames/60`` s); returns the matching snapshot or null on timeout. ``max_frames`` is translated to wall-clock at 60fps (not a raw frame-counter delta, which expires in ~ms on the multi-kHz headless server); prefer ``wait_until_sec`` for new code. :Arguments: * **app** : :ref:`ImguiApp ` * **max_frames** : int * **pred** : block<(snap: :ref:`JsonValue `?):bool> .. _function-imgui_playwright_wait_until_ready_ImguiApp_float: .. das:function:: wait_until_ready(app: ImguiApp; timeout_sec: float) : bool Poll GET /status until 200 OK or timeout. Returns true on ready. :Arguments: * **app** : :ref:`ImguiApp ` * **timeout_sec** : float .. _function-imgui_playwright_wait_until_sec_ImguiApp_float_block_ls_var_snap_c_JsonValue_q__c_bool_gr_: .. das:function:: wait_until_sec(app: ImguiApp; timeout_sec: float; pred: block<(var snap:JsonValue?):bool>) : JsonValue? Wall-clock variant of ``wait_until``: poll ``pred(snap)`` until true, up to ``timeout_sec`` real seconds; returns the matching snapshot or null on timeout. Prefer over ``wait_until`` for time-driven server work — the frame-budget form fires prematurely in headless (multi-kHz, no vsync). :Arguments: * **app** : :ref:`ImguiApp ` * **timeout_sec** : float * **pred** : block<(snap: :ref:`JsonValue `?):bool> +++++++++++++ Pause control +++++++++++++ * :ref:`pause (app: ImguiApp) ` * :ref:`unpause (app: ImguiApp) ` * :ref:`with_paused (app: ImguiApp; body: block\<():void\>) ` .. _function-imgui_playwright_pause_ImguiApp: .. das:function:: pause(app: ImguiApp) POST ``/pause`` to halt the host's update() (skips advance_coroutines + render). POST /command still dispatches and POSTs still drain via ``server.tick()``. Useful for deterministic observation: probe ``imgui_await`` while the world is frozen, then ``unpause``. :Arguments: * **app** : :ref:`ImguiApp ` .. _function-imgui_playwright_unpause_ImguiApp: .. das:function:: unpause(app: ImguiApp) POST ``/unpause`` to resume the host's update(). :Arguments: * **app** : :ref:`ImguiApp ` .. _function-imgui_playwright_with_paused_ImguiApp_block_ls__c_void_gr_: .. das:function:: with_paused(app: ImguiApp; body: block<():void>) Scope: ``pause`` the host, invoke ``body``, ``unpause``. Body observes a frozen world — coroutines spawned by ``imgui_click`` sit unadvanced until unpause, so ``imgui_await`` reports ``pending_coroutines >= 1`` deterministically even in headless mode (where the advance races the next POST). :Arguments: * **app** : :ref:`ImguiApp ` * **body** : block ++++++++++++++++++ Transport plumbing ++++++++++++++++++ * :ref:`post_command (app: ImguiApp; name: string; var args: JsonValue?) : JsonValue? ` .. _function-imgui_playwright_post_command_ImguiApp_string_JsonValue_q_: .. das:function:: post_command(app: ImguiApp; name: string; args: JsonValue?) : JsonValue? Send ``{"name":,"args":}`` through ``app.transport``; returns parsed JSON or null on transport failure. Escape hatch for custom verbs outside the shipped helper set. :Arguments: * **app** : :ref:`ImguiApp ` * **name** : string * **args** : :ref:`JsonValue `?