|
1 | 1 | import asyncio |
| 2 | +from collections.abc import Mapping |
2 | 3 | from types import SimpleNamespace |
3 | 4 | from typing import Any, Optional |
4 | 5 |
|
@@ -149,6 +150,45 @@ def test_scrape_tool_rejects_non_string_markdown_field(): |
149 | 150 | WebsiteScrapeTool.runnable(client, {"url": "https://example.com"}) |
150 | 151 |
|
151 | 152 |
|
| 153 | +def test_scrape_tool_supports_mapping_response_data(): |
| 154 | + client = _SyncScrapeClient(_Response(data={"markdown": "from mapping"})) |
| 155 | + |
| 156 | + output = WebsiteScrapeTool.runnable(client, {"url": "https://example.com"}) |
| 157 | + |
| 158 | + assert output == "from mapping" |
| 159 | + |
| 160 | + |
| 161 | +def test_scrape_tool_returns_empty_for_missing_mapping_markdown_field(): |
| 162 | + client = _SyncScrapeClient(_Response(data={"other": "value"})) |
| 163 | + |
| 164 | + output = WebsiteScrapeTool.runnable(client, {"url": "https://example.com"}) |
| 165 | + |
| 166 | + assert output == "" |
| 167 | + |
| 168 | + |
| 169 | +def test_scrape_tool_wraps_mapping_field_read_failures(): |
| 170 | + class _BrokenMapping(Mapping[str, object]): |
| 171 | + def __iter__(self): |
| 172 | + yield "markdown" |
| 173 | + |
| 174 | + def __len__(self) -> int: |
| 175 | + return 1 |
| 176 | + |
| 177 | + def __getitem__(self, key: str) -> object: |
| 178 | + _ = key |
| 179 | + raise RuntimeError("cannot read mapping field") |
| 180 | + |
| 181 | + client = _SyncScrapeClient(_Response(data=_BrokenMapping())) |
| 182 | + |
| 183 | + with pytest.raises( |
| 184 | + HyperbrowserError, |
| 185 | + match="Failed to read scrape tool response field 'markdown'", |
| 186 | + ) as exc_info: |
| 187 | + WebsiteScrapeTool.runnable(client, {"url": "https://example.com"}) |
| 188 | + |
| 189 | + assert exc_info.value.original_error is not None |
| 190 | + |
| 191 | + |
152 | 192 | def test_screenshot_tool_rejects_non_string_screenshot_field(): |
153 | 193 | client = _SyncScrapeClient(_Response(data=SimpleNamespace(screenshot=123))) |
154 | 194 |
|
@@ -185,6 +225,48 @@ def markdown(self) -> str: |
185 | 225 | assert exc_info.value.original_error is not None |
186 | 226 |
|
187 | 227 |
|
| 228 | +def test_crawl_tool_supports_mapping_page_items(): |
| 229 | + client = _SyncCrawlClient( |
| 230 | + _Response(data=[{"url": "https://example.com", "markdown": "mapping body"}]) |
| 231 | + ) |
| 232 | + |
| 233 | + output = WebsiteCrawlTool.runnable(client, {"url": "https://example.com"}) |
| 234 | + |
| 235 | + assert "Url: https://example.com" in output |
| 236 | + assert "mapping body" in output |
| 237 | + |
| 238 | + |
| 239 | +def test_crawl_tool_skips_mapping_pages_without_markdown_key(): |
| 240 | + client = _SyncCrawlClient(_Response(data=[{"url": "https://example.com"}])) |
| 241 | + |
| 242 | + output = WebsiteCrawlTool.runnable(client, {"url": "https://example.com"}) |
| 243 | + |
| 244 | + assert output == "" |
| 245 | + |
| 246 | + |
| 247 | +def test_crawl_tool_wraps_mapping_page_value_read_failures(): |
| 248 | + class _BrokenPage(Mapping[str, object]): |
| 249 | + def __iter__(self): |
| 250 | + yield "markdown" |
| 251 | + |
| 252 | + def __len__(self) -> int: |
| 253 | + return 1 |
| 254 | + |
| 255 | + def __getitem__(self, key: str) -> object: |
| 256 | + _ = key |
| 257 | + raise RuntimeError("cannot read page field") |
| 258 | + |
| 259 | + client = _SyncCrawlClient(_Response(data=[_BrokenPage()])) |
| 260 | + |
| 261 | + with pytest.raises( |
| 262 | + HyperbrowserError, |
| 263 | + match="Failed to read crawl tool page field 'markdown' at index 0", |
| 264 | + ) as exc_info: |
| 265 | + WebsiteCrawlTool.runnable(client, {"url": "https://example.com"}) |
| 266 | + |
| 267 | + assert exc_info.value.original_error is not None |
| 268 | + |
| 269 | + |
188 | 270 | def test_crawl_tool_rejects_non_string_page_urls(): |
189 | 271 | client = _SyncCrawlClient( |
190 | 272 | _Response(data=[SimpleNamespace(url=42, markdown="body")]) |
@@ -233,6 +315,14 @@ def test_browser_use_tool_rejects_non_string_final_result(): |
233 | 315 | BrowserUseTool.runnable(client, {"task": "search docs"}) |
234 | 316 |
|
235 | 317 |
|
| 318 | +def test_browser_use_tool_supports_mapping_response_data(): |
| 319 | + client = _SyncBrowserUseClient(_Response(data={"final_result": "mapping output"})) |
| 320 | + |
| 321 | + output = BrowserUseTool.runnable(client, {"task": "search docs"}) |
| 322 | + |
| 323 | + assert output == "mapping output" |
| 324 | + |
| 325 | + |
236 | 326 | def test_async_scrape_tool_wraps_response_data_read_failures(): |
237 | 327 | async def run() -> None: |
238 | 328 | client = _AsyncScrapeClient( |
|
0 commit comments