diff --git a/tests/agents/test_agent_utils.py b/tests/agents/test_agent_utils.py new file mode 100644 index 000000000..26339af90 --- /dev/null +++ b/tests/agents/test_agent_utils.py @@ -0,0 +1,67 @@ +from unittest.mock import MagicMock + +import pytest +from PIL import Image, ImageDraw + +from agentlab.agents.agent_utils import ( + draw_arrowhead, + draw_click_indicator, + draw_mouse_pointer, + zoom_webpage, +) + + +@pytest.fixture +def white_image(): + return Image.new("RGB", (100, 100), color=(255, 255, 255)) + + +def test_draw_mouse_pointer_returns_new_rgba_image(white_image): + result = draw_mouse_pointer(white_image, 10, 10) + + assert result is not white_image + assert result.mode == "RGBA" + assert result.size == white_image.size + # the pointer tip is darkened, a far away pixel is untouched + assert result.getpixel((12, 11))[:3] != (255, 255, 255) + assert result.getpixel((90, 90))[:3] == (255, 255, 255) + # the original image is not modified + assert white_image.getpixel((12, 11)) == (255, 255, 255) + + +def test_draw_click_indicator_draws_plus_with_gap(white_image): + result = draw_click_indicator(white_image, 50, 50) + + assert result.mode == "RGBA" + # center is left blank (gap), arms are drawn + assert result.getpixel((50, 50))[:3] == (255, 255, 255) + assert result.getpixel((50, 40))[:3] == (0, 0, 0) + assert result.getpixel((50, 60))[:3] == (0, 0, 0) + assert result.getpixel((40, 50))[:3] == (0, 0, 0) + assert result.getpixel((60, 50))[:3] == (0, 0, 0) + assert result.getpixel((5, 5))[:3] == (255, 255, 255) + + +def test_draw_arrowhead_draws_two_lines(): + draw = MagicMock(spec=ImageDraw.ImageDraw) + draw_arrowhead(draw, (0, 0), (50, 0)) + + assert draw.line.call_count == 2 + for call in draw.line.call_args_list: + points = call.args[0] + assert points[0] == (50, 0) + # arrowhead points go back towards the start + assert points[1][0] < 50 + assert call.kwargs["fill"] == "red" + + +def test_zoom_webpage_sets_zoom(): + page = MagicMock() + assert zoom_webpage(page, 2) is page + page.evaluate.assert_called_once_with("document.documentElement.style.zoom='200%'") + + +@pytest.mark.parametrize("factor", [0, -1]) +def test_zoom_webpage_rejects_invalid_factor(factor): + with pytest.raises(ValueError): + zoom_webpage(MagicMock(), factor) diff --git a/tests/agents/test_debug_agent.py b/tests/agents/test_debug_agent.py new file mode 100644 index 000000000..f7b557c44 --- /dev/null +++ b/tests/agents/test_debug_agent.py @@ -0,0 +1,71 @@ +from types import SimpleNamespace +from unittest.mock import MagicMock + +import numpy as np + +from agentlab.agents import debug_agent +from agentlab.agents.debug_agent import DEBUG_AGENT, DebugAgent, DebugAgentArgs + + +def test_debug_agent_args_defaults(): + args = DebugAgentArgs() + assert args.agent_name == "debug" + assert args.use_html is False + assert DEBUG_AGENT.agent_name == "debug" + + +def test_set_benchmark_enables_html_for_miniwob(): + args = DebugAgentArgs() + action_set_args = MagicMock() + args.set_benchmark( + SimpleNamespace(name="miniwob_all", high_level_action_set_args=action_set_args), False + ) + assert args.use_html is True + assert args.action_set_args is action_set_args + + args = DebugAgentArgs() + args.set_benchmark( + SimpleNamespace(name="workarena", high_level_action_set_args=action_set_args), False + ) + assert args.use_html is False + + +def test_make_agent(): + agent = DebugAgentArgs().make_agent() + assert isinstance(agent, DebugAgent) + assert agent.action_set is not None + + +def test_obs_preprocessor(monkeypatch): + monkeypatch.setattr(debug_agent, "flatten_dom_to_str", lambda dom, **kw: "
dom
") + monkeypatch.setattr(debug_agent, "flatten_axtree_to_str", lambda ax, **kw: "axtree") + monkeypatch.setattr(debug_agent, "prune_html", lambda html: "pruned:" + html) + monkeypatch.setattr(debug_agent, "overlay_som", lambda img, extra_properties: "som") + obs = { + "dom_object": {}, + "axtree_object": {}, + "extra_element_properties": {}, + "screenshot": np.zeros((2, 2, 3)), + } + out = DebugAgentArgs().make_agent().obs_preprocessor(obs) + assert out["dom_txt"] == "dom
" + assert out["axtree_txt"] == "axtree" + assert out["pruned_html"] == "pruned:dom
" + assert out["screenshot_som"] == "som" + assert "dom_txt" not in obs # input is not mutated + + +def test_get_action_prompts_user(monkeypatch): + prompts = [] + monkeypatch.setattr("builtins.input", lambda p: prompts.append(p) or "noop()") + obs = {"pruned_html": "