import pytest from tiny_computer_agent import ( Action, DryRunExecutor, Element, InputUnavailable, Observation, PyAutoGuiExecutor, resolve_point, ) FRAME = Observation(elements=(Element("Save", x=320, y=120, width=80, height=24),)) def test_resolve_point_uses_pixels_when_given(): assert resolve_point(Action("click", x=1, y=2), FRAME) == (1, 2) def test_resolve_point_uses_the_element_index_for_labels(): assert resolve_point(Action("click", label="Save"), FRAME) == (360, 132) def test_resolve_point_is_none_for_unmatched_labels_and_other_verbs(): assert resolve_point(Action("click", label="Quit"), FRAME) is None assert resolve_point(Action("type", text="hi"), FRAME) is None def test_dry_run_records_instead_of_acting(): executor = DryRunExecutor() result = executor.execute(Action("click", label="Save"), FRAME) assert result.ok assert "(dry-run)" in result.note assert "(360, 132)" in result.note assert len(executor.performed) == 1 def test_dry_run_fails_where_live_input_would_refuse(): """A dry run must never give false confidence about a doomed click.""" dry = DryRunExecutor().execute(Action("click", label="Quit"), FRAME) live = PyAutoGuiExecutor().execute(Action("click", label="Quit"), FRAME) assert dry.ok is False and live.ok is False assert "absent from the element index" in dry.note def test_live_executor_refuses_to_guess_pixels(): """The dangerous path: a label with no element behind it must never click.""" result = PyAutoGuiExecutor().execute(Action("click", label="Quit"), FRAME) assert result.ok is False assert "refusing to guess pixels" in result.note def test_live_executor_treats_done_as_a_no_op(): result = PyAutoGuiExecutor().execute(Action("done", reason="finished")) assert result.ok and "goal reported met" in result.note def test_live_executor_needs_pyautogui_installed(): """Real input on a machine that cannot run pyautogui fails loudly, not silently. pyautogui installed is not the same as pyautogui usable: with no display it raises KeyError('DISPLAY') on import, so this must catch Exception rather than ImportError or the failure escapes as a traceback instead of InputUnavailable. """ try: import pyautogui # noqa: F401 except Exception: with pytest.raises(InputUnavailable, match="pyautogui"): PyAutoGuiExecutor().execute(Action("type", text="hello")) else: pytest.skip("pyautogui is usable here")