Actionability Gate — Wait Until Ready Before Acting
Modern UI frameworks (Playwright, Cypress, WebdriverIO) run an actionability check
before every click: the target must be present, have stopped moving, be enabled, and
actually receive the event (not be covered). AutoControl had no equivalent —
self_heal_click locates and clicks immediately, and wait_until_screen_stable
only watches the whole frame. wait_actionable composes the four checks into one
gate, so a click lands on a button that is genuinely ready rather than mid-animation,
disabled, or behind a dialog.
Every signal is an injectable callable — bbox_provider (locate the target),
region_sampler (pixel-stability token), enabled_probe, hit_tester — plus
an injectable clock / sleep via GateConfig, so the gate is fully
deterministic and headless-testable. Imports no PySide6.
Headless API
from je_auto_control import wait_actionable, act_when_ready, GateConfig
report = wait_actionable(
bbox_provider=lambda: locate_button(), # () -> (x, y, w, h) or None
enabled_probe=lambda: not is_greyed_out(),
config=GateConfig(timeout_s=8.0, stable_for_s=0.4))
if report.actionable:
click(*report.point)
else:
print("blocked:", report.reason) # not visible / not stable / …
# Or gate + act in one call (raises if it never becomes actionable):
act_when_ready(lambda point: click(*point), bbox_provider=locate_button)
wait_actionable returns an ActionabilityReport with actionable plus
the per-check booleans (visible / stable / enabled / receives_events),
the target point, waited_s and a reason (the first failing check).
act_when_ready waits and then calls action(center_point), raising
AutoControlActionException on timeout.
Executor command
AC_wait_actionable binds the gate to a template image (located each poll) and
samples that region’s pixels for stability: timeout_s / stable_for_s /
min_score / region → the report dict. It is exposed as the MCP tool
ac_wait_actionable and as a Script Builder command under Flow.