hud-python 0.3.5__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.
- hud/__init__.py +22 -89
- hud/agents/__init__.py +17 -0
- hud/agents/art.py +101 -0
- hud/agents/base.py +599 -0
- hud/{mcp → agents}/claude.py +373 -321
- hud/{mcp → agents}/langchain.py +250 -250
- hud/agents/misc/__init__.py +7 -0
- hud/{agent → agents}/misc/response_agent.py +80 -80
- hud/{mcp → agents}/openai.py +352 -334
- hud/agents/openai_chat_generic.py +154 -0
- hud/{mcp → agents}/tests/__init__.py +1 -1
- hud/agents/tests/test_base.py +742 -0
- hud/agents/tests/test_claude.py +324 -0
- hud/{mcp → agents}/tests/test_client.py +363 -324
- hud/{mcp → agents}/tests/test_openai.py +237 -238
- hud/cli/__init__.py +617 -0
- hud/cli/__main__.py +8 -0
- hud/cli/analyze.py +371 -0
- hud/cli/analyze_metadata.py +230 -0
- hud/cli/build.py +427 -0
- hud/cli/clone.py +185 -0
- hud/cli/cursor.py +92 -0
- hud/cli/debug.py +392 -0
- hud/cli/docker_utils.py +83 -0
- hud/cli/init.py +281 -0
- hud/cli/interactive.py +353 -0
- hud/cli/mcp_server.py +756 -0
- hud/cli/pull.py +336 -0
- hud/cli/push.py +379 -0
- hud/cli/remote_runner.py +311 -0
- hud/cli/runner.py +160 -0
- hud/cli/tests/__init__.py +3 -0
- hud/cli/tests/test_analyze.py +284 -0
- hud/cli/tests/test_cli_init.py +265 -0
- hud/cli/tests/test_cli_main.py +27 -0
- hud/cli/tests/test_clone.py +142 -0
- hud/cli/tests/test_cursor.py +253 -0
- hud/cli/tests/test_debug.py +453 -0
- hud/cli/tests/test_mcp_server.py +139 -0
- hud/cli/tests/test_utils.py +388 -0
- hud/cli/utils.py +263 -0
- hud/clients/README.md +143 -0
- hud/clients/__init__.py +16 -0
- hud/clients/base.py +354 -0
- hud/clients/fastmcp.py +202 -0
- hud/clients/mcp_use.py +278 -0
- hud/clients/tests/__init__.py +1 -0
- hud/clients/tests/test_client_integration.py +111 -0
- hud/clients/tests/test_fastmcp.py +342 -0
- hud/clients/tests/test_protocol.py +188 -0
- hud/clients/utils/__init__.py +1 -0
- hud/clients/utils/retry_transport.py +160 -0
- hud/datasets.py +322 -192
- hud/misc/__init__.py +1 -0
- hud/{agent → misc}/claude_plays_pokemon.py +292 -283
- hud/otel/__init__.py +35 -0
- hud/otel/collector.py +142 -0
- hud/otel/config.py +164 -0
- hud/otel/context.py +536 -0
- hud/otel/exporters.py +366 -0
- hud/otel/instrumentation.py +97 -0
- hud/otel/processors.py +118 -0
- hud/otel/tests/__init__.py +1 -0
- hud/otel/tests/test_processors.py +197 -0
- hud/server/__init__.py +5 -5
- hud/server/context.py +114 -0
- hud/server/helper/__init__.py +5 -0
- hud/server/low_level.py +132 -0
- hud/server/server.py +166 -0
- hud/server/tests/__init__.py +3 -0
- hud/settings.py +73 -79
- hud/shared/__init__.py +5 -0
- hud/{exceptions.py → shared/exceptions.py} +180 -180
- hud/{server → shared}/requests.py +264 -264
- hud/shared/tests/test_exceptions.py +157 -0
- hud/{server → shared}/tests/test_requests.py +275 -275
- hud/telemetry/__init__.py +25 -30
- hud/telemetry/instrument.py +379 -0
- hud/telemetry/job.py +309 -141
- hud/telemetry/replay.py +74 -0
- hud/telemetry/trace.py +83 -0
- hud/tools/__init__.py +33 -34
- hud/tools/base.py +365 -65
- hud/tools/bash.py +161 -137
- hud/tools/computer/__init__.py +15 -13
- hud/tools/computer/anthropic.py +437 -420
- hud/tools/computer/hud.py +376 -334
- hud/tools/computer/openai.py +295 -292
- hud/tools/computer/settings.py +82 -0
- hud/tools/edit.py +314 -290
- hud/tools/executors/__init__.py +30 -30
- hud/tools/executors/base.py +539 -532
- hud/tools/executors/pyautogui.py +621 -619
- 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 -503
- hud/tools/{playwright_tool.py → playwright.py} +412 -379
- hud/tools/tests/__init__.py +3 -3
- hud/tools/tests/test_base.py +282 -0
- hud/tools/tests/test_bash.py +158 -152
- hud/tools/tests/test_bash_extended.py +197 -0
- hud/tools/tests/test_computer.py +425 -52
- hud/tools/tests/test_computer_actions.py +34 -34
- hud/tools/tests/test_edit.py +259 -240
- hud/tools/tests/test_init.py +27 -27
- hud/tools/tests/test_playwright_tool.py +183 -183
- hud/tools/tests/test_tools.py +145 -157
- hud/tools/tests/test_utils.py +156 -156
- hud/tools/types.py +72 -0
- hud/tools/utils.py +50 -50
- hud/types.py +136 -89
- hud/utils/__init__.py +10 -16
- hud/utils/async_utils.py +65 -0
- hud/utils/design.py +168 -0
- hud/utils/mcp.py +55 -0
- hud/utils/progress.py +149 -149
- hud/utils/telemetry.py +66 -66
- hud/utils/tests/test_async_utils.py +173 -0
- hud/utils/tests/test_init.py +17 -21
- hud/utils/tests/test_progress.py +261 -225
- hud/utils/tests/test_telemetry.py +82 -37
- hud/utils/tests/test_version.py +8 -8
- hud/version.py +7 -7
- hud_python-0.4.0.dist-info/METADATA +474 -0
- hud_python-0.4.0.dist-info/RECORD +132 -0
- hud_python-0.4.0.dist-info/entry_points.txt +3 -0
- {hud_python-0.3.5.dist-info → hud_python-0.4.0.dist-info}/licenses/LICENSE +21 -21
- hud/adapters/__init__.py +0 -8
- hud/adapters/claude/__init__.py +0 -5
- hud/adapters/claude/adapter.py +0 -180
- hud/adapters/claude/tests/__init__.py +0 -1
- hud/adapters/claude/tests/test_adapter.py +0 -519
- hud/adapters/common/__init__.py +0 -6
- hud/adapters/common/adapter.py +0 -178
- hud/adapters/common/tests/test_adapter.py +0 -289
- hud/adapters/common/types.py +0 -446
- hud/adapters/operator/__init__.py +0 -5
- hud/adapters/operator/adapter.py +0 -108
- hud/adapters/operator/tests/__init__.py +0 -1
- hud/adapters/operator/tests/test_adapter.py +0 -370
- hud/agent/__init__.py +0 -19
- hud/agent/base.py +0 -126
- hud/agent/claude.py +0 -271
- hud/agent/langchain.py +0 -215
- hud/agent/misc/__init__.py +0 -3
- hud/agent/operator.py +0 -268
- hud/agent/tests/__init__.py +0 -1
- hud/agent/tests/test_base.py +0 -202
- hud/env/__init__.py +0 -11
- hud/env/client.py +0 -35
- hud/env/docker_client.py +0 -349
- hud/env/environment.py +0 -446
- hud/env/local_docker_client.py +0 -358
- hud/env/remote_client.py +0 -212
- hud/env/remote_docker_client.py +0 -292
- hud/gym.py +0 -130
- hud/job.py +0 -773
- hud/mcp/__init__.py +0 -17
- hud/mcp/base.py +0 -631
- hud/mcp/client.py +0 -312
- hud/mcp/tests/test_base.py +0 -512
- hud/mcp/tests/test_claude.py +0 -294
- hud/task.py +0 -149
- hud/taskset.py +0 -237
- hud/telemetry/_trace.py +0 -347
- hud/telemetry/context.py +0 -230
- hud/telemetry/exporter.py +0 -575
- hud/telemetry/instrumentation/__init__.py +0 -3
- hud/telemetry/instrumentation/mcp.py +0 -259
- hud/telemetry/instrumentation/registry.py +0 -59
- hud/telemetry/mcp_models.py +0 -270
- hud/telemetry/tests/__init__.py +0 -1
- hud/telemetry/tests/test_context.py +0 -210
- hud/telemetry/tests/test_trace.py +0 -312
- hud/tools/helper/README.md +0 -56
- hud/tools/helper/__init__.py +0 -9
- hud/tools/helper/mcp_server.py +0 -78
- hud/tools/helper/server_initialization.py +0 -115
- hud/tools/helper/utils.py +0 -58
- hud/trajectory.py +0 -94
- hud/utils/agent.py +0 -37
- hud/utils/common.py +0 -256
- hud/utils/config.py +0 -120
- hud/utils/deprecation.py +0 -115
- hud/utils/misc.py +0 -53
- hud/utils/tests/test_common.py +0 -277
- hud/utils/tests/test_config.py +0 -129
- hud_python-0.3.5.dist-info/METADATA +0 -284
- hud_python-0.3.5.dist-info/RECORD +0 -120
- /hud/{adapters/common → shared}/tests/__init__.py +0 -0
- {hud_python-0.3.5.dist-info → hud_python-0.4.0.dist-info}/WHEEL +0 -0
|
@@ -1,183 +1,183 @@
|
|
|
1
|
-
"""Tests for Playwright tool."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from unittest.mock import AsyncMock, patch
|
|
6
|
-
|
|
7
|
-
import pytest
|
|
8
|
-
from mcp.shared.exceptions import McpError
|
|
9
|
-
from mcp.types import INVALID_PARAMS, ImageContent, TextContent
|
|
10
|
-
|
|
11
|
-
from hud.tools.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class TestPlaywrightTool:
|
|
15
|
-
"""Tests for PlaywrightTool."""
|
|
16
|
-
|
|
17
|
-
@pytest.mark.asyncio
|
|
18
|
-
async def test_playwright_tool_init(self):
|
|
19
|
-
"""Test tool initialization."""
|
|
20
|
-
tool = PlaywrightTool()
|
|
21
|
-
assert tool._browser is None
|
|
22
|
-
assert tool.
|
|
23
|
-
assert tool.
|
|
24
|
-
|
|
25
|
-
@pytest.mark.asyncio
|
|
26
|
-
async def test_playwright_tool_invalid_action(self):
|
|
27
|
-
"""Test that invalid action raises error."""
|
|
28
|
-
tool = PlaywrightTool()
|
|
29
|
-
|
|
30
|
-
with pytest.raises(McpError) as exc_info:
|
|
31
|
-
await tool(action="invalid_action")
|
|
32
|
-
|
|
33
|
-
assert exc_info.value.error.code == INVALID_PARAMS
|
|
34
|
-
assert "Unknown action" in exc_info.value.error.message
|
|
35
|
-
|
|
36
|
-
@pytest.mark.asyncio
|
|
37
|
-
async def test_playwright_tool_navigate_with_mocked_browser(self):
|
|
38
|
-
"""Test navigate action with mocked browser."""
|
|
39
|
-
tool = PlaywrightTool()
|
|
40
|
-
|
|
41
|
-
# Mock the browser components
|
|
42
|
-
mock_page = AsyncMock()
|
|
43
|
-
mock_page.goto = AsyncMock()
|
|
44
|
-
|
|
45
|
-
with patch.object(tool, "_ensure_browser", new_callable=AsyncMock) as mock_ensure:
|
|
46
|
-
# Set up the tool with mocked page
|
|
47
|
-
tool.
|
|
48
|
-
|
|
49
|
-
blocks = await tool(action="navigate", url="https://example.com")
|
|
50
|
-
|
|
51
|
-
assert blocks is not None
|
|
52
|
-
assert any(isinstance(b, TextContent) for b in blocks)
|
|
53
|
-
# The actual call includes wait_until parameter with a Field object
|
|
54
|
-
mock_page.goto.assert_called_once()
|
|
55
|
-
args, kwargs = mock_page.goto.call_args
|
|
56
|
-
assert args[0] == "https://example.com"
|
|
57
|
-
mock_ensure.assert_called_once()
|
|
58
|
-
|
|
59
|
-
@pytest.mark.asyncio
|
|
60
|
-
async def test_playwright_tool_click_with_mocked_browser(self):
|
|
61
|
-
"""Test click action with mocked browser."""
|
|
62
|
-
tool = PlaywrightTool()
|
|
63
|
-
|
|
64
|
-
# Mock the browser components
|
|
65
|
-
mock_page = AsyncMock()
|
|
66
|
-
mock_page.click = AsyncMock()
|
|
67
|
-
|
|
68
|
-
with patch.object(tool, "_ensure_browser", new_callable=AsyncMock):
|
|
69
|
-
# Set up the tool with mocked page
|
|
70
|
-
tool.
|
|
71
|
-
|
|
72
|
-
blocks = await tool(action="click", selector="button#submit")
|
|
73
|
-
|
|
74
|
-
assert blocks is not None
|
|
75
|
-
assert any(isinstance(b, TextContent) for b in blocks)
|
|
76
|
-
mock_page.click.assert_called_once_with("button#submit")
|
|
77
|
-
|
|
78
|
-
@pytest.mark.asyncio
|
|
79
|
-
async def test_playwright_tool_type_with_mocked_browser(self):
|
|
80
|
-
"""Test type action with mocked browser."""
|
|
81
|
-
tool = PlaywrightTool()
|
|
82
|
-
|
|
83
|
-
# Mock the browser components
|
|
84
|
-
mock_page = AsyncMock()
|
|
85
|
-
mock_page.fill = AsyncMock() # Playwright uses fill, not type
|
|
86
|
-
|
|
87
|
-
with patch.object(tool, "_ensure_browser", new_callable=AsyncMock):
|
|
88
|
-
# Set up the tool with mocked page
|
|
89
|
-
tool.
|
|
90
|
-
|
|
91
|
-
blocks = await tool(action="type", selector="input#name", text="John Doe")
|
|
92
|
-
|
|
93
|
-
assert blocks is not None
|
|
94
|
-
assert any(isinstance(b, TextContent) for b in blocks)
|
|
95
|
-
mock_page.fill.assert_called_once_with("input#name", "John Doe")
|
|
96
|
-
|
|
97
|
-
@pytest.mark.asyncio
|
|
98
|
-
async def test_playwright_tool_screenshot_with_mocked_browser(self):
|
|
99
|
-
"""Test screenshot action with mocked browser."""
|
|
100
|
-
tool = PlaywrightTool()
|
|
101
|
-
|
|
102
|
-
# Mock the browser components
|
|
103
|
-
mock_page = AsyncMock()
|
|
104
|
-
mock_page.screenshot = AsyncMock(return_value=b"fake_screenshot_data")
|
|
105
|
-
|
|
106
|
-
with patch.object(tool, "_ensure_browser", new_callable=AsyncMock):
|
|
107
|
-
# Set up the tool with mocked page
|
|
108
|
-
tool.
|
|
109
|
-
|
|
110
|
-
blocks = await tool(action="screenshot")
|
|
111
|
-
|
|
112
|
-
assert blocks is not None
|
|
113
|
-
assert len(blocks) > 0
|
|
114
|
-
assert any(isinstance(b, ImageContent | TextContent) for b in blocks)
|
|
115
|
-
mock_page.screenshot.assert_called_once()
|
|
116
|
-
|
|
117
|
-
@pytest.mark.asyncio
|
|
118
|
-
async def test_playwright_tool_get_page_info_with_mocked_browser(self):
|
|
119
|
-
"""Test get_page_info action with mocked browser."""
|
|
120
|
-
tool = PlaywrightTool()
|
|
121
|
-
|
|
122
|
-
# Mock the browser components
|
|
123
|
-
mock_page = AsyncMock()
|
|
124
|
-
mock_page.url = "https://example.com"
|
|
125
|
-
mock_page.title = AsyncMock(return_value="Example Page")
|
|
126
|
-
mock_page.evaluate = AsyncMock(return_value={"height": 1000})
|
|
127
|
-
|
|
128
|
-
with patch.object(tool, "_ensure_browser", new_callable=AsyncMock):
|
|
129
|
-
# Set up the tool with mocked page
|
|
130
|
-
tool.
|
|
131
|
-
|
|
132
|
-
blocks = await tool(action="get_page_info")
|
|
133
|
-
|
|
134
|
-
assert blocks is not None
|
|
135
|
-
assert any(isinstance(b, TextContent) for b in blocks)
|
|
136
|
-
# Check that the text contains expected info
|
|
137
|
-
text_blocks = [b.text for b in blocks if isinstance(b, TextContent)]
|
|
138
|
-
combined_text = " ".join(text_blocks)
|
|
139
|
-
assert "https://example.com" in combined_text
|
|
140
|
-
assert "Example Page" in combined_text
|
|
141
|
-
|
|
142
|
-
@pytest.mark.asyncio
|
|
143
|
-
async def test_playwright_tool_wait_for_element_with_mocked_browser(self):
|
|
144
|
-
"""Test wait_for_element action with mocked browser."""
|
|
145
|
-
tool = PlaywrightTool()
|
|
146
|
-
|
|
147
|
-
# Mock the browser components
|
|
148
|
-
mock_page = AsyncMock()
|
|
149
|
-
mock_page.wait_for_selector = AsyncMock()
|
|
150
|
-
|
|
151
|
-
with patch.object(tool, "_ensure_browser", new_callable=AsyncMock):
|
|
152
|
-
# Set up the tool with mocked page
|
|
153
|
-
tool.
|
|
154
|
-
|
|
155
|
-
# wait_for_element doesn't accept timeout parameter directly
|
|
156
|
-
blocks = await tool(action="wait_for_element", selector="div#loaded")
|
|
157
|
-
|
|
158
|
-
assert blocks is not None
|
|
159
|
-
assert any(isinstance(b, TextContent) for b in blocks)
|
|
160
|
-
# Default timeout is used
|
|
161
|
-
mock_page.wait_for_selector.assert_called_once()
|
|
162
|
-
|
|
163
|
-
@pytest.mark.asyncio
|
|
164
|
-
async def test_playwright_tool_cleanup(self):
|
|
165
|
-
"""Test cleanup functionality."""
|
|
166
|
-
tool = PlaywrightTool()
|
|
167
|
-
|
|
168
|
-
# Mock browser and context
|
|
169
|
-
mock_browser = AsyncMock()
|
|
170
|
-
mock_context = AsyncMock()
|
|
171
|
-
mock_page = AsyncMock()
|
|
172
|
-
|
|
173
|
-
tool._browser = mock_browser
|
|
174
|
-
tool.
|
|
175
|
-
tool.
|
|
176
|
-
|
|
177
|
-
# Call the cleanup method directly (tool is not a context manager)
|
|
178
|
-
await tool.close()
|
|
179
|
-
|
|
180
|
-
mock_browser.close.assert_called_once()
|
|
181
|
-
assert tool._browser is None
|
|
182
|
-
assert tool.
|
|
183
|
-
assert tool.
|
|
1
|
+
"""Tests for Playwright tool."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from unittest.mock import AsyncMock, patch
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from mcp.shared.exceptions import McpError
|
|
9
|
+
from mcp.types import INVALID_PARAMS, ImageContent, TextContent
|
|
10
|
+
|
|
11
|
+
from hud.tools.playwright import PlaywrightTool
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestPlaywrightTool:
|
|
15
|
+
"""Tests for PlaywrightTool."""
|
|
16
|
+
|
|
17
|
+
@pytest.mark.asyncio
|
|
18
|
+
async def test_playwright_tool_init(self):
|
|
19
|
+
"""Test tool initialization."""
|
|
20
|
+
tool = PlaywrightTool()
|
|
21
|
+
assert tool._browser is None
|
|
22
|
+
assert tool._browser_context is None
|
|
23
|
+
assert tool.page is None
|
|
24
|
+
|
|
25
|
+
@pytest.mark.asyncio
|
|
26
|
+
async def test_playwright_tool_invalid_action(self):
|
|
27
|
+
"""Test that invalid action raises error."""
|
|
28
|
+
tool = PlaywrightTool()
|
|
29
|
+
|
|
30
|
+
with pytest.raises(McpError) as exc_info:
|
|
31
|
+
await tool(action="invalid_action")
|
|
32
|
+
|
|
33
|
+
assert exc_info.value.error.code == INVALID_PARAMS
|
|
34
|
+
assert "Unknown action" in exc_info.value.error.message
|
|
35
|
+
|
|
36
|
+
@pytest.mark.asyncio
|
|
37
|
+
async def test_playwright_tool_navigate_with_mocked_browser(self):
|
|
38
|
+
"""Test navigate action with mocked browser."""
|
|
39
|
+
tool = PlaywrightTool()
|
|
40
|
+
|
|
41
|
+
# Mock the browser components
|
|
42
|
+
mock_page = AsyncMock()
|
|
43
|
+
mock_page.goto = AsyncMock()
|
|
44
|
+
|
|
45
|
+
with patch.object(tool, "_ensure_browser", new_callable=AsyncMock) as mock_ensure:
|
|
46
|
+
# Set up the tool with mocked page
|
|
47
|
+
tool.page = mock_page
|
|
48
|
+
|
|
49
|
+
blocks = await tool(action="navigate", url="https://example.com")
|
|
50
|
+
|
|
51
|
+
assert blocks is not None
|
|
52
|
+
assert any(isinstance(b, TextContent) for b in blocks)
|
|
53
|
+
# The actual call includes wait_until parameter with a Field object
|
|
54
|
+
mock_page.goto.assert_called_once()
|
|
55
|
+
args, kwargs = mock_page.goto.call_args
|
|
56
|
+
assert args[0] == "https://example.com"
|
|
57
|
+
mock_ensure.assert_called_once()
|
|
58
|
+
|
|
59
|
+
@pytest.mark.asyncio
|
|
60
|
+
async def test_playwright_tool_click_with_mocked_browser(self):
|
|
61
|
+
"""Test click action with mocked browser."""
|
|
62
|
+
tool = PlaywrightTool()
|
|
63
|
+
|
|
64
|
+
# Mock the browser components
|
|
65
|
+
mock_page = AsyncMock()
|
|
66
|
+
mock_page.click = AsyncMock()
|
|
67
|
+
|
|
68
|
+
with patch.object(tool, "_ensure_browser", new_callable=AsyncMock):
|
|
69
|
+
# Set up the tool with mocked page
|
|
70
|
+
tool.page = mock_page
|
|
71
|
+
|
|
72
|
+
blocks = await tool(action="click", selector="button#submit")
|
|
73
|
+
|
|
74
|
+
assert blocks is not None
|
|
75
|
+
assert any(isinstance(b, TextContent) for b in blocks)
|
|
76
|
+
mock_page.click.assert_called_once_with("button#submit", button="left", click_count=1)
|
|
77
|
+
|
|
78
|
+
@pytest.mark.asyncio
|
|
79
|
+
async def test_playwright_tool_type_with_mocked_browser(self):
|
|
80
|
+
"""Test type action with mocked browser."""
|
|
81
|
+
tool = PlaywrightTool()
|
|
82
|
+
|
|
83
|
+
# Mock the browser components
|
|
84
|
+
mock_page = AsyncMock()
|
|
85
|
+
mock_page.fill = AsyncMock() # Playwright uses fill, not type
|
|
86
|
+
|
|
87
|
+
with patch.object(tool, "_ensure_browser", new_callable=AsyncMock):
|
|
88
|
+
# Set up the tool with mocked page
|
|
89
|
+
tool.page = mock_page
|
|
90
|
+
|
|
91
|
+
blocks = await tool(action="type", selector="input#name", text="John Doe")
|
|
92
|
+
|
|
93
|
+
assert blocks is not None
|
|
94
|
+
assert any(isinstance(b, TextContent) for b in blocks)
|
|
95
|
+
mock_page.fill.assert_called_once_with("input#name", "John Doe")
|
|
96
|
+
|
|
97
|
+
@pytest.mark.asyncio
|
|
98
|
+
async def test_playwright_tool_screenshot_with_mocked_browser(self):
|
|
99
|
+
"""Test screenshot action with mocked browser."""
|
|
100
|
+
tool = PlaywrightTool()
|
|
101
|
+
|
|
102
|
+
# Mock the browser components
|
|
103
|
+
mock_page = AsyncMock()
|
|
104
|
+
mock_page.screenshot = AsyncMock(return_value=b"fake_screenshot_data")
|
|
105
|
+
|
|
106
|
+
with patch.object(tool, "_ensure_browser", new_callable=AsyncMock):
|
|
107
|
+
# Set up the tool with mocked page
|
|
108
|
+
tool.page = mock_page
|
|
109
|
+
|
|
110
|
+
blocks = await tool(action="screenshot")
|
|
111
|
+
|
|
112
|
+
assert blocks is not None
|
|
113
|
+
assert len(blocks) > 0
|
|
114
|
+
assert any(isinstance(b, ImageContent | TextContent) for b in blocks)
|
|
115
|
+
mock_page.screenshot.assert_called_once()
|
|
116
|
+
|
|
117
|
+
@pytest.mark.asyncio
|
|
118
|
+
async def test_playwright_tool_get_page_info_with_mocked_browser(self):
|
|
119
|
+
"""Test get_page_info action with mocked browser."""
|
|
120
|
+
tool = PlaywrightTool()
|
|
121
|
+
|
|
122
|
+
# Mock the browser components
|
|
123
|
+
mock_page = AsyncMock()
|
|
124
|
+
mock_page.url = "https://example.com"
|
|
125
|
+
mock_page.title = AsyncMock(return_value="Example Page")
|
|
126
|
+
mock_page.evaluate = AsyncMock(return_value={"height": 1000})
|
|
127
|
+
|
|
128
|
+
with patch.object(tool, "_ensure_browser", new_callable=AsyncMock):
|
|
129
|
+
# Set up the tool with mocked page
|
|
130
|
+
tool.page = mock_page
|
|
131
|
+
|
|
132
|
+
blocks = await tool(action="get_page_info")
|
|
133
|
+
|
|
134
|
+
assert blocks is not None
|
|
135
|
+
assert any(isinstance(b, TextContent) for b in blocks)
|
|
136
|
+
# Check that the text contains expected info
|
|
137
|
+
text_blocks = [b.text for b in blocks if isinstance(b, TextContent)]
|
|
138
|
+
combined_text = " ".join(text_blocks)
|
|
139
|
+
assert "https://example.com" in combined_text
|
|
140
|
+
assert "Example Page" in combined_text
|
|
141
|
+
|
|
142
|
+
@pytest.mark.asyncio
|
|
143
|
+
async def test_playwright_tool_wait_for_element_with_mocked_browser(self):
|
|
144
|
+
"""Test wait_for_element action with mocked browser."""
|
|
145
|
+
tool = PlaywrightTool()
|
|
146
|
+
|
|
147
|
+
# Mock the browser components
|
|
148
|
+
mock_page = AsyncMock()
|
|
149
|
+
mock_page.wait_for_selector = AsyncMock()
|
|
150
|
+
|
|
151
|
+
with patch.object(tool, "_ensure_browser", new_callable=AsyncMock):
|
|
152
|
+
# Set up the tool with mocked page
|
|
153
|
+
tool.page = mock_page
|
|
154
|
+
|
|
155
|
+
# wait_for_element doesn't accept timeout parameter directly
|
|
156
|
+
blocks = await tool(action="wait_for_element", selector="div#loaded")
|
|
157
|
+
|
|
158
|
+
assert blocks is not None
|
|
159
|
+
assert any(isinstance(b, TextContent) for b in blocks)
|
|
160
|
+
# Default timeout is used
|
|
161
|
+
mock_page.wait_for_selector.assert_called_once()
|
|
162
|
+
|
|
163
|
+
@pytest.mark.asyncio
|
|
164
|
+
async def test_playwright_tool_cleanup(self):
|
|
165
|
+
"""Test cleanup functionality."""
|
|
166
|
+
tool = PlaywrightTool()
|
|
167
|
+
|
|
168
|
+
# Mock browser and context
|
|
169
|
+
mock_browser = AsyncMock()
|
|
170
|
+
mock_context = AsyncMock()
|
|
171
|
+
mock_page = AsyncMock()
|
|
172
|
+
|
|
173
|
+
tool._browser = mock_browser
|
|
174
|
+
tool._browser_context = mock_context
|
|
175
|
+
tool.page = mock_page
|
|
176
|
+
|
|
177
|
+
# Call the cleanup method directly (tool is not a context manager)
|
|
178
|
+
await tool.close()
|
|
179
|
+
|
|
180
|
+
mock_browser.close.assert_called_once()
|
|
181
|
+
assert tool._browser is None
|
|
182
|
+
assert tool._browser_context is None
|
|
183
|
+
assert tool.page is None
|