hud-python 0.3.4__py3-none-any.whl → 0.4.0__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.

Files changed (192) hide show
  1. hud/__init__.py +22 -89
  2. hud/agents/__init__.py +17 -0
  3. hud/agents/art.py +101 -0
  4. hud/agents/base.py +599 -0
  5. hud/{mcp → agents}/claude.py +373 -321
  6. hud/{mcp → agents}/langchain.py +250 -250
  7. hud/agents/misc/__init__.py +7 -0
  8. hud/{agent → agents}/misc/response_agent.py +80 -80
  9. hud/{mcp → agents}/openai.py +352 -334
  10. hud/agents/openai_chat_generic.py +154 -0
  11. hud/{mcp → agents}/tests/__init__.py +1 -1
  12. hud/agents/tests/test_base.py +742 -0
  13. hud/agents/tests/test_claude.py +324 -0
  14. hud/{mcp → agents}/tests/test_client.py +363 -324
  15. hud/{mcp → agents}/tests/test_openai.py +237 -238
  16. hud/cli/__init__.py +617 -0
  17. hud/cli/__main__.py +8 -0
  18. hud/cli/analyze.py +371 -0
  19. hud/cli/analyze_metadata.py +230 -0
  20. hud/cli/build.py +427 -0
  21. hud/cli/clone.py +185 -0
  22. hud/cli/cursor.py +92 -0
  23. hud/cli/debug.py +392 -0
  24. hud/cli/docker_utils.py +83 -0
  25. hud/cli/init.py +281 -0
  26. hud/cli/interactive.py +353 -0
  27. hud/cli/mcp_server.py +756 -0
  28. hud/cli/pull.py +336 -0
  29. hud/cli/push.py +379 -0
  30. hud/cli/remote_runner.py +311 -0
  31. hud/cli/runner.py +160 -0
  32. hud/cli/tests/__init__.py +3 -0
  33. hud/cli/tests/test_analyze.py +284 -0
  34. hud/cli/tests/test_cli_init.py +265 -0
  35. hud/cli/tests/test_cli_main.py +27 -0
  36. hud/cli/tests/test_clone.py +142 -0
  37. hud/cli/tests/test_cursor.py +253 -0
  38. hud/cli/tests/test_debug.py +453 -0
  39. hud/cli/tests/test_mcp_server.py +139 -0
  40. hud/cli/tests/test_utils.py +388 -0
  41. hud/cli/utils.py +263 -0
  42. hud/clients/README.md +143 -0
  43. hud/clients/__init__.py +16 -0
  44. hud/clients/base.py +354 -0
  45. hud/clients/fastmcp.py +202 -0
  46. hud/clients/mcp_use.py +278 -0
  47. hud/clients/tests/__init__.py +1 -0
  48. hud/clients/tests/test_client_integration.py +111 -0
  49. hud/clients/tests/test_fastmcp.py +342 -0
  50. hud/clients/tests/test_protocol.py +188 -0
  51. hud/clients/utils/__init__.py +1 -0
  52. hud/clients/utils/retry_transport.py +160 -0
  53. hud/datasets.py +322 -192
  54. hud/misc/__init__.py +1 -0
  55. hud/{agent → misc}/claude_plays_pokemon.py +292 -283
  56. hud/otel/__init__.py +35 -0
  57. hud/otel/collector.py +142 -0
  58. hud/otel/config.py +164 -0
  59. hud/otel/context.py +536 -0
  60. hud/otel/exporters.py +366 -0
  61. hud/otel/instrumentation.py +97 -0
  62. hud/otel/processors.py +118 -0
  63. hud/otel/tests/__init__.py +1 -0
  64. hud/otel/tests/test_processors.py +197 -0
  65. hud/server/__init__.py +5 -5
  66. hud/server/context.py +114 -0
  67. hud/server/helper/__init__.py +5 -0
  68. hud/server/low_level.py +132 -0
  69. hud/server/server.py +166 -0
  70. hud/server/tests/__init__.py +3 -0
  71. hud/settings.py +73 -79
  72. hud/shared/__init__.py +5 -0
  73. hud/{exceptions.py → shared/exceptions.py} +180 -180
  74. hud/{server → shared}/requests.py +264 -264
  75. hud/shared/tests/test_exceptions.py +157 -0
  76. hud/{server → shared}/tests/test_requests.py +275 -275
  77. hud/telemetry/__init__.py +25 -30
  78. hud/telemetry/instrument.py +379 -0
  79. hud/telemetry/job.py +309 -141
  80. hud/telemetry/replay.py +74 -0
  81. hud/telemetry/trace.py +83 -0
  82. hud/tools/__init__.py +33 -34
  83. hud/tools/base.py +365 -65
  84. hud/tools/bash.py +161 -137
  85. hud/tools/computer/__init__.py +15 -13
  86. hud/tools/computer/anthropic.py +437 -414
  87. hud/tools/computer/hud.py +376 -328
  88. hud/tools/computer/openai.py +295 -286
  89. hud/tools/computer/settings.py +82 -0
  90. hud/tools/edit.py +314 -290
  91. hud/tools/executors/__init__.py +30 -30
  92. hud/tools/executors/base.py +539 -532
  93. hud/tools/executors/pyautogui.py +621 -619
  94. hud/tools/executors/tests/__init__.py +1 -1
  95. hud/tools/executors/tests/test_base_executor.py +338 -338
  96. hud/tools/executors/tests/test_pyautogui_executor.py +165 -165
  97. hud/tools/executors/xdo.py +511 -503
  98. hud/tools/{playwright_tool.py → playwright.py} +412 -379
  99. hud/tools/tests/__init__.py +3 -3
  100. hud/tools/tests/test_base.py +282 -0
  101. hud/tools/tests/test_bash.py +158 -152
  102. hud/tools/tests/test_bash_extended.py +197 -0
  103. hud/tools/tests/test_computer.py +425 -52
  104. hud/tools/tests/test_computer_actions.py +34 -34
  105. hud/tools/tests/test_edit.py +259 -240
  106. hud/tools/tests/test_init.py +27 -27
  107. hud/tools/tests/test_playwright_tool.py +183 -183
  108. hud/tools/tests/test_tools.py +145 -157
  109. hud/tools/tests/test_utils.py +156 -156
  110. hud/tools/types.py +72 -0
  111. hud/tools/utils.py +50 -50
  112. hud/types.py +136 -89
  113. hud/utils/__init__.py +10 -16
  114. hud/utils/async_utils.py +65 -0
  115. hud/utils/design.py +168 -0
  116. hud/utils/mcp.py +55 -0
  117. hud/utils/progress.py +149 -149
  118. hud/utils/telemetry.py +66 -66
  119. hud/utils/tests/test_async_utils.py +173 -0
  120. hud/utils/tests/test_init.py +17 -21
  121. hud/utils/tests/test_progress.py +261 -225
  122. hud/utils/tests/test_telemetry.py +82 -37
  123. hud/utils/tests/test_version.py +8 -8
  124. hud/version.py +7 -7
  125. hud_python-0.4.0.dist-info/METADATA +474 -0
  126. hud_python-0.4.0.dist-info/RECORD +132 -0
  127. hud_python-0.4.0.dist-info/entry_points.txt +3 -0
  128. {hud_python-0.3.4.dist-info → hud_python-0.4.0.dist-info}/licenses/LICENSE +21 -21
  129. hud/adapters/__init__.py +0 -8
  130. hud/adapters/claude/__init__.py +0 -5
  131. hud/adapters/claude/adapter.py +0 -180
  132. hud/adapters/claude/tests/__init__.py +0 -1
  133. hud/adapters/claude/tests/test_adapter.py +0 -519
  134. hud/adapters/common/__init__.py +0 -6
  135. hud/adapters/common/adapter.py +0 -178
  136. hud/adapters/common/tests/test_adapter.py +0 -289
  137. hud/adapters/common/types.py +0 -446
  138. hud/adapters/operator/__init__.py +0 -5
  139. hud/adapters/operator/adapter.py +0 -108
  140. hud/adapters/operator/tests/__init__.py +0 -1
  141. hud/adapters/operator/tests/test_adapter.py +0 -370
  142. hud/agent/__init__.py +0 -19
  143. hud/agent/base.py +0 -126
  144. hud/agent/claude.py +0 -271
  145. hud/agent/langchain.py +0 -215
  146. hud/agent/misc/__init__.py +0 -3
  147. hud/agent/operator.py +0 -268
  148. hud/agent/tests/__init__.py +0 -1
  149. hud/agent/tests/test_base.py +0 -202
  150. hud/env/__init__.py +0 -11
  151. hud/env/client.py +0 -35
  152. hud/env/docker_client.py +0 -349
  153. hud/env/environment.py +0 -446
  154. hud/env/local_docker_client.py +0 -358
  155. hud/env/remote_client.py +0 -212
  156. hud/env/remote_docker_client.py +0 -292
  157. hud/gym.py +0 -130
  158. hud/job.py +0 -773
  159. hud/mcp/__init__.py +0 -17
  160. hud/mcp/base.py +0 -631
  161. hud/mcp/client.py +0 -312
  162. hud/mcp/tests/test_base.py +0 -512
  163. hud/mcp/tests/test_claude.py +0 -294
  164. hud/task.py +0 -149
  165. hud/taskset.py +0 -237
  166. hud/telemetry/_trace.py +0 -347
  167. hud/telemetry/context.py +0 -230
  168. hud/telemetry/exporter.py +0 -575
  169. hud/telemetry/instrumentation/__init__.py +0 -3
  170. hud/telemetry/instrumentation/mcp.py +0 -259
  171. hud/telemetry/instrumentation/registry.py +0 -59
  172. hud/telemetry/mcp_models.py +0 -270
  173. hud/telemetry/tests/__init__.py +0 -1
  174. hud/telemetry/tests/test_context.py +0 -210
  175. hud/telemetry/tests/test_trace.py +0 -312
  176. hud/tools/helper/README.md +0 -56
  177. hud/tools/helper/__init__.py +0 -9
  178. hud/tools/helper/mcp_server.py +0 -78
  179. hud/tools/helper/server_initialization.py +0 -115
  180. hud/tools/helper/utils.py +0 -58
  181. hud/trajectory.py +0 -94
  182. hud/utils/agent.py +0 -37
  183. hud/utils/common.py +0 -256
  184. hud/utils/config.py +0 -120
  185. hud/utils/deprecation.py +0 -115
  186. hud/utils/misc.py +0 -53
  187. hud/utils/tests/test_common.py +0 -277
  188. hud/utils/tests/test_config.py +0 -129
  189. hud_python-0.3.4.dist-info/METADATA +0 -284
  190. hud_python-0.3.4.dist-info/RECORD +0 -120
  191. /hud/{adapters/common → shared}/tests/__init__.py +0 -0
  192. {hud_python-0.3.4.dist-info → hud_python-0.4.0.dist-info}/WHEEL +0 -0
@@ -1,338 +1,338 @@
1
- """Tests for BaseExecutor."""
2
-
3
- from __future__ import annotations
4
-
5
- from unittest.mock import patch
6
-
7
- import pytest
8
-
9
- from hud.tools.base import ToolResult
10
- from hud.tools.executors.base import BaseExecutor
11
-
12
-
13
- class TestBaseExecutor:
14
- """Tests for BaseExecutor simulated actions."""
15
-
16
- def test_init(self):
17
- """Test BaseExecutor initialization."""
18
- # Without display num
19
- executor = BaseExecutor()
20
- assert executor.display_num is None
21
- assert executor._screenshot_delay == 0.5
22
-
23
- # With display num
24
- executor = BaseExecutor(display_num=1)
25
- assert executor.display_num == 1
26
-
27
- @pytest.mark.asyncio
28
- async def test_click_basic(self):
29
- """Test basic click action."""
30
- executor = BaseExecutor()
31
- result = await executor.click(x=100, y=200, button="left", take_screenshot=False)
32
-
33
- assert isinstance(result, ToolResult)
34
- assert result.output == "[SIMULATED] Click at (100, 200) with left button"
35
- assert result.base64_image is None # No screenshot requested
36
-
37
- @pytest.mark.asyncio
38
- async def test_click_with_screenshot(self):
39
- """Test click with screenshot."""
40
- executor = BaseExecutor()
41
- result = await executor.click(x=100, y=200, take_screenshot=True)
42
-
43
- assert isinstance(result, ToolResult)
44
- assert result.output == "[SIMULATED] Click at (100, 200) with left button"
45
- assert result.base64_image is not None # Screenshot included
46
-
47
- @pytest.mark.asyncio
48
- async def test_click_with_pattern(self):
49
- """Test click with multi-click pattern."""
50
- executor = BaseExecutor()
51
- result = await executor.click(x=100, y=200, pattern=[100, 50], take_screenshot=False)
52
-
53
- assert isinstance(result, ToolResult)
54
- assert result.output is not None
55
- assert (
56
- "[SIMULATED] Click at (100, 200) with left button (multi-click pattern: [100, 50])"
57
- in result.output
58
- )
59
-
60
- @pytest.mark.asyncio
61
- async def test_click_with_hold_keys(self):
62
- """Test click while holding keys."""
63
- executor = BaseExecutor()
64
- result = await executor.click(
65
- x=100, y=200, hold_keys=["ctrl", "shift"], take_screenshot=False
66
- )
67
-
68
- assert isinstance(result, ToolResult)
69
- assert result.output is not None
70
- assert "while holding ['ctrl', 'shift']" in result.output
71
-
72
- @pytest.mark.asyncio
73
- async def test_type_basic(self):
74
- """Test basic typing."""
75
- executor = BaseExecutor()
76
- result = await executor.type("Hello World", take_screenshot=False)
77
-
78
- assert isinstance(result, ToolResult)
79
- assert result.output == "[SIMULATED] Type 'Hello World'"
80
-
81
- @pytest.mark.asyncio
82
- async def test_type_with_enter(self):
83
- """Test typing with enter."""
84
- executor = BaseExecutor()
85
- result = await executor.type("Hello", enter_after=True, take_screenshot=False)
86
-
87
- assert isinstance(result, ToolResult)
88
- assert result.output == "[SIMULATED] Type 'Hello' followed by Enter"
89
-
90
- @pytest.mark.asyncio
91
- async def test_press_keys(self):
92
- """Test pressing key combination."""
93
- executor = BaseExecutor()
94
- result = await executor.press(["ctrl", "c"], take_screenshot=False)
95
-
96
- assert isinstance(result, ToolResult)
97
- assert result.output == "[SIMULATED] Press key combination: ctrl+c"
98
-
99
- @pytest.mark.asyncio
100
- async def test_key_single(self):
101
- """Test pressing single key."""
102
- executor = BaseExecutor()
103
- result = await executor.key("Return", take_screenshot=False)
104
-
105
- assert isinstance(result, ToolResult)
106
- assert result.output == "[SIMULATED] Press key: Return"
107
-
108
- @pytest.mark.asyncio
109
- async def test_keydown(self):
110
- """Test key down action."""
111
- executor = BaseExecutor()
112
- result = await executor.keydown(["shift", "ctrl"], take_screenshot=False)
113
-
114
- assert isinstance(result, ToolResult)
115
- assert result.output == "[SIMULATED] Key down: shift, ctrl"
116
-
117
- @pytest.mark.asyncio
118
- async def test_keyup(self):
119
- """Test key up action."""
120
- executor = BaseExecutor()
121
- result = await executor.keyup(["shift", "ctrl"], take_screenshot=False)
122
-
123
- assert isinstance(result, ToolResult)
124
- assert result.output == "[SIMULATED] Key up: shift, ctrl"
125
-
126
- @pytest.mark.asyncio
127
- async def test_scroll_basic(self):
128
- """Test basic scroll."""
129
- executor = BaseExecutor()
130
- result = await executor.scroll(x=100, y=200, scroll_y=5, take_screenshot=False)
131
-
132
- assert isinstance(result, ToolResult)
133
- assert result.output is not None
134
- assert "[SIMULATED] Scroll at (100, 200)" in result.output
135
- assert "vertically by 5" in result.output
136
-
137
- @pytest.mark.asyncio
138
- async def test_scroll_horizontal(self):
139
- """Test horizontal scroll."""
140
- executor = BaseExecutor()
141
- result = await executor.scroll(scroll_x=10, take_screenshot=False)
142
-
143
- assert isinstance(result, ToolResult)
144
- assert result.output is not None
145
- assert "[SIMULATED] Scroll" in result.output
146
- assert "horizontally by 10" in result.output
147
-
148
- @pytest.mark.asyncio
149
- async def test_scroll_with_hold_keys(self):
150
- """Test scroll with held keys."""
151
- executor = BaseExecutor()
152
- result = await executor.scroll(
153
- x=100, y=200, scroll_y=5, hold_keys=["shift"], take_screenshot=False
154
- )
155
-
156
- assert isinstance(result, ToolResult)
157
- assert result.output is not None
158
- assert "while holding ['shift']" in result.output
159
-
160
- @pytest.mark.asyncio
161
- async def test_move_absolute(self):
162
- """Test absolute mouse movement."""
163
- executor = BaseExecutor()
164
- result = await executor.move(x=300, y=400, take_screenshot=False)
165
-
166
- assert isinstance(result, ToolResult)
167
- assert result.output == "[SIMULATED] Move mouse to (300, 400)"
168
-
169
- @pytest.mark.asyncio
170
- async def test_move_relative(self):
171
- """Test relative mouse movement."""
172
- executor = BaseExecutor()
173
- result = await executor.move(offset_x=50, offset_y=-30, take_screenshot=False)
174
-
175
- assert isinstance(result, ToolResult)
176
- assert result.output == "[SIMULATED] Move mouse by offset (50, -30)"
177
-
178
- @pytest.mark.asyncio
179
- async def test_move_no_coords(self):
180
- """Test move with no coordinates."""
181
- executor = BaseExecutor()
182
- result = await executor.move(take_screenshot=False)
183
-
184
- assert isinstance(result, ToolResult)
185
- assert result.output == "[SIMULATED] Move mouse (no coordinates specified)"
186
-
187
- @pytest.mark.asyncio
188
- async def test_drag_basic(self):
189
- """Test basic drag operation."""
190
- executor = BaseExecutor()
191
- path = [(100, 100), (200, 200)]
192
- result = await executor.drag(path, take_screenshot=False)
193
-
194
- assert isinstance(result, ToolResult)
195
- assert result.output == "[SIMULATED] Drag from (100, 100) to (200, 200)"
196
-
197
- @pytest.mark.asyncio
198
- async def test_drag_with_intermediate_points(self):
199
- """Test drag with intermediate points."""
200
- executor = BaseExecutor()
201
- path = [(100, 100), (150, 150), (200, 200)]
202
- result = await executor.drag(path, take_screenshot=False)
203
-
204
- assert isinstance(result, ToolResult)
205
- assert result.output is not None
206
- assert (
207
- "[SIMULATED] Drag from (100, 100) to (200, 200) via 1 intermediate points"
208
- in result.output
209
- )
210
-
211
- @pytest.mark.asyncio
212
- async def test_drag_invalid_path(self):
213
- """Test drag with invalid path."""
214
- executor = BaseExecutor()
215
- result = await executor.drag([(100, 100)], take_screenshot=False) # Only one point
216
-
217
- assert isinstance(result, ToolResult)
218
- assert result.error == "Drag path must have at least 2 points"
219
- assert result.output is None
220
-
221
- @pytest.mark.asyncio
222
- async def test_drag_with_hold_keys(self):
223
- """Test drag with held keys."""
224
- executor = BaseExecutor()
225
- path = [(100, 100), (200, 200)]
226
- result = await executor.drag(path, hold_keys=["alt"], take_screenshot=False)
227
-
228
- assert isinstance(result, ToolResult)
229
- assert result.output is not None
230
- assert "while holding ['alt']" in result.output
231
-
232
- @pytest.mark.asyncio
233
- async def test_mouse_down(self):
234
- """Test mouse down action."""
235
- executor = BaseExecutor()
236
- result = await executor.mouse_down(button="right", take_screenshot=False)
237
-
238
- assert isinstance(result, ToolResult)
239
- assert result.output == "[SIMULATED] Mouse down: right button"
240
-
241
- @pytest.mark.asyncio
242
- async def test_mouse_up(self):
243
- """Test mouse up action."""
244
- executor = BaseExecutor()
245
- result = await executor.mouse_up(button="middle", take_screenshot=False)
246
-
247
- assert isinstance(result, ToolResult)
248
- assert result.output == "[SIMULATED] Mouse up: middle button"
249
-
250
- @pytest.mark.asyncio
251
- async def test_hold_key(self):
252
- """Test holding a key for duration."""
253
- executor = BaseExecutor()
254
-
255
- # Mock sleep to avoid actual wait
256
- with patch("asyncio.sleep") as mock_sleep:
257
- result = await executor.hold_key("shift", 0.5, take_screenshot=False)
258
-
259
- assert isinstance(result, ToolResult)
260
- assert result.output == "[SIMULATED] Hold key 'shift' for 0.5 seconds"
261
- mock_sleep.assert_called_once_with(0.5)
262
-
263
- @pytest.mark.asyncio
264
- async def test_wait(self):
265
- """Test wait action."""
266
- executor = BaseExecutor()
267
-
268
- # Mock sleep to avoid actual wait
269
- with patch("asyncio.sleep") as mock_sleep:
270
- result = await executor.wait(1000) # 1000ms
271
-
272
- assert isinstance(result, ToolResult)
273
- assert result.output == "Waited 1000ms"
274
- mock_sleep.assert_called_once_with(1.0)
275
-
276
- @pytest.mark.asyncio
277
- async def test_screenshot(self):
278
- """Test screenshot action."""
279
- executor = BaseExecutor()
280
- result = await executor.screenshot()
281
-
282
- assert isinstance(result, str)
283
- # Check it's a valid base64 string (starts with PNG header)
284
- assert result.startswith("iVBORw0KGgo")
285
-
286
- @pytest.mark.asyncio
287
- async def test_position(self):
288
- """Test getting cursor position."""
289
- executor = BaseExecutor()
290
- result = await executor.position()
291
-
292
- assert isinstance(result, ToolResult)
293
- assert result.output == "[SIMULATED] Mouse position: (0, 0)"
294
-
295
- @pytest.mark.asyncio
296
- async def test_execute(self):
297
- """Test execute command."""
298
- executor = BaseExecutor()
299
- result = await executor.execute("custom command", take_screenshot=False)
300
-
301
- assert isinstance(result, ToolResult)
302
- assert result.output == "[SIMULATED] Execute: custom command"
303
-
304
- @pytest.mark.asyncio
305
- async def test_type_text_alias(self):
306
- """Test type_text alias method."""
307
- executor = BaseExecutor()
308
- result = await executor.type_text("test", delay=20, take_screenshot=False)
309
-
310
- assert isinstance(result, ToolResult)
311
- assert result.output == "[SIMULATED] Type 'test'"
312
-
313
- @pytest.mark.asyncio
314
- async def test_mouse_move_alias(self):
315
- """Test mouse_move alias method."""
316
- executor = BaseExecutor()
317
- result = await executor.mouse_move(100, 200, take_screenshot=False)
318
-
319
- assert isinstance(result, ToolResult)
320
- assert result.output == "[SIMULATED] Move mouse to (100, 200)"
321
-
322
- @pytest.mark.asyncio
323
- async def test_multiple_actions_with_screenshots(self):
324
- """Test multiple actions with screenshots to ensure consistency."""
325
- executor = BaseExecutor()
326
-
327
- # Test that screenshots are consistent
328
- screenshot1 = await executor.screenshot()
329
- screenshot2 = await executor.screenshot()
330
-
331
- assert screenshot1 == screenshot2 # Simulated screenshots should be identical
332
-
333
- # Test actions with screenshots
334
- result1 = await executor.click(10, 20, take_screenshot=True)
335
- result2 = await executor.type("test", take_screenshot=True)
336
-
337
- assert result1.base64_image == screenshot1
338
- assert result2.base64_image == screenshot1
1
+ """Tests for BaseExecutor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from unittest.mock import patch
6
+
7
+ import pytest
8
+
9
+ from hud.tools.executors.base import BaseExecutor
10
+ from hud.tools.types import ContentResult
11
+
12
+
13
+ class TestBaseExecutor:
14
+ """Tests for BaseExecutor simulated actions."""
15
+
16
+ def test_init(self):
17
+ """Test BaseExecutor initialization."""
18
+ # Without display num - defaults to computer_settings.DISPLAY_NUM
19
+ executor = BaseExecutor()
20
+ assert executor.display_num == 0 # Default from computer_settings
21
+ assert executor._screenshot_delay == 0.5
22
+
23
+ # With display num
24
+ executor = BaseExecutor(display_num=1)
25
+ assert executor.display_num == 1
26
+
27
+ @pytest.mark.asyncio
28
+ async def test_click_basic(self):
29
+ """Test basic click action."""
30
+ executor = BaseExecutor()
31
+ result = await executor.click(x=100, y=200, button="left", take_screenshot=False)
32
+
33
+ assert isinstance(result, ContentResult)
34
+ assert result.output == "[SIMULATED] Click at (100, 200) with left button"
35
+ assert result.base64_image is None # No screenshot requested
36
+
37
+ @pytest.mark.asyncio
38
+ async def test_click_with_screenshot(self):
39
+ """Test click with screenshot."""
40
+ executor = BaseExecutor()
41
+ result = await executor.click(x=100, y=200, take_screenshot=True)
42
+
43
+ assert isinstance(result, ContentResult)
44
+ assert result.output == "[SIMULATED] Click at (100, 200) with left button"
45
+ assert result.base64_image is not None # Screenshot included
46
+
47
+ @pytest.mark.asyncio
48
+ async def test_click_with_pattern(self):
49
+ """Test click with multi-click pattern."""
50
+ executor = BaseExecutor()
51
+ result = await executor.click(x=100, y=200, pattern=[100, 50], take_screenshot=False)
52
+
53
+ assert isinstance(result, ContentResult)
54
+ assert result.output is not None
55
+ assert (
56
+ "[SIMULATED] Click at (100, 200) with left button (multi-click pattern: [100, 50])"
57
+ in result.output
58
+ )
59
+
60
+ @pytest.mark.asyncio
61
+ async def test_click_with_hold_keys(self):
62
+ """Test click while holding keys."""
63
+ executor = BaseExecutor()
64
+ result = await executor.click(
65
+ x=100, y=200, hold_keys=["ctrl", "shift"], take_screenshot=False
66
+ )
67
+
68
+ assert isinstance(result, ContentResult)
69
+ assert result.output is not None
70
+ assert "while holding ['ctrl', 'shift']" in result.output
71
+
72
+ @pytest.mark.asyncio
73
+ async def test_type_basic(self):
74
+ """Test basic typing."""
75
+ executor = BaseExecutor()
76
+ result = await executor.write("Hello World", take_screenshot=False)
77
+
78
+ assert isinstance(result, ContentResult)
79
+ assert result.output == "[SIMULATED] Type 'Hello World'"
80
+
81
+ @pytest.mark.asyncio
82
+ async def test_type_with_enter(self):
83
+ """Test typing with enter."""
84
+ executor = BaseExecutor()
85
+ result = await executor.write("Hello", enter_after=True, take_screenshot=False)
86
+
87
+ assert isinstance(result, ContentResult)
88
+ assert result.output == "[SIMULATED] Type 'Hello' followed by Enter"
89
+
90
+ @pytest.mark.asyncio
91
+ async def test_press_keys(self):
92
+ """Test pressing key combination."""
93
+ executor = BaseExecutor()
94
+ result = await executor.press(["ctrl", "c"], take_screenshot=False)
95
+
96
+ assert isinstance(result, ContentResult)
97
+ assert result.output == "[SIMULATED] Press key combination: ctrl+c"
98
+
99
+ @pytest.mark.asyncio
100
+ async def test_key_single(self):
101
+ """Test pressing single key."""
102
+ executor = BaseExecutor()
103
+ result = await executor.key("Return", take_screenshot=False)
104
+
105
+ assert isinstance(result, ContentResult)
106
+ assert result.output == "[SIMULATED] Press key: Return"
107
+
108
+ @pytest.mark.asyncio
109
+ async def test_keydown(self):
110
+ """Test key down action."""
111
+ executor = BaseExecutor()
112
+ result = await executor.keydown(["shift", "ctrl"], take_screenshot=False)
113
+
114
+ assert isinstance(result, ContentResult)
115
+ assert result.output == "[SIMULATED] Key down: shift, ctrl"
116
+
117
+ @pytest.mark.asyncio
118
+ async def test_keyup(self):
119
+ """Test key up action."""
120
+ executor = BaseExecutor()
121
+ result = await executor.keyup(["shift", "ctrl"], take_screenshot=False)
122
+
123
+ assert isinstance(result, ContentResult)
124
+ assert result.output == "[SIMULATED] Key up: shift, ctrl"
125
+
126
+ @pytest.mark.asyncio
127
+ async def test_scroll_basic(self):
128
+ """Test basic scroll."""
129
+ executor = BaseExecutor()
130
+ result = await executor.scroll(x=100, y=200, scroll_y=5, take_screenshot=False)
131
+
132
+ assert isinstance(result, ContentResult)
133
+ assert result.output is not None
134
+ assert "[SIMULATED] Scroll at (100, 200)" in result.output
135
+ assert "vertically by 5" in result.output
136
+
137
+ @pytest.mark.asyncio
138
+ async def test_scroll_horizontal(self):
139
+ """Test horizontal scroll."""
140
+ executor = BaseExecutor()
141
+ result = await executor.scroll(scroll_x=10, take_screenshot=False)
142
+
143
+ assert isinstance(result, ContentResult)
144
+ assert result.output is not None
145
+ assert "[SIMULATED] Scroll" in result.output
146
+ assert "horizontally by 10" in result.output
147
+
148
+ @pytest.mark.asyncio
149
+ async def test_scroll_with_hold_keys(self):
150
+ """Test scroll with held keys."""
151
+ executor = BaseExecutor()
152
+ result = await executor.scroll(
153
+ x=100, y=200, scroll_y=5, hold_keys=["shift"], take_screenshot=False
154
+ )
155
+
156
+ assert isinstance(result, ContentResult)
157
+ assert result.output is not None
158
+ assert "while holding ['shift']" in result.output
159
+
160
+ @pytest.mark.asyncio
161
+ async def test_move_absolute(self):
162
+ """Test absolute mouse movement."""
163
+ executor = BaseExecutor()
164
+ result = await executor.move(x=300, y=400, take_screenshot=False)
165
+
166
+ assert isinstance(result, ContentResult)
167
+ assert result.output == "[SIMULATED] Move mouse to (300, 400)"
168
+
169
+ @pytest.mark.asyncio
170
+ async def test_move_relative(self):
171
+ """Test relative mouse movement."""
172
+ executor = BaseExecutor()
173
+ result = await executor.move(offset_x=50, offset_y=-30, take_screenshot=False)
174
+
175
+ assert isinstance(result, ContentResult)
176
+ assert result.output == "[SIMULATED] Move mouse by offset (50, -30)"
177
+
178
+ @pytest.mark.asyncio
179
+ async def test_move_no_coords(self):
180
+ """Test move with no coordinates."""
181
+ executor = BaseExecutor()
182
+ result = await executor.move(take_screenshot=False)
183
+
184
+ assert isinstance(result, ContentResult)
185
+ assert result.output == "[SIMULATED] Move mouse (no coordinates specified)"
186
+
187
+ @pytest.mark.asyncio
188
+ async def test_drag_basic(self):
189
+ """Test basic drag operation."""
190
+ executor = BaseExecutor()
191
+ path = [(100, 100), (200, 200)]
192
+ result = await executor.drag(path, take_screenshot=False)
193
+
194
+ assert isinstance(result, ContentResult)
195
+ assert result.output == "[SIMULATED] Drag from (100, 100) to (200, 200)"
196
+
197
+ @pytest.mark.asyncio
198
+ async def test_drag_with_intermediate_points(self):
199
+ """Test drag with intermediate points."""
200
+ executor = BaseExecutor()
201
+ path = [(100, 100), (150, 150), (200, 200)]
202
+ result = await executor.drag(path, take_screenshot=False)
203
+
204
+ assert isinstance(result, ContentResult)
205
+ assert result.output is not None
206
+ assert (
207
+ "[SIMULATED] Drag from (100, 100) to (200, 200) via 1 intermediate points"
208
+ in result.output
209
+ )
210
+
211
+ @pytest.mark.asyncio
212
+ async def test_drag_invalid_path(self):
213
+ """Test drag with invalid path."""
214
+ executor = BaseExecutor()
215
+ result = await executor.drag([(100, 100)], take_screenshot=False) # Only one point
216
+
217
+ assert isinstance(result, ContentResult)
218
+ assert result.error == "Drag path must have at least 2 points"
219
+ assert result.output is None
220
+
221
+ @pytest.mark.asyncio
222
+ async def test_drag_with_hold_keys(self):
223
+ """Test drag with held keys."""
224
+ executor = BaseExecutor()
225
+ path = [(100, 100), (200, 200)]
226
+ result = await executor.drag(path, hold_keys=["alt"], take_screenshot=False)
227
+
228
+ assert isinstance(result, ContentResult)
229
+ assert result.output is not None
230
+ assert "while holding ['alt']" in result.output
231
+
232
+ @pytest.mark.asyncio
233
+ async def test_mouse_down(self):
234
+ """Test mouse down action."""
235
+ executor = BaseExecutor()
236
+ result = await executor.mouse_down(button="right", take_screenshot=False)
237
+
238
+ assert isinstance(result, ContentResult)
239
+ assert result.output == "[SIMULATED] Mouse down: right button"
240
+
241
+ @pytest.mark.asyncio
242
+ async def test_mouse_up(self):
243
+ """Test mouse up action."""
244
+ executor = BaseExecutor()
245
+ result = await executor.mouse_up(button="middle", take_screenshot=False)
246
+
247
+ assert isinstance(result, ContentResult)
248
+ assert result.output == "[SIMULATED] Mouse up: middle button"
249
+
250
+ @pytest.mark.asyncio
251
+ async def test_hold_key(self):
252
+ """Test holding a key for duration."""
253
+ executor = BaseExecutor()
254
+
255
+ # Mock sleep to avoid actual wait
256
+ with patch("asyncio.sleep") as mock_sleep:
257
+ result = await executor.hold_key("shift", 0.5, take_screenshot=False)
258
+
259
+ assert isinstance(result, ContentResult)
260
+ assert result.output == "[SIMULATED] Hold key 'shift' for 0.5 seconds"
261
+ mock_sleep.assert_called_once_with(0.5)
262
+
263
+ @pytest.mark.asyncio
264
+ async def test_wait(self):
265
+ """Test wait action."""
266
+ executor = BaseExecutor()
267
+
268
+ # Mock sleep to avoid actual wait
269
+ with patch("asyncio.sleep") as mock_sleep:
270
+ result = await executor.wait(1000) # 1000ms
271
+
272
+ assert isinstance(result, ContentResult)
273
+ assert result.output == "Waited 1000ms"
274
+ mock_sleep.assert_called_once_with(1.0)
275
+
276
+ @pytest.mark.asyncio
277
+ async def test_screenshot(self):
278
+ """Test screenshot action."""
279
+ executor = BaseExecutor()
280
+ result = await executor.screenshot()
281
+
282
+ assert isinstance(result, str)
283
+ # Check it's a valid base64 string (starts with PNG header)
284
+ assert result.startswith("iVBORw0KGgo")
285
+
286
+ @pytest.mark.asyncio
287
+ async def test_position(self):
288
+ """Test getting cursor position."""
289
+ executor = BaseExecutor()
290
+ result = await executor.position()
291
+
292
+ assert isinstance(result, ContentResult)
293
+ assert result.output == "[SIMULATED] Mouse position: (0, 0)"
294
+
295
+ @pytest.mark.asyncio
296
+ async def test_execute(self):
297
+ """Test execute command."""
298
+ executor = BaseExecutor()
299
+ result = await executor.execute("custom command", take_screenshot=False)
300
+
301
+ assert isinstance(result, ContentResult)
302
+ assert result.output == "[SIMULATED] Execute: custom command"
303
+
304
+ @pytest.mark.asyncio
305
+ async def test_type_text_alias(self):
306
+ """Test type_text alias method."""
307
+ executor = BaseExecutor()
308
+ result = await executor.write("test", delay=20, take_screenshot=False)
309
+
310
+ assert isinstance(result, ContentResult)
311
+ assert result.output == "[SIMULATED] Type 'test'"
312
+
313
+ @pytest.mark.asyncio
314
+ async def test_mouse_move_alias(self):
315
+ """Test mouse_move alias method."""
316
+ executor = BaseExecutor()
317
+ result = await executor.mouse_move(100, 200, take_screenshot=False)
318
+
319
+ assert isinstance(result, ContentResult)
320
+ assert result.output == "[SIMULATED] Move mouse to (100, 200)"
321
+
322
+ @pytest.mark.asyncio
323
+ async def test_multiple_actions_with_screenshots(self):
324
+ """Test multiple actions with screenshots to ensure consistency."""
325
+ executor = BaseExecutor()
326
+
327
+ # Test that screenshots are consistent
328
+ screenshot1 = await executor.screenshot()
329
+ screenshot2 = await executor.screenshot()
330
+
331
+ assert screenshot1 == screenshot2 # Simulated screenshots should be identical
332
+
333
+ # Test actions with screenshots
334
+ result1 = await executor.click(10, 20, take_screenshot=True)
335
+ result2 = await executor.write("test", take_screenshot=True)
336
+
337
+ assert result1.base64_image == screenshot1
338
+ assert result2.base64_image == screenshot1