hud-python 0.4.1__py3-none-any.whl → 0.4.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hud-python might be problematic. Click here for more details.
- hud/__init__.py +22 -22
- hud/agents/__init__.py +13 -15
- hud/agents/base.py +599 -599
- hud/agents/claude.py +373 -373
- hud/agents/langchain.py +261 -250
- hud/agents/misc/__init__.py +7 -7
- hud/agents/misc/response_agent.py +82 -80
- hud/agents/openai.py +352 -352
- hud/agents/openai_chat_generic.py +154 -154
- hud/agents/tests/__init__.py +1 -1
- hud/agents/tests/test_base.py +742 -742
- hud/agents/tests/test_claude.py +324 -324
- hud/agents/tests/test_client.py +363 -363
- hud/agents/tests/test_openai.py +237 -237
- hud/cli/__init__.py +617 -617
- hud/cli/__main__.py +8 -8
- hud/cli/analyze.py +371 -371
- hud/cli/analyze_metadata.py +230 -230
- hud/cli/build.py +498 -427
- hud/cli/clone.py +185 -185
- hud/cli/cursor.py +92 -92
- hud/cli/debug.py +392 -392
- hud/cli/docker_utils.py +83 -83
- hud/cli/init.py +280 -281
- hud/cli/interactive.py +353 -353
- hud/cli/mcp_server.py +764 -756
- hud/cli/pull.py +330 -336
- hud/cli/push.py +404 -370
- hud/cli/remote_runner.py +311 -311
- hud/cli/runner.py +160 -160
- hud/cli/tests/__init__.py +3 -3
- hud/cli/tests/test_analyze.py +284 -284
- hud/cli/tests/test_cli_init.py +265 -265
- hud/cli/tests/test_cli_main.py +27 -27
- hud/cli/tests/test_clone.py +142 -142
- hud/cli/tests/test_cursor.py +253 -253
- hud/cli/tests/test_debug.py +453 -453
- hud/cli/tests/test_mcp_server.py +139 -139
- hud/cli/tests/test_utils.py +388 -388
- hud/cli/utils.py +263 -263
- hud/clients/README.md +143 -143
- hud/clients/__init__.py +16 -16
- hud/clients/base.py +378 -379
- hud/clients/fastmcp.py +222 -222
- hud/clients/mcp_use.py +298 -278
- hud/clients/tests/__init__.py +1 -1
- hud/clients/tests/test_client_integration.py +111 -111
- hud/clients/tests/test_fastmcp.py +342 -342
- hud/clients/tests/test_protocol.py +188 -188
- hud/clients/utils/__init__.py +1 -1
- hud/clients/utils/retry_transport.py +160 -160
- hud/datasets.py +327 -322
- hud/misc/__init__.py +1 -1
- hud/misc/claude_plays_pokemon.py +292 -292
- hud/otel/__init__.py +35 -35
- hud/otel/collector.py +142 -142
- hud/otel/config.py +164 -164
- hud/otel/context.py +536 -536
- hud/otel/exporters.py +366 -366
- hud/otel/instrumentation.py +97 -97
- hud/otel/processors.py +118 -118
- hud/otel/tests/__init__.py +1 -1
- hud/otel/tests/test_processors.py +197 -197
- hud/server/__init__.py +5 -5
- hud/server/context.py +114 -114
- hud/server/helper/__init__.py +5 -5
- hud/server/low_level.py +132 -132
- hud/server/server.py +170 -166
- hud/server/tests/__init__.py +3 -3
- hud/settings.py +73 -73
- hud/shared/__init__.py +5 -5
- hud/shared/exceptions.py +180 -180
- hud/shared/requests.py +264 -264
- hud/shared/tests/test_exceptions.py +157 -157
- hud/shared/tests/test_requests.py +275 -275
- hud/telemetry/__init__.py +25 -25
- hud/telemetry/instrument.py +379 -379
- hud/telemetry/job.py +309 -309
- hud/telemetry/replay.py +74 -74
- hud/telemetry/trace.py +83 -83
- hud/tools/__init__.py +33 -33
- hud/tools/base.py +365 -365
- hud/tools/bash.py +161 -161
- hud/tools/computer/__init__.py +15 -15
- hud/tools/computer/anthropic.py +437 -437
- hud/tools/computer/hud.py +376 -376
- hud/tools/computer/openai.py +295 -295
- hud/tools/computer/settings.py +82 -82
- hud/tools/edit.py +314 -314
- hud/tools/executors/__init__.py +30 -30
- hud/tools/executors/base.py +539 -539
- hud/tools/executors/pyautogui.py +621 -621
- hud/tools/executors/tests/__init__.py +1 -1
- hud/tools/executors/tests/test_base_executor.py +338 -338
- hud/tools/executors/tests/test_pyautogui_executor.py +165 -165
- hud/tools/executors/xdo.py +511 -511
- hud/tools/playwright.py +412 -412
- hud/tools/tests/__init__.py +3 -3
- hud/tools/tests/test_base.py +282 -282
- hud/tools/tests/test_bash.py +158 -158
- hud/tools/tests/test_bash_extended.py +197 -197
- hud/tools/tests/test_computer.py +425 -425
- hud/tools/tests/test_computer_actions.py +34 -34
- hud/tools/tests/test_edit.py +259 -259
- hud/tools/tests/test_init.py +27 -27
- hud/tools/tests/test_playwright_tool.py +183 -183
- hud/tools/tests/test_tools.py +145 -145
- hud/tools/tests/test_utils.py +156 -156
- hud/tools/types.py +72 -72
- hud/tools/utils.py +50 -50
- hud/types.py +136 -136
- hud/utils/__init__.py +10 -10
- hud/utils/async_utils.py +65 -65
- hud/utils/design.py +236 -168
- hud/utils/mcp.py +55 -55
- hud/utils/progress.py +149 -149
- hud/utils/telemetry.py +66 -66
- hud/utils/tests/test_async_utils.py +173 -173
- hud/utils/tests/test_init.py +17 -17
- hud/utils/tests/test_progress.py +261 -261
- hud/utils/tests/test_telemetry.py +82 -82
- hud/utils/tests/test_version.py +8 -8
- hud/version.py +7 -7
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/METADATA +10 -8
- hud_python-0.4.3.dist-info/RECORD +131 -0
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/licenses/LICENSE +21 -21
- hud/agents/art.py +0 -101
- hud_python-0.4.1.dist-info/RECORD +0 -132
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/WHEEL +0 -0
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/entry_points.txt +0 -0
hud/tools/tests/test_computer.py
CHANGED
|
@@ -1,425 +1,425 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from unittest.mock import AsyncMock, MagicMock, patch
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
6
|
-
from mcp.types import ImageContent, TextContent
|
|
7
|
-
|
|
8
|
-
from hud.tools.computer.anthropic import AnthropicComputerTool
|
|
9
|
-
from hud.tools.computer.hud import HudComputerTool
|
|
10
|
-
from hud.tools.computer.openai import OpenAIComputerTool
|
|
11
|
-
from hud.tools.executors.base import BaseExecutor
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@pytest.mark.asyncio
|
|
15
|
-
async def test_hud_computer_screenshot():
|
|
16
|
-
comp = HudComputerTool()
|
|
17
|
-
blocks = await comp(action="screenshot")
|
|
18
|
-
# Screenshot might return ImageContent or TextContent (if error)
|
|
19
|
-
assert blocks is not None
|
|
20
|
-
assert len(blocks) > 0
|
|
21
|
-
assert all(isinstance(b, (ImageContent | TextContent)) for b in blocks)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@pytest.mark.asyncio
|
|
25
|
-
async def test_hud_computer_click_simulation():
|
|
26
|
-
comp = HudComputerTool()
|
|
27
|
-
blocks = await comp(action="click", x=10, y=10)
|
|
28
|
-
# Should return text confirming execution or screenshot block
|
|
29
|
-
assert blocks
|
|
30
|
-
assert len(blocks) > 0
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@pytest.mark.asyncio
|
|
34
|
-
async def test_openai_computer_screenshot():
|
|
35
|
-
comp = OpenAIComputerTool()
|
|
36
|
-
blocks = await comp(type="screenshot")
|
|
37
|
-
assert blocks is not None
|
|
38
|
-
assert len(blocks) > 0
|
|
39
|
-
assert all(isinstance(b, (ImageContent | TextContent)) for b in blocks)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@pytest.mark.asyncio
|
|
43
|
-
async def test_anthropic_computer_screenshot():
|
|
44
|
-
comp = AnthropicComputerTool()
|
|
45
|
-
blocks = await comp(action="screenshot")
|
|
46
|
-
assert blocks is not None
|
|
47
|
-
assert len(blocks) > 0
|
|
48
|
-
assert all(isinstance(b, (ImageContent | TextContent)) for b in blocks)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
@pytest.mark.asyncio
|
|
52
|
-
async def test_openai_computer_click():
|
|
53
|
-
comp = OpenAIComputerTool()
|
|
54
|
-
blocks = await comp(type="click", x=5, y=5)
|
|
55
|
-
assert blocks
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class TestHudComputerToolExtended:
|
|
59
|
-
"""Extended tests for HudComputerTool covering edge cases and platform logic."""
|
|
60
|
-
|
|
61
|
-
@pytest.fixture
|
|
62
|
-
def base_executor(self):
|
|
63
|
-
"""Create a BaseExecutor instance for testing."""
|
|
64
|
-
return BaseExecutor()
|
|
65
|
-
|
|
66
|
-
@pytest.mark.asyncio
|
|
67
|
-
async def test_explicit_base_executor(self, base_executor):
|
|
68
|
-
"""Test explicitly using BaseExecutor."""
|
|
69
|
-
tool = HudComputerTool(executor=base_executor)
|
|
70
|
-
assert tool.executor is base_executor
|
|
71
|
-
|
|
72
|
-
# Test that actions work with base executor
|
|
73
|
-
result = await tool(action="click", x=100, y=200)
|
|
74
|
-
assert result
|
|
75
|
-
assert any(
|
|
76
|
-
"[SIMULATED]" in content.text for content in result if isinstance(content, TextContent)
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
@pytest.mark.asyncio
|
|
80
|
-
async def test_platform_auto_selection_linux(self):
|
|
81
|
-
"""Test platform auto-selection on Linux."""
|
|
82
|
-
with (
|
|
83
|
-
patch("platform.system", return_value="Linux"),
|
|
84
|
-
patch("hud.tools.executors.xdo.XDOExecutor.is_available", return_value=False),
|
|
85
|
-
patch(
|
|
86
|
-
"hud.tools.executors.pyautogui.PyAutoGUIExecutor.is_available",
|
|
87
|
-
return_value=False,
|
|
88
|
-
),
|
|
89
|
-
):
|
|
90
|
-
tool = HudComputerTool()
|
|
91
|
-
assert isinstance(tool.executor, BaseExecutor)
|
|
92
|
-
|
|
93
|
-
@pytest.mark.asyncio
|
|
94
|
-
async def test_platform_auto_selection_windows(self):
|
|
95
|
-
"""Test platform auto-selection on Windows."""
|
|
96
|
-
with (
|
|
97
|
-
patch("platform.system", return_value="Windows"),
|
|
98
|
-
patch(
|
|
99
|
-
"hud.tools.executors.pyautogui.PyAutoGUIExecutor.is_available", return_value=False
|
|
100
|
-
),
|
|
101
|
-
):
|
|
102
|
-
tool = HudComputerTool()
|
|
103
|
-
assert isinstance(tool.executor, BaseExecutor)
|
|
104
|
-
|
|
105
|
-
@pytest.mark.asyncio
|
|
106
|
-
async def test_platform_xdo_fallback(self):
|
|
107
|
-
"""Test XDO platform fallback to BaseExecutor."""
|
|
108
|
-
with patch("hud.tools.executors.xdo.XDOExecutor.is_available", return_value=False):
|
|
109
|
-
tool = HudComputerTool(platform_type="xdo")
|
|
110
|
-
assert isinstance(tool.executor, BaseExecutor)
|
|
111
|
-
|
|
112
|
-
@pytest.mark.asyncio
|
|
113
|
-
async def test_platform_pyautogui_fallback(self):
|
|
114
|
-
"""Test PyAutoGUI platform fallback to BaseExecutor."""
|
|
115
|
-
with patch(
|
|
116
|
-
"hud.tools.executors.pyautogui.PyAutoGUIExecutor.is_available", return_value=False
|
|
117
|
-
):
|
|
118
|
-
tool = HudComputerTool(platform_type="pyautogui")
|
|
119
|
-
assert isinstance(tool.executor, BaseExecutor)
|
|
120
|
-
|
|
121
|
-
@pytest.mark.asyncio
|
|
122
|
-
async def test_invalid_platform_type(self):
|
|
123
|
-
"""Test invalid platform type raises ValueError."""
|
|
124
|
-
with pytest.raises(ValueError, match="Invalid platform_type"):
|
|
125
|
-
HudComputerTool(platform_type="invalid_platform") # type: ignore[arg-type]
|
|
126
|
-
|
|
127
|
-
@pytest.mark.asyncio
|
|
128
|
-
async def test_coordinate_scaling(self, base_executor):
|
|
129
|
-
"""Test coordinate scaling with different screen sizes."""
|
|
130
|
-
# Test with custom dimensions that require scaling
|
|
131
|
-
tool = HudComputerTool(executor=base_executor, width=800, height=600)
|
|
132
|
-
|
|
133
|
-
# Test click with scaling
|
|
134
|
-
result = await tool(action="click", x=400, y=300)
|
|
135
|
-
assert result
|
|
136
|
-
|
|
137
|
-
# Test that coordinates are scaled properly
|
|
138
|
-
assert tool.scale_x == 800 / 1920 # Default environment width is 1920
|
|
139
|
-
assert tool.scale_y == 600 / 1080 # Default environment height is 1080
|
|
140
|
-
assert tool.needs_scaling is True
|
|
141
|
-
|
|
142
|
-
@pytest.mark.asyncio
|
|
143
|
-
async def test_no_scaling_needed(self, base_executor):
|
|
144
|
-
"""Test when no scaling is needed."""
|
|
145
|
-
tool = HudComputerTool(executor=base_executor, width=1920, height=1080)
|
|
146
|
-
assert tool.needs_scaling is False
|
|
147
|
-
assert tool.scale_x == 1.0
|
|
148
|
-
assert tool.scale_y == 1.0
|
|
149
|
-
|
|
150
|
-
@pytest.mark.asyncio
|
|
151
|
-
async def test_type_action(self, base_executor):
|
|
152
|
-
"""Test type action with BaseExecutor."""
|
|
153
|
-
tool = HudComputerTool(executor=base_executor)
|
|
154
|
-
result = await tool(action="type", text="Hello World", enter_after=True)
|
|
155
|
-
assert result
|
|
156
|
-
assert any(
|
|
157
|
-
"[SIMULATED] Type" in content.text
|
|
158
|
-
for content in result
|
|
159
|
-
if isinstance(content, TextContent)
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
@pytest.mark.asyncio
|
|
163
|
-
async def test_press_action(self, base_executor):
|
|
164
|
-
"""Test press action with BaseExecutor."""
|
|
165
|
-
tool = HudComputerTool(executor=base_executor)
|
|
166
|
-
result = await tool(action="press", keys=["ctrl", "c"])
|
|
167
|
-
assert result
|
|
168
|
-
assert any(
|
|
169
|
-
"[SIMULATED] Press" in content.text
|
|
170
|
-
for content in result
|
|
171
|
-
if isinstance(content, TextContent)
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
@pytest.mark.asyncio
|
|
175
|
-
async def test_scroll_action(self, base_executor):
|
|
176
|
-
"""Test scroll action with BaseExecutor."""
|
|
177
|
-
tool = HudComputerTool(executor=base_executor)
|
|
178
|
-
result = await tool(action="scroll", x=500, y=500, scroll_x=0, scroll_y=5)
|
|
179
|
-
assert result
|
|
180
|
-
assert any(
|
|
181
|
-
"Scroll" in content.text for content in result if isinstance(content, TextContent)
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
@pytest.mark.asyncio
|
|
185
|
-
async def test_move_action(self, base_executor):
|
|
186
|
-
"""Test move action with BaseExecutor."""
|
|
187
|
-
tool = HudComputerTool(executor=base_executor)
|
|
188
|
-
result = await tool(action="move", x=100, y=100)
|
|
189
|
-
assert result
|
|
190
|
-
assert any("Move" in content.text for content in result if isinstance(content, TextContent))
|
|
191
|
-
|
|
192
|
-
@pytest.mark.asyncio
|
|
193
|
-
async def test_drag_action(self, base_executor):
|
|
194
|
-
"""Test drag action with BaseExecutor."""
|
|
195
|
-
tool = HudComputerTool(executor=base_executor)
|
|
196
|
-
result = await tool(action="drag", path=[(100, 100), (200, 200)])
|
|
197
|
-
assert result
|
|
198
|
-
assert any("Drag" in content.text for content in result if isinstance(content, TextContent))
|
|
199
|
-
|
|
200
|
-
@pytest.mark.asyncio
|
|
201
|
-
async def test_wait_action(self, base_executor):
|
|
202
|
-
"""Test wait action with BaseExecutor."""
|
|
203
|
-
tool = HudComputerTool(executor=base_executor)
|
|
204
|
-
result = await tool(action="wait", time=100) # 100ms for quick test
|
|
205
|
-
assert result
|
|
206
|
-
assert any("Wait" in content.text for content in result if isinstance(content, TextContent))
|
|
207
|
-
|
|
208
|
-
@pytest.mark.asyncio
|
|
209
|
-
async def test_keydown_keyup_actions(self, base_executor):
|
|
210
|
-
"""Test keydown and keyup actions with BaseExecutor."""
|
|
211
|
-
tool = HudComputerTool(executor=base_executor)
|
|
212
|
-
|
|
213
|
-
# Test keydown
|
|
214
|
-
result = await tool(action="keydown", keys=["shift"])
|
|
215
|
-
assert result
|
|
216
|
-
|
|
217
|
-
# Test keyup
|
|
218
|
-
result = await tool(action="keyup", keys=["shift"])
|
|
219
|
-
assert result
|
|
220
|
-
|
|
221
|
-
@pytest.mark.asyncio
|
|
222
|
-
async def test_hold_key_action(self, base_executor):
|
|
223
|
-
"""Test hold_key action with BaseExecutor."""
|
|
224
|
-
tool = HudComputerTool(executor=base_executor)
|
|
225
|
-
result = await tool(action="hold_key", text="a", duration=0.1)
|
|
226
|
-
assert result
|
|
227
|
-
|
|
228
|
-
@pytest.mark.asyncio
|
|
229
|
-
async def test_mouse_down_up_actions(self, base_executor):
|
|
230
|
-
"""Test mouse_down and mouse_up actions with BaseExecutor."""
|
|
231
|
-
tool = HudComputerTool(executor=base_executor)
|
|
232
|
-
|
|
233
|
-
# Test mouse_down
|
|
234
|
-
result = await tool(action="mouse_down", button="left")
|
|
235
|
-
assert result
|
|
236
|
-
|
|
237
|
-
# Test mouse_up
|
|
238
|
-
result = await tool(action="mouse_up", button="left")
|
|
239
|
-
assert result
|
|
240
|
-
|
|
241
|
-
@pytest.mark.asyncio
|
|
242
|
-
async def test_position_action(self, base_executor):
|
|
243
|
-
"""Test position action with BaseExecutor."""
|
|
244
|
-
tool = HudComputerTool(executor=base_executor)
|
|
245
|
-
result = await tool(action="position")
|
|
246
|
-
assert result
|
|
247
|
-
|
|
248
|
-
@pytest.mark.asyncio
|
|
249
|
-
async def test_response_action(self, base_executor):
|
|
250
|
-
"""Test response action."""
|
|
251
|
-
tool = HudComputerTool(executor=base_executor)
|
|
252
|
-
result = await tool(action="response", text="Test response")
|
|
253
|
-
assert result
|
|
254
|
-
assert len(result) == 1
|
|
255
|
-
assert isinstance(result[0], TextContent)
|
|
256
|
-
assert result[0].text == "Test response"
|
|
257
|
-
|
|
258
|
-
@pytest.mark.asyncio
|
|
259
|
-
async def test_click_with_different_buttons(self, base_executor):
|
|
260
|
-
"""Test click with different mouse buttons."""
|
|
261
|
-
tool = HudComputerTool(executor=base_executor)
|
|
262
|
-
|
|
263
|
-
# Right click
|
|
264
|
-
result = await tool(action="click", x=100, y=100, button="right")
|
|
265
|
-
assert result
|
|
266
|
-
|
|
267
|
-
# Middle click
|
|
268
|
-
result = await tool(action="click", x=100, y=100, button="middle")
|
|
269
|
-
assert result
|
|
270
|
-
|
|
271
|
-
# Double click (using pattern)
|
|
272
|
-
result = await tool(action="click", x=100, y=100, pattern=[100])
|
|
273
|
-
assert result
|
|
274
|
-
|
|
275
|
-
@pytest.mark.asyncio
|
|
276
|
-
async def test_invalid_action(self, base_executor):
|
|
277
|
-
"""Test invalid action returns error."""
|
|
278
|
-
tool = HudComputerTool(executor=base_executor)
|
|
279
|
-
|
|
280
|
-
with pytest.raises(Exception): # Will raise McpError
|
|
281
|
-
await tool(action="invalid_action")
|
|
282
|
-
|
|
283
|
-
@pytest.mark.asyncio
|
|
284
|
-
async def test_screenshot_action(self, base_executor):
|
|
285
|
-
"""Test screenshot action."""
|
|
286
|
-
tool = HudComputerTool(executor=base_executor)
|
|
287
|
-
|
|
288
|
-
# Mock the screenshot method
|
|
289
|
-
base_executor.screenshot = AsyncMock(return_value="fake_base64_data")
|
|
290
|
-
|
|
291
|
-
result = await tool(action="screenshot")
|
|
292
|
-
assert result
|
|
293
|
-
assert any(isinstance(content, ImageContent) for content in result)
|
|
294
|
-
|
|
295
|
-
@pytest.mark.asyncio
|
|
296
|
-
async def test_screenshot_rescaling(self, base_executor):
|
|
297
|
-
"""Test screenshot rescaling functionality."""
|
|
298
|
-
tool = HudComputerTool(executor=base_executor, width=800, height=600, rescale_images=True)
|
|
299
|
-
|
|
300
|
-
# Mock the screenshot method
|
|
301
|
-
base_executor.screenshot = AsyncMock(return_value="fake_base64_data")
|
|
302
|
-
|
|
303
|
-
# Mock the rescale method
|
|
304
|
-
tool._rescale_screenshot = AsyncMock(return_value="rescaled_base64_data")
|
|
305
|
-
|
|
306
|
-
result = await tool(action="screenshot")
|
|
307
|
-
assert result
|
|
308
|
-
# The rescale method is called twice - once for the screenshot action,
|
|
309
|
-
# and once when processing the result
|
|
310
|
-
assert tool._rescale_screenshot.call_count == 2
|
|
311
|
-
tool._rescale_screenshot.assert_any_call("fake_base64_data")
|
|
312
|
-
|
|
313
|
-
@pytest.mark.asyncio
|
|
314
|
-
async def test_executor_initialization_with_display_num(self):
|
|
315
|
-
"""Test executor initialization with display number."""
|
|
316
|
-
with patch(
|
|
317
|
-
"hud.tools.executors.pyautogui.PyAutoGUIExecutor.is_available", return_value=False
|
|
318
|
-
):
|
|
319
|
-
tool = HudComputerTool(display_num=1)
|
|
320
|
-
assert tool.display_num == 1
|
|
321
|
-
|
|
322
|
-
@pytest.mark.asyncio
|
|
323
|
-
async def test_coordinate_none_values(self, base_executor):
|
|
324
|
-
"""Test actions with None coordinate values."""
|
|
325
|
-
tool = HudComputerTool(executor=base_executor)
|
|
326
|
-
|
|
327
|
-
# Test press without coordinates (keyboard shortcut)
|
|
328
|
-
result = await tool(action="press", keys=["ctrl", "a"])
|
|
329
|
-
assert result
|
|
330
|
-
|
|
331
|
-
# Test type without coordinates
|
|
332
|
-
result = await tool(action="type", text="test")
|
|
333
|
-
assert result
|
|
334
|
-
|
|
335
|
-
@pytest.mark.asyncio
|
|
336
|
-
async def test_tool_metadata(self, base_executor):
|
|
337
|
-
"""Test tool metadata is set correctly."""
|
|
338
|
-
tool = HudComputerTool(
|
|
339
|
-
executor=base_executor,
|
|
340
|
-
name="custom_computer",
|
|
341
|
-
title="Custom Computer Tool",
|
|
342
|
-
description="Custom description",
|
|
343
|
-
)
|
|
344
|
-
assert tool.name == "custom_computer"
|
|
345
|
-
assert tool.title == "Custom Computer Tool"
|
|
346
|
-
assert tool.description == "Custom description"
|
|
347
|
-
|
|
348
|
-
# Test defaults
|
|
349
|
-
default_tool = HudComputerTool(executor=base_executor)
|
|
350
|
-
assert default_tool.name == "computer"
|
|
351
|
-
assert default_tool.title == "Computer Control"
|
|
352
|
-
assert default_tool.description == "Control computer with mouse, keyboard, and screenshots"
|
|
353
|
-
|
|
354
|
-
@pytest.mark.asyncio
|
|
355
|
-
async def test_missing_required_parameters(self, base_executor):
|
|
356
|
-
"""Test actions that are missing required parameters."""
|
|
357
|
-
tool = HudComputerTool(executor=base_executor)
|
|
358
|
-
|
|
359
|
-
# Test type without text
|
|
360
|
-
from hud.tools.types import ToolError
|
|
361
|
-
|
|
362
|
-
with pytest.raises(ToolError, match="text parameter is required"):
|
|
363
|
-
await tool(action="type", text=None)
|
|
364
|
-
|
|
365
|
-
# Test press without keys
|
|
366
|
-
with pytest.raises(ToolError, match="keys parameter is required"):
|
|
367
|
-
await tool(action="press", keys=None)
|
|
368
|
-
|
|
369
|
-
# Test wait without time
|
|
370
|
-
with pytest.raises(ToolError, match="time parameter is required"):
|
|
371
|
-
await tool(action="wait", time=None)
|
|
372
|
-
|
|
373
|
-
# Test drag without path
|
|
374
|
-
with pytest.raises(ToolError, match="path parameter is required"):
|
|
375
|
-
await tool(action="drag", path=None)
|
|
376
|
-
|
|
377
|
-
@pytest.mark.asyncio
|
|
378
|
-
async def test_relative_move(self, base_executor):
|
|
379
|
-
"""Test relative move with offsets."""
|
|
380
|
-
tool = HudComputerTool(executor=base_executor)
|
|
381
|
-
result = await tool(action="move", offset_x=50, offset_y=50)
|
|
382
|
-
assert result
|
|
383
|
-
|
|
384
|
-
@pytest.mark.asyncio
|
|
385
|
-
async def test_screenshot_failure(self, base_executor):
|
|
386
|
-
"""Test screenshot failure handling."""
|
|
387
|
-
tool = HudComputerTool(executor=base_executor)
|
|
388
|
-
|
|
389
|
-
# Mock screenshot to return None (failure)
|
|
390
|
-
base_executor.screenshot = AsyncMock(return_value=None)
|
|
391
|
-
|
|
392
|
-
result = await tool(action="screenshot")
|
|
393
|
-
assert result
|
|
394
|
-
# Should contain error message
|
|
395
|
-
assert any(
|
|
396
|
-
"Failed" in content.text for content in result if isinstance(content, TextContent)
|
|
397
|
-
)
|
|
398
|
-
|
|
399
|
-
@pytest.mark.asyncio
|
|
400
|
-
async def test_platform_selection_with_available_executors(self):
|
|
401
|
-
"""Test platform selection when executors are available."""
|
|
402
|
-
# Test Linux with XDO available
|
|
403
|
-
mock_xdo_instance = MagicMock()
|
|
404
|
-
with (
|
|
405
|
-
patch("platform.system", return_value="Linux"),
|
|
406
|
-
patch("hud.tools.executors.xdo.XDOExecutor.is_available", return_value=True),
|
|
407
|
-
patch("hud.tools.computer.hud.XDOExecutor", return_value=mock_xdo_instance) as mock_xdo,
|
|
408
|
-
):
|
|
409
|
-
tool = HudComputerTool(platform_type="auto")
|
|
410
|
-
mock_xdo.assert_called_once()
|
|
411
|
-
assert tool.executor is mock_xdo_instance
|
|
412
|
-
|
|
413
|
-
# Test with PyAutoGUI available
|
|
414
|
-
mock_pyautogui_instance = MagicMock()
|
|
415
|
-
with (
|
|
416
|
-
patch(
|
|
417
|
-
"hud.tools.executors.pyautogui.PyAutoGUIExecutor.is_available", return_value=True
|
|
418
|
-
),
|
|
419
|
-
patch(
|
|
420
|
-
"hud.tools.computer.hud.PyAutoGUIExecutor", return_value=mock_pyautogui_instance
|
|
421
|
-
) as mock_pyautogui,
|
|
422
|
-
):
|
|
423
|
-
tool = HudComputerTool(platform_type="pyautogui")
|
|
424
|
-
mock_pyautogui.assert_called_once()
|
|
425
|
-
assert tool.executor is mock_pyautogui_instance
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from unittest.mock import AsyncMock, MagicMock, patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from mcp.types import ImageContent, TextContent
|
|
7
|
+
|
|
8
|
+
from hud.tools.computer.anthropic import AnthropicComputerTool
|
|
9
|
+
from hud.tools.computer.hud import HudComputerTool
|
|
10
|
+
from hud.tools.computer.openai import OpenAIComputerTool
|
|
11
|
+
from hud.tools.executors.base import BaseExecutor
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.mark.asyncio
|
|
15
|
+
async def test_hud_computer_screenshot():
|
|
16
|
+
comp = HudComputerTool()
|
|
17
|
+
blocks = await comp(action="screenshot")
|
|
18
|
+
# Screenshot might return ImageContent or TextContent (if error)
|
|
19
|
+
assert blocks is not None
|
|
20
|
+
assert len(blocks) > 0
|
|
21
|
+
assert all(isinstance(b, (ImageContent | TextContent)) for b in blocks)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.mark.asyncio
|
|
25
|
+
async def test_hud_computer_click_simulation():
|
|
26
|
+
comp = HudComputerTool()
|
|
27
|
+
blocks = await comp(action="click", x=10, y=10)
|
|
28
|
+
# Should return text confirming execution or screenshot block
|
|
29
|
+
assert blocks
|
|
30
|
+
assert len(blocks) > 0
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@pytest.mark.asyncio
|
|
34
|
+
async def test_openai_computer_screenshot():
|
|
35
|
+
comp = OpenAIComputerTool()
|
|
36
|
+
blocks = await comp(type="screenshot")
|
|
37
|
+
assert blocks is not None
|
|
38
|
+
assert len(blocks) > 0
|
|
39
|
+
assert all(isinstance(b, (ImageContent | TextContent)) for b in blocks)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.mark.asyncio
|
|
43
|
+
async def test_anthropic_computer_screenshot():
|
|
44
|
+
comp = AnthropicComputerTool()
|
|
45
|
+
blocks = await comp(action="screenshot")
|
|
46
|
+
assert blocks is not None
|
|
47
|
+
assert len(blocks) > 0
|
|
48
|
+
assert all(isinstance(b, (ImageContent | TextContent)) for b in blocks)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest.mark.asyncio
|
|
52
|
+
async def test_openai_computer_click():
|
|
53
|
+
comp = OpenAIComputerTool()
|
|
54
|
+
blocks = await comp(type="click", x=5, y=5)
|
|
55
|
+
assert blocks
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class TestHudComputerToolExtended:
|
|
59
|
+
"""Extended tests for HudComputerTool covering edge cases and platform logic."""
|
|
60
|
+
|
|
61
|
+
@pytest.fixture
|
|
62
|
+
def base_executor(self):
|
|
63
|
+
"""Create a BaseExecutor instance for testing."""
|
|
64
|
+
return BaseExecutor()
|
|
65
|
+
|
|
66
|
+
@pytest.mark.asyncio
|
|
67
|
+
async def test_explicit_base_executor(self, base_executor):
|
|
68
|
+
"""Test explicitly using BaseExecutor."""
|
|
69
|
+
tool = HudComputerTool(executor=base_executor)
|
|
70
|
+
assert tool.executor is base_executor
|
|
71
|
+
|
|
72
|
+
# Test that actions work with base executor
|
|
73
|
+
result = await tool(action="click", x=100, y=200)
|
|
74
|
+
assert result
|
|
75
|
+
assert any(
|
|
76
|
+
"[SIMULATED]" in content.text for content in result if isinstance(content, TextContent)
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
@pytest.mark.asyncio
|
|
80
|
+
async def test_platform_auto_selection_linux(self):
|
|
81
|
+
"""Test platform auto-selection on Linux."""
|
|
82
|
+
with (
|
|
83
|
+
patch("platform.system", return_value="Linux"),
|
|
84
|
+
patch("hud.tools.executors.xdo.XDOExecutor.is_available", return_value=False),
|
|
85
|
+
patch(
|
|
86
|
+
"hud.tools.executors.pyautogui.PyAutoGUIExecutor.is_available",
|
|
87
|
+
return_value=False,
|
|
88
|
+
),
|
|
89
|
+
):
|
|
90
|
+
tool = HudComputerTool()
|
|
91
|
+
assert isinstance(tool.executor, BaseExecutor)
|
|
92
|
+
|
|
93
|
+
@pytest.mark.asyncio
|
|
94
|
+
async def test_platform_auto_selection_windows(self):
|
|
95
|
+
"""Test platform auto-selection on Windows."""
|
|
96
|
+
with (
|
|
97
|
+
patch("platform.system", return_value="Windows"),
|
|
98
|
+
patch(
|
|
99
|
+
"hud.tools.executors.pyautogui.PyAutoGUIExecutor.is_available", return_value=False
|
|
100
|
+
),
|
|
101
|
+
):
|
|
102
|
+
tool = HudComputerTool()
|
|
103
|
+
assert isinstance(tool.executor, BaseExecutor)
|
|
104
|
+
|
|
105
|
+
@pytest.mark.asyncio
|
|
106
|
+
async def test_platform_xdo_fallback(self):
|
|
107
|
+
"""Test XDO platform fallback to BaseExecutor."""
|
|
108
|
+
with patch("hud.tools.executors.xdo.XDOExecutor.is_available", return_value=False):
|
|
109
|
+
tool = HudComputerTool(platform_type="xdo")
|
|
110
|
+
assert isinstance(tool.executor, BaseExecutor)
|
|
111
|
+
|
|
112
|
+
@pytest.mark.asyncio
|
|
113
|
+
async def test_platform_pyautogui_fallback(self):
|
|
114
|
+
"""Test PyAutoGUI platform fallback to BaseExecutor."""
|
|
115
|
+
with patch(
|
|
116
|
+
"hud.tools.executors.pyautogui.PyAutoGUIExecutor.is_available", return_value=False
|
|
117
|
+
):
|
|
118
|
+
tool = HudComputerTool(platform_type="pyautogui")
|
|
119
|
+
assert isinstance(tool.executor, BaseExecutor)
|
|
120
|
+
|
|
121
|
+
@pytest.mark.asyncio
|
|
122
|
+
async def test_invalid_platform_type(self):
|
|
123
|
+
"""Test invalid platform type raises ValueError."""
|
|
124
|
+
with pytest.raises(ValueError, match="Invalid platform_type"):
|
|
125
|
+
HudComputerTool(platform_type="invalid_platform") # type: ignore[arg-type]
|
|
126
|
+
|
|
127
|
+
@pytest.mark.asyncio
|
|
128
|
+
async def test_coordinate_scaling(self, base_executor):
|
|
129
|
+
"""Test coordinate scaling with different screen sizes."""
|
|
130
|
+
# Test with custom dimensions that require scaling
|
|
131
|
+
tool = HudComputerTool(executor=base_executor, width=800, height=600)
|
|
132
|
+
|
|
133
|
+
# Test click with scaling
|
|
134
|
+
result = await tool(action="click", x=400, y=300)
|
|
135
|
+
assert result
|
|
136
|
+
|
|
137
|
+
# Test that coordinates are scaled properly
|
|
138
|
+
assert tool.scale_x == 800 / 1920 # Default environment width is 1920
|
|
139
|
+
assert tool.scale_y == 600 / 1080 # Default environment height is 1080
|
|
140
|
+
assert tool.needs_scaling is True
|
|
141
|
+
|
|
142
|
+
@pytest.mark.asyncio
|
|
143
|
+
async def test_no_scaling_needed(self, base_executor):
|
|
144
|
+
"""Test when no scaling is needed."""
|
|
145
|
+
tool = HudComputerTool(executor=base_executor, width=1920, height=1080)
|
|
146
|
+
assert tool.needs_scaling is False
|
|
147
|
+
assert tool.scale_x == 1.0
|
|
148
|
+
assert tool.scale_y == 1.0
|
|
149
|
+
|
|
150
|
+
@pytest.mark.asyncio
|
|
151
|
+
async def test_type_action(self, base_executor):
|
|
152
|
+
"""Test type action with BaseExecutor."""
|
|
153
|
+
tool = HudComputerTool(executor=base_executor)
|
|
154
|
+
result = await tool(action="type", text="Hello World", enter_after=True)
|
|
155
|
+
assert result
|
|
156
|
+
assert any(
|
|
157
|
+
"[SIMULATED] Type" in content.text
|
|
158
|
+
for content in result
|
|
159
|
+
if isinstance(content, TextContent)
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
@pytest.mark.asyncio
|
|
163
|
+
async def test_press_action(self, base_executor):
|
|
164
|
+
"""Test press action with BaseExecutor."""
|
|
165
|
+
tool = HudComputerTool(executor=base_executor)
|
|
166
|
+
result = await tool(action="press", keys=["ctrl", "c"])
|
|
167
|
+
assert result
|
|
168
|
+
assert any(
|
|
169
|
+
"[SIMULATED] Press" in content.text
|
|
170
|
+
for content in result
|
|
171
|
+
if isinstance(content, TextContent)
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
@pytest.mark.asyncio
|
|
175
|
+
async def test_scroll_action(self, base_executor):
|
|
176
|
+
"""Test scroll action with BaseExecutor."""
|
|
177
|
+
tool = HudComputerTool(executor=base_executor)
|
|
178
|
+
result = await tool(action="scroll", x=500, y=500, scroll_x=0, scroll_y=5)
|
|
179
|
+
assert result
|
|
180
|
+
assert any(
|
|
181
|
+
"Scroll" in content.text for content in result if isinstance(content, TextContent)
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
@pytest.mark.asyncio
|
|
185
|
+
async def test_move_action(self, base_executor):
|
|
186
|
+
"""Test move action with BaseExecutor."""
|
|
187
|
+
tool = HudComputerTool(executor=base_executor)
|
|
188
|
+
result = await tool(action="move", x=100, y=100)
|
|
189
|
+
assert result
|
|
190
|
+
assert any("Move" in content.text for content in result if isinstance(content, TextContent))
|
|
191
|
+
|
|
192
|
+
@pytest.mark.asyncio
|
|
193
|
+
async def test_drag_action(self, base_executor):
|
|
194
|
+
"""Test drag action with BaseExecutor."""
|
|
195
|
+
tool = HudComputerTool(executor=base_executor)
|
|
196
|
+
result = await tool(action="drag", path=[(100, 100), (200, 200)])
|
|
197
|
+
assert result
|
|
198
|
+
assert any("Drag" in content.text for content in result if isinstance(content, TextContent))
|
|
199
|
+
|
|
200
|
+
@pytest.mark.asyncio
|
|
201
|
+
async def test_wait_action(self, base_executor):
|
|
202
|
+
"""Test wait action with BaseExecutor."""
|
|
203
|
+
tool = HudComputerTool(executor=base_executor)
|
|
204
|
+
result = await tool(action="wait", time=100) # 100ms for quick test
|
|
205
|
+
assert result
|
|
206
|
+
assert any("Wait" in content.text for content in result if isinstance(content, TextContent))
|
|
207
|
+
|
|
208
|
+
@pytest.mark.asyncio
|
|
209
|
+
async def test_keydown_keyup_actions(self, base_executor):
|
|
210
|
+
"""Test keydown and keyup actions with BaseExecutor."""
|
|
211
|
+
tool = HudComputerTool(executor=base_executor)
|
|
212
|
+
|
|
213
|
+
# Test keydown
|
|
214
|
+
result = await tool(action="keydown", keys=["shift"])
|
|
215
|
+
assert result
|
|
216
|
+
|
|
217
|
+
# Test keyup
|
|
218
|
+
result = await tool(action="keyup", keys=["shift"])
|
|
219
|
+
assert result
|
|
220
|
+
|
|
221
|
+
@pytest.mark.asyncio
|
|
222
|
+
async def test_hold_key_action(self, base_executor):
|
|
223
|
+
"""Test hold_key action with BaseExecutor."""
|
|
224
|
+
tool = HudComputerTool(executor=base_executor)
|
|
225
|
+
result = await tool(action="hold_key", text="a", duration=0.1)
|
|
226
|
+
assert result
|
|
227
|
+
|
|
228
|
+
@pytest.mark.asyncio
|
|
229
|
+
async def test_mouse_down_up_actions(self, base_executor):
|
|
230
|
+
"""Test mouse_down and mouse_up actions with BaseExecutor."""
|
|
231
|
+
tool = HudComputerTool(executor=base_executor)
|
|
232
|
+
|
|
233
|
+
# Test mouse_down
|
|
234
|
+
result = await tool(action="mouse_down", button="left")
|
|
235
|
+
assert result
|
|
236
|
+
|
|
237
|
+
# Test mouse_up
|
|
238
|
+
result = await tool(action="mouse_up", button="left")
|
|
239
|
+
assert result
|
|
240
|
+
|
|
241
|
+
@pytest.mark.asyncio
|
|
242
|
+
async def test_position_action(self, base_executor):
|
|
243
|
+
"""Test position action with BaseExecutor."""
|
|
244
|
+
tool = HudComputerTool(executor=base_executor)
|
|
245
|
+
result = await tool(action="position")
|
|
246
|
+
assert result
|
|
247
|
+
|
|
248
|
+
@pytest.mark.asyncio
|
|
249
|
+
async def test_response_action(self, base_executor):
|
|
250
|
+
"""Test response action."""
|
|
251
|
+
tool = HudComputerTool(executor=base_executor)
|
|
252
|
+
result = await tool(action="response", text="Test response")
|
|
253
|
+
assert result
|
|
254
|
+
assert len(result) == 1
|
|
255
|
+
assert isinstance(result[0], TextContent)
|
|
256
|
+
assert result[0].text == "Test response"
|
|
257
|
+
|
|
258
|
+
@pytest.mark.asyncio
|
|
259
|
+
async def test_click_with_different_buttons(self, base_executor):
|
|
260
|
+
"""Test click with different mouse buttons."""
|
|
261
|
+
tool = HudComputerTool(executor=base_executor)
|
|
262
|
+
|
|
263
|
+
# Right click
|
|
264
|
+
result = await tool(action="click", x=100, y=100, button="right")
|
|
265
|
+
assert result
|
|
266
|
+
|
|
267
|
+
# Middle click
|
|
268
|
+
result = await tool(action="click", x=100, y=100, button="middle")
|
|
269
|
+
assert result
|
|
270
|
+
|
|
271
|
+
# Double click (using pattern)
|
|
272
|
+
result = await tool(action="click", x=100, y=100, pattern=[100])
|
|
273
|
+
assert result
|
|
274
|
+
|
|
275
|
+
@pytest.mark.asyncio
|
|
276
|
+
async def test_invalid_action(self, base_executor):
|
|
277
|
+
"""Test invalid action returns error."""
|
|
278
|
+
tool = HudComputerTool(executor=base_executor)
|
|
279
|
+
|
|
280
|
+
with pytest.raises(Exception): # Will raise McpError
|
|
281
|
+
await tool(action="invalid_action")
|
|
282
|
+
|
|
283
|
+
@pytest.mark.asyncio
|
|
284
|
+
async def test_screenshot_action(self, base_executor):
|
|
285
|
+
"""Test screenshot action."""
|
|
286
|
+
tool = HudComputerTool(executor=base_executor)
|
|
287
|
+
|
|
288
|
+
# Mock the screenshot method
|
|
289
|
+
base_executor.screenshot = AsyncMock(return_value="fake_base64_data")
|
|
290
|
+
|
|
291
|
+
result = await tool(action="screenshot")
|
|
292
|
+
assert result
|
|
293
|
+
assert any(isinstance(content, ImageContent) for content in result)
|
|
294
|
+
|
|
295
|
+
@pytest.mark.asyncio
|
|
296
|
+
async def test_screenshot_rescaling(self, base_executor):
|
|
297
|
+
"""Test screenshot rescaling functionality."""
|
|
298
|
+
tool = HudComputerTool(executor=base_executor, width=800, height=600, rescale_images=True)
|
|
299
|
+
|
|
300
|
+
# Mock the screenshot method
|
|
301
|
+
base_executor.screenshot = AsyncMock(return_value="fake_base64_data")
|
|
302
|
+
|
|
303
|
+
# Mock the rescale method
|
|
304
|
+
tool._rescale_screenshot = AsyncMock(return_value="rescaled_base64_data")
|
|
305
|
+
|
|
306
|
+
result = await tool(action="screenshot")
|
|
307
|
+
assert result
|
|
308
|
+
# The rescale method is called twice - once for the screenshot action,
|
|
309
|
+
# and once when processing the result
|
|
310
|
+
assert tool._rescale_screenshot.call_count == 2
|
|
311
|
+
tool._rescale_screenshot.assert_any_call("fake_base64_data")
|
|
312
|
+
|
|
313
|
+
@pytest.mark.asyncio
|
|
314
|
+
async def test_executor_initialization_with_display_num(self):
|
|
315
|
+
"""Test executor initialization with display number."""
|
|
316
|
+
with patch(
|
|
317
|
+
"hud.tools.executors.pyautogui.PyAutoGUIExecutor.is_available", return_value=False
|
|
318
|
+
):
|
|
319
|
+
tool = HudComputerTool(display_num=1)
|
|
320
|
+
assert tool.display_num == 1
|
|
321
|
+
|
|
322
|
+
@pytest.mark.asyncio
|
|
323
|
+
async def test_coordinate_none_values(self, base_executor):
|
|
324
|
+
"""Test actions with None coordinate values."""
|
|
325
|
+
tool = HudComputerTool(executor=base_executor)
|
|
326
|
+
|
|
327
|
+
# Test press without coordinates (keyboard shortcut)
|
|
328
|
+
result = await tool(action="press", keys=["ctrl", "a"])
|
|
329
|
+
assert result
|
|
330
|
+
|
|
331
|
+
# Test type without coordinates
|
|
332
|
+
result = await tool(action="type", text="test")
|
|
333
|
+
assert result
|
|
334
|
+
|
|
335
|
+
@pytest.mark.asyncio
|
|
336
|
+
async def test_tool_metadata(self, base_executor):
|
|
337
|
+
"""Test tool metadata is set correctly."""
|
|
338
|
+
tool = HudComputerTool(
|
|
339
|
+
executor=base_executor,
|
|
340
|
+
name="custom_computer",
|
|
341
|
+
title="Custom Computer Tool",
|
|
342
|
+
description="Custom description",
|
|
343
|
+
)
|
|
344
|
+
assert tool.name == "custom_computer"
|
|
345
|
+
assert tool.title == "Custom Computer Tool"
|
|
346
|
+
assert tool.description == "Custom description"
|
|
347
|
+
|
|
348
|
+
# Test defaults
|
|
349
|
+
default_tool = HudComputerTool(executor=base_executor)
|
|
350
|
+
assert default_tool.name == "computer"
|
|
351
|
+
assert default_tool.title == "Computer Control"
|
|
352
|
+
assert default_tool.description == "Control computer with mouse, keyboard, and screenshots"
|
|
353
|
+
|
|
354
|
+
@pytest.mark.asyncio
|
|
355
|
+
async def test_missing_required_parameters(self, base_executor):
|
|
356
|
+
"""Test actions that are missing required parameters."""
|
|
357
|
+
tool = HudComputerTool(executor=base_executor)
|
|
358
|
+
|
|
359
|
+
# Test type without text
|
|
360
|
+
from hud.tools.types import ToolError
|
|
361
|
+
|
|
362
|
+
with pytest.raises(ToolError, match="text parameter is required"):
|
|
363
|
+
await tool(action="type", text=None)
|
|
364
|
+
|
|
365
|
+
# Test press without keys
|
|
366
|
+
with pytest.raises(ToolError, match="keys parameter is required"):
|
|
367
|
+
await tool(action="press", keys=None)
|
|
368
|
+
|
|
369
|
+
# Test wait without time
|
|
370
|
+
with pytest.raises(ToolError, match="time parameter is required"):
|
|
371
|
+
await tool(action="wait", time=None)
|
|
372
|
+
|
|
373
|
+
# Test drag without path
|
|
374
|
+
with pytest.raises(ToolError, match="path parameter is required"):
|
|
375
|
+
await tool(action="drag", path=None)
|
|
376
|
+
|
|
377
|
+
@pytest.mark.asyncio
|
|
378
|
+
async def test_relative_move(self, base_executor):
|
|
379
|
+
"""Test relative move with offsets."""
|
|
380
|
+
tool = HudComputerTool(executor=base_executor)
|
|
381
|
+
result = await tool(action="move", offset_x=50, offset_y=50)
|
|
382
|
+
assert result
|
|
383
|
+
|
|
384
|
+
@pytest.mark.asyncio
|
|
385
|
+
async def test_screenshot_failure(self, base_executor):
|
|
386
|
+
"""Test screenshot failure handling."""
|
|
387
|
+
tool = HudComputerTool(executor=base_executor)
|
|
388
|
+
|
|
389
|
+
# Mock screenshot to return None (failure)
|
|
390
|
+
base_executor.screenshot = AsyncMock(return_value=None)
|
|
391
|
+
|
|
392
|
+
result = await tool(action="screenshot")
|
|
393
|
+
assert result
|
|
394
|
+
# Should contain error message
|
|
395
|
+
assert any(
|
|
396
|
+
"Failed" in content.text for content in result if isinstance(content, TextContent)
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
@pytest.mark.asyncio
|
|
400
|
+
async def test_platform_selection_with_available_executors(self):
|
|
401
|
+
"""Test platform selection when executors are available."""
|
|
402
|
+
# Test Linux with XDO available
|
|
403
|
+
mock_xdo_instance = MagicMock()
|
|
404
|
+
with (
|
|
405
|
+
patch("platform.system", return_value="Linux"),
|
|
406
|
+
patch("hud.tools.executors.xdo.XDOExecutor.is_available", return_value=True),
|
|
407
|
+
patch("hud.tools.computer.hud.XDOExecutor", return_value=mock_xdo_instance) as mock_xdo,
|
|
408
|
+
):
|
|
409
|
+
tool = HudComputerTool(platform_type="auto")
|
|
410
|
+
mock_xdo.assert_called_once()
|
|
411
|
+
assert tool.executor is mock_xdo_instance
|
|
412
|
+
|
|
413
|
+
# Test with PyAutoGUI available
|
|
414
|
+
mock_pyautogui_instance = MagicMock()
|
|
415
|
+
with (
|
|
416
|
+
patch(
|
|
417
|
+
"hud.tools.executors.pyautogui.PyAutoGUIExecutor.is_available", return_value=True
|
|
418
|
+
),
|
|
419
|
+
patch(
|
|
420
|
+
"hud.tools.computer.hud.PyAutoGUIExecutor", return_value=mock_pyautogui_instance
|
|
421
|
+
) as mock_pyautogui,
|
|
422
|
+
):
|
|
423
|
+
tool = HudComputerTool(platform_type="pyautogui")
|
|
424
|
+
mock_pyautogui.assert_called_once()
|
|
425
|
+
assert tool.executor is mock_pyautogui_instance
|