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
|
@@ -1,165 +1,165 @@
|
|
|
1
|
-
"""Tests for PyAutoGUI executor."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from unittest.mock import AsyncMock, MagicMock, patch
|
|
6
|
-
|
|
7
|
-
import pytest
|
|
8
|
-
|
|
9
|
-
from hud.tools.executors.pyautogui import PyAutoGUIExecutor
|
|
10
|
-
from hud.tools.types import ContentResult
|
|
11
|
-
|
|
12
|
-
# Check if pyautogui is available for test skipping
|
|
13
|
-
PYAUTOGUI_AVAILABLE = PyAutoGUIExecutor.is_available()
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class TestPyAutoGUIExecutor:
|
|
17
|
-
"""Tests for PyAutoGUIExecutor."""
|
|
18
|
-
|
|
19
|
-
def test_is_available(self):
|
|
20
|
-
"""Test is_available method."""
|
|
21
|
-
# The availability is determined by the module-level PYAUTOGUI_AVAILABLE
|
|
22
|
-
assert PyAutoGUIExecutor.is_available() == PYAUTOGUI_AVAILABLE
|
|
23
|
-
|
|
24
|
-
@pytest.mark.skipif(not PYAUTOGUI_AVAILABLE, reason="pyautogui not available")
|
|
25
|
-
@pytest.mark.asyncio
|
|
26
|
-
async def test_screenshot_with_pyautogui(self):
|
|
27
|
-
"""Test screenshot when pyautogui is available."""
|
|
28
|
-
executor = PyAutoGUIExecutor()
|
|
29
|
-
|
|
30
|
-
# Mock pyautogui screenshot
|
|
31
|
-
with patch("pyautogui.screenshot") as mock_screenshot:
|
|
32
|
-
mock_img = MagicMock()
|
|
33
|
-
mock_img.save = MagicMock()
|
|
34
|
-
mock_screenshot.return_value = mock_img
|
|
35
|
-
|
|
36
|
-
result = await executor.screenshot()
|
|
37
|
-
|
|
38
|
-
# screenshot() returns a base64 string, not a ContentResult
|
|
39
|
-
assert isinstance(result, str)
|
|
40
|
-
mock_screenshot.assert_called_once()
|
|
41
|
-
|
|
42
|
-
@pytest.mark.skipif(not PYAUTOGUI_AVAILABLE, reason="pyautogui not available")
|
|
43
|
-
@pytest.mark.asyncio
|
|
44
|
-
async def test_click_with_pyautogui(self):
|
|
45
|
-
"""Test click when pyautogui is available."""
|
|
46
|
-
executor = PyAutoGUIExecutor()
|
|
47
|
-
|
|
48
|
-
with patch("pyautogui.click") as mock_click:
|
|
49
|
-
result = await executor.click(100, 200, "left")
|
|
50
|
-
|
|
51
|
-
assert isinstance(result, ContentResult)
|
|
52
|
-
assert result.output and "Clicked" in result.output
|
|
53
|
-
mock_click.assert_called_once_with(x=100, y=200, button="left")
|
|
54
|
-
|
|
55
|
-
@pytest.mark.skipif(not PYAUTOGUI_AVAILABLE, reason="pyautogui not available")
|
|
56
|
-
@pytest.mark.asyncio
|
|
57
|
-
async def test_type_text_with_pyautogui(self):
|
|
58
|
-
"""Test type when pyautogui is available."""
|
|
59
|
-
executor = PyAutoGUIExecutor()
|
|
60
|
-
|
|
61
|
-
with patch("pyautogui.typewrite") as mock_type:
|
|
62
|
-
result = await executor.write("Hello world")
|
|
63
|
-
|
|
64
|
-
assert isinstance(result, ContentResult)
|
|
65
|
-
assert result.output and "Typed" in result.output
|
|
66
|
-
# The implementation adds interval=0.012 (12ms converted to seconds)
|
|
67
|
-
mock_type.assert_called_once_with("Hello world", interval=0.012)
|
|
68
|
-
|
|
69
|
-
@pytest.mark.skipif(not PYAUTOGUI_AVAILABLE, reason="pyautogui not available")
|
|
70
|
-
@pytest.mark.asyncio
|
|
71
|
-
async def test_press_keys_with_pyautogui(self):
|
|
72
|
-
"""Test press when pyautogui is available."""
|
|
73
|
-
executor = PyAutoGUIExecutor()
|
|
74
|
-
|
|
75
|
-
# For key combinations, the implementation uses hotkey
|
|
76
|
-
with patch("pyautogui.hotkey") as mock_hotkey:
|
|
77
|
-
result = await executor.press(["ctrl", "a"])
|
|
78
|
-
|
|
79
|
-
assert isinstance(result, ContentResult)
|
|
80
|
-
assert result.output and "Pressed" in result.output
|
|
81
|
-
mock_hotkey.assert_called_once_with("ctrl", "a")
|
|
82
|
-
|
|
83
|
-
@pytest.mark.skipif(not PYAUTOGUI_AVAILABLE, reason="pyautogui not available")
|
|
84
|
-
@pytest.mark.asyncio
|
|
85
|
-
async def test_scroll_with_pyautogui(self):
|
|
86
|
-
"""Test scroll when pyautogui is available."""
|
|
87
|
-
executor = PyAutoGUIExecutor()
|
|
88
|
-
|
|
89
|
-
with patch("pyautogui.moveTo") as mock_move, patch("pyautogui.scroll") as mock_scroll:
|
|
90
|
-
result = await executor.scroll(100, 200, scroll_y=5)
|
|
91
|
-
|
|
92
|
-
assert isinstance(result, ContentResult)
|
|
93
|
-
assert result.output and "Scrolled" in result.output
|
|
94
|
-
# First moves to position
|
|
95
|
-
mock_move.assert_called_once_with(100, 200)
|
|
96
|
-
# Then scrolls (note: implementation negates scroll_y)
|
|
97
|
-
mock_scroll.assert_called_once_with(-5)
|
|
98
|
-
|
|
99
|
-
@pytest.mark.skipif(not PYAUTOGUI_AVAILABLE, reason="pyautogui not available")
|
|
100
|
-
@pytest.mark.asyncio
|
|
101
|
-
async def test_move_with_pyautogui(self):
|
|
102
|
-
"""Test move when pyautogui is available."""
|
|
103
|
-
executor = PyAutoGUIExecutor()
|
|
104
|
-
|
|
105
|
-
with patch("pyautogui.moveTo") as mock_move:
|
|
106
|
-
result = await executor.move(300, 400)
|
|
107
|
-
|
|
108
|
-
assert isinstance(result, ContentResult)
|
|
109
|
-
assert result.output and "Moved" in result.output
|
|
110
|
-
# The implementation adds duration=0.1
|
|
111
|
-
mock_move.assert_called_once_with(300, 400, duration=0.1)
|
|
112
|
-
|
|
113
|
-
@pytest.mark.skipif(not PYAUTOGUI_AVAILABLE, reason="pyautogui not available")
|
|
114
|
-
@pytest.mark.asyncio
|
|
115
|
-
async def test_drag_with_pyautogui(self):
|
|
116
|
-
"""Test drag when pyautogui is available."""
|
|
117
|
-
executor = PyAutoGUIExecutor()
|
|
118
|
-
|
|
119
|
-
with patch("pyautogui.dragTo") as mock_drag:
|
|
120
|
-
# drag expects a path (list of coordinate tuples)
|
|
121
|
-
path = [(100, 100), (300, 400)]
|
|
122
|
-
result = await executor.drag(path)
|
|
123
|
-
|
|
124
|
-
assert isinstance(result, ContentResult)
|
|
125
|
-
assert result.output and "Dragged" in result.output
|
|
126
|
-
# Implementation uses dragTo to move to each point
|
|
127
|
-
mock_drag.assert_called()
|
|
128
|
-
|
|
129
|
-
@pytest.mark.asyncio
|
|
130
|
-
async def test_wait(self):
|
|
131
|
-
"""Test wait method."""
|
|
132
|
-
executor = PyAutoGUIExecutor()
|
|
133
|
-
|
|
134
|
-
# Mock asyncio.sleep
|
|
135
|
-
with patch("asyncio.sleep", new_callable=AsyncMock) as mock_sleep:
|
|
136
|
-
# wait expects time in milliseconds
|
|
137
|
-
result = await executor.wait(2500) # 2500ms = 2.5s
|
|
138
|
-
|
|
139
|
-
assert isinstance(result, ContentResult)
|
|
140
|
-
assert result.output and "Waited" in result.output
|
|
141
|
-
# Implementation converts to seconds
|
|
142
|
-
mock_sleep.assert_called_once_with(2.5)
|
|
143
|
-
|
|
144
|
-
@pytest.mark.skipif(not PYAUTOGUI_AVAILABLE, reason="pyautogui not available")
|
|
145
|
-
@pytest.mark.asyncio
|
|
146
|
-
async def test_position_with_pyautogui(self):
|
|
147
|
-
"""Test position when pyautogui is available."""
|
|
148
|
-
executor = PyAutoGUIExecutor()
|
|
149
|
-
|
|
150
|
-
with patch("pyautogui.position") as mock_position:
|
|
151
|
-
mock_position.return_value = (123, 456)
|
|
152
|
-
result = await executor.position()
|
|
153
|
-
|
|
154
|
-
assert isinstance(result, ContentResult)
|
|
155
|
-
assert result.output is not None
|
|
156
|
-
assert "Mouse position" in result.output
|
|
157
|
-
assert "123" in result.output
|
|
158
|
-
assert "456" in result.output
|
|
159
|
-
mock_position.assert_called_once()
|
|
160
|
-
|
|
161
|
-
def test_init_with_display_num(self):
|
|
162
|
-
"""Test initialization with display number."""
|
|
163
|
-
# Should not raise
|
|
164
|
-
executor = PyAutoGUIExecutor(display_num=0)
|
|
165
|
-
assert executor.display_num == 0
|
|
1
|
+
"""Tests for PyAutoGUI executor."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from unittest.mock import AsyncMock, MagicMock, patch
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from hud.tools.executors.pyautogui import PyAutoGUIExecutor
|
|
10
|
+
from hud.tools.types import ContentResult
|
|
11
|
+
|
|
12
|
+
# Check if pyautogui is available for test skipping
|
|
13
|
+
PYAUTOGUI_AVAILABLE = PyAutoGUIExecutor.is_available()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestPyAutoGUIExecutor:
|
|
17
|
+
"""Tests for PyAutoGUIExecutor."""
|
|
18
|
+
|
|
19
|
+
def test_is_available(self):
|
|
20
|
+
"""Test is_available method."""
|
|
21
|
+
# The availability is determined by the module-level PYAUTOGUI_AVAILABLE
|
|
22
|
+
assert PyAutoGUIExecutor.is_available() == PYAUTOGUI_AVAILABLE
|
|
23
|
+
|
|
24
|
+
@pytest.mark.skipif(not PYAUTOGUI_AVAILABLE, reason="pyautogui not available")
|
|
25
|
+
@pytest.mark.asyncio
|
|
26
|
+
async def test_screenshot_with_pyautogui(self):
|
|
27
|
+
"""Test screenshot when pyautogui is available."""
|
|
28
|
+
executor = PyAutoGUIExecutor()
|
|
29
|
+
|
|
30
|
+
# Mock pyautogui screenshot
|
|
31
|
+
with patch("pyautogui.screenshot") as mock_screenshot:
|
|
32
|
+
mock_img = MagicMock()
|
|
33
|
+
mock_img.save = MagicMock()
|
|
34
|
+
mock_screenshot.return_value = mock_img
|
|
35
|
+
|
|
36
|
+
result = await executor.screenshot()
|
|
37
|
+
|
|
38
|
+
# screenshot() returns a base64 string, not a ContentResult
|
|
39
|
+
assert isinstance(result, str)
|
|
40
|
+
mock_screenshot.assert_called_once()
|
|
41
|
+
|
|
42
|
+
@pytest.mark.skipif(not PYAUTOGUI_AVAILABLE, reason="pyautogui not available")
|
|
43
|
+
@pytest.mark.asyncio
|
|
44
|
+
async def test_click_with_pyautogui(self):
|
|
45
|
+
"""Test click when pyautogui is available."""
|
|
46
|
+
executor = PyAutoGUIExecutor()
|
|
47
|
+
|
|
48
|
+
with patch("pyautogui.click") as mock_click:
|
|
49
|
+
result = await executor.click(100, 200, "left")
|
|
50
|
+
|
|
51
|
+
assert isinstance(result, ContentResult)
|
|
52
|
+
assert result.output and "Clicked" in result.output
|
|
53
|
+
mock_click.assert_called_once_with(x=100, y=200, button="left")
|
|
54
|
+
|
|
55
|
+
@pytest.mark.skipif(not PYAUTOGUI_AVAILABLE, reason="pyautogui not available")
|
|
56
|
+
@pytest.mark.asyncio
|
|
57
|
+
async def test_type_text_with_pyautogui(self):
|
|
58
|
+
"""Test type when pyautogui is available."""
|
|
59
|
+
executor = PyAutoGUIExecutor()
|
|
60
|
+
|
|
61
|
+
with patch("pyautogui.typewrite") as mock_type:
|
|
62
|
+
result = await executor.write("Hello world")
|
|
63
|
+
|
|
64
|
+
assert isinstance(result, ContentResult)
|
|
65
|
+
assert result.output and "Typed" in result.output
|
|
66
|
+
# The implementation adds interval=0.012 (12ms converted to seconds)
|
|
67
|
+
mock_type.assert_called_once_with("Hello world", interval=0.012)
|
|
68
|
+
|
|
69
|
+
@pytest.mark.skipif(not PYAUTOGUI_AVAILABLE, reason="pyautogui not available")
|
|
70
|
+
@pytest.mark.asyncio
|
|
71
|
+
async def test_press_keys_with_pyautogui(self):
|
|
72
|
+
"""Test press when pyautogui is available."""
|
|
73
|
+
executor = PyAutoGUIExecutor()
|
|
74
|
+
|
|
75
|
+
# For key combinations, the implementation uses hotkey
|
|
76
|
+
with patch("pyautogui.hotkey") as mock_hotkey:
|
|
77
|
+
result = await executor.press(["ctrl", "a"])
|
|
78
|
+
|
|
79
|
+
assert isinstance(result, ContentResult)
|
|
80
|
+
assert result.output and "Pressed" in result.output
|
|
81
|
+
mock_hotkey.assert_called_once_with("ctrl", "a")
|
|
82
|
+
|
|
83
|
+
@pytest.mark.skipif(not PYAUTOGUI_AVAILABLE, reason="pyautogui not available")
|
|
84
|
+
@pytest.mark.asyncio
|
|
85
|
+
async def test_scroll_with_pyautogui(self):
|
|
86
|
+
"""Test scroll when pyautogui is available."""
|
|
87
|
+
executor = PyAutoGUIExecutor()
|
|
88
|
+
|
|
89
|
+
with patch("pyautogui.moveTo") as mock_move, patch("pyautogui.scroll") as mock_scroll:
|
|
90
|
+
result = await executor.scroll(100, 200, scroll_y=5)
|
|
91
|
+
|
|
92
|
+
assert isinstance(result, ContentResult)
|
|
93
|
+
assert result.output and "Scrolled" in result.output
|
|
94
|
+
# First moves to position
|
|
95
|
+
mock_move.assert_called_once_with(100, 200)
|
|
96
|
+
# Then scrolls (note: implementation negates scroll_y)
|
|
97
|
+
mock_scroll.assert_called_once_with(-5)
|
|
98
|
+
|
|
99
|
+
@pytest.mark.skipif(not PYAUTOGUI_AVAILABLE, reason="pyautogui not available")
|
|
100
|
+
@pytest.mark.asyncio
|
|
101
|
+
async def test_move_with_pyautogui(self):
|
|
102
|
+
"""Test move when pyautogui is available."""
|
|
103
|
+
executor = PyAutoGUIExecutor()
|
|
104
|
+
|
|
105
|
+
with patch("pyautogui.moveTo") as mock_move:
|
|
106
|
+
result = await executor.move(300, 400)
|
|
107
|
+
|
|
108
|
+
assert isinstance(result, ContentResult)
|
|
109
|
+
assert result.output and "Moved" in result.output
|
|
110
|
+
# The implementation adds duration=0.1
|
|
111
|
+
mock_move.assert_called_once_with(300, 400, duration=0.1)
|
|
112
|
+
|
|
113
|
+
@pytest.mark.skipif(not PYAUTOGUI_AVAILABLE, reason="pyautogui not available")
|
|
114
|
+
@pytest.mark.asyncio
|
|
115
|
+
async def test_drag_with_pyautogui(self):
|
|
116
|
+
"""Test drag when pyautogui is available."""
|
|
117
|
+
executor = PyAutoGUIExecutor()
|
|
118
|
+
|
|
119
|
+
with patch("pyautogui.dragTo") as mock_drag:
|
|
120
|
+
# drag expects a path (list of coordinate tuples)
|
|
121
|
+
path = [(100, 100), (300, 400)]
|
|
122
|
+
result = await executor.drag(path)
|
|
123
|
+
|
|
124
|
+
assert isinstance(result, ContentResult)
|
|
125
|
+
assert result.output and "Dragged" in result.output
|
|
126
|
+
# Implementation uses dragTo to move to each point
|
|
127
|
+
mock_drag.assert_called()
|
|
128
|
+
|
|
129
|
+
@pytest.mark.asyncio
|
|
130
|
+
async def test_wait(self):
|
|
131
|
+
"""Test wait method."""
|
|
132
|
+
executor = PyAutoGUIExecutor()
|
|
133
|
+
|
|
134
|
+
# Mock asyncio.sleep
|
|
135
|
+
with patch("asyncio.sleep", new_callable=AsyncMock) as mock_sleep:
|
|
136
|
+
# wait expects time in milliseconds
|
|
137
|
+
result = await executor.wait(2500) # 2500ms = 2.5s
|
|
138
|
+
|
|
139
|
+
assert isinstance(result, ContentResult)
|
|
140
|
+
assert result.output and "Waited" in result.output
|
|
141
|
+
# Implementation converts to seconds
|
|
142
|
+
mock_sleep.assert_called_once_with(2.5)
|
|
143
|
+
|
|
144
|
+
@pytest.mark.skipif(not PYAUTOGUI_AVAILABLE, reason="pyautogui not available")
|
|
145
|
+
@pytest.mark.asyncio
|
|
146
|
+
async def test_position_with_pyautogui(self):
|
|
147
|
+
"""Test position when pyautogui is available."""
|
|
148
|
+
executor = PyAutoGUIExecutor()
|
|
149
|
+
|
|
150
|
+
with patch("pyautogui.position") as mock_position:
|
|
151
|
+
mock_position.return_value = (123, 456)
|
|
152
|
+
result = await executor.position()
|
|
153
|
+
|
|
154
|
+
assert isinstance(result, ContentResult)
|
|
155
|
+
assert result.output is not None
|
|
156
|
+
assert "Mouse position" in result.output
|
|
157
|
+
assert "123" in result.output
|
|
158
|
+
assert "456" in result.output
|
|
159
|
+
mock_position.assert_called_once()
|
|
160
|
+
|
|
161
|
+
def test_init_with_display_num(self):
|
|
162
|
+
"""Test initialization with display number."""
|
|
163
|
+
# Should not raise
|
|
164
|
+
executor = PyAutoGUIExecutor(display_num=0)
|
|
165
|
+
assert executor.display_num == 0
|