@@ -169,7 +169,7 @@ def __getitem__(self, key: str) -> object:
169169 assert exc_info .value .original_error is not None
170170
171171
172- def test_scrape_tool_rejects_mapping_responses_missing_data_on_lookup ():
172+ def test_scrape_tool_wraps_mapping_responses_keyerrors_on_data_lookup ():
173173 class _InconsistentResponse (Mapping [str , object ]):
174174 def __iter__ (self ):
175175 yield "data"
@@ -187,10 +187,12 @@ def __getitem__(self, key: str) -> object:
187187 client = _SyncScrapeClient (_InconsistentResponse ()) # type: ignore[arg-type]
188188
189189 with pytest .raises (
190- HyperbrowserError , match = "scrape tool response must include ' data' "
191- ):
190+ HyperbrowserError , match = "Failed to read scrape tool response data"
191+ ) as exc_info :
192192 WebsiteScrapeTool .runnable (client , {"url" : "https://example.com" })
193193
194+ assert exc_info .value .original_error is not None
195+
194196
195197def test_scrape_tool_wraps_mapping_response_data_inspection_failures ():
196198 class _BrokenContainsResponse (Mapping [str , object ]):
@@ -343,6 +345,9 @@ def __iter__(self):
343345 def __len__ (self ) -> int :
344346 return 1
345347
348+ def __contains__ (self , key : object ) -> bool :
349+ return key == "markdown"
350+
346351 def __getitem__ (self , key : str ) -> object :
347352 _ = key
348353 raise RuntimeError ("cannot read mapping field" )
@@ -358,6 +363,59 @@ def __getitem__(self, key: str) -> object:
358363 assert exc_info .value .original_error is not None
359364
360365
366+ def test_scrape_tool_wraps_mapping_field_keyerrors_after_membership_check ():
367+ class _InconsistentMapping (Mapping [str , object ]):
368+ def __iter__ (self ):
369+ yield "markdown"
370+
371+ def __len__ (self ) -> int :
372+ return 1
373+
374+ def __contains__ (self , key : object ) -> bool :
375+ return key == "markdown"
376+
377+ def __getitem__ (self , key : str ) -> object :
378+ _ = key
379+ raise KeyError ("markdown" )
380+
381+ client = _SyncScrapeClient (_Response (data = _InconsistentMapping ()))
382+
383+ with pytest .raises (
384+ HyperbrowserError ,
385+ match = "Failed to read scrape tool response field 'markdown'" ,
386+ ) as exc_info :
387+ WebsiteScrapeTool .runnable (client , {"url" : "https://example.com" })
388+
389+ assert exc_info .value .original_error is not None
390+
391+
392+ def test_scrape_tool_wraps_mapping_field_inspection_failures ():
393+ class _BrokenContainsMapping (Mapping [str , object ]):
394+ def __iter__ (self ):
395+ yield "markdown"
396+
397+ def __len__ (self ) -> int :
398+ return 1
399+
400+ def __contains__ (self , key : object ) -> bool :
401+ _ = key
402+ raise RuntimeError ("cannot inspect markdown key" )
403+
404+ def __getitem__ (self , key : str ) -> object :
405+ _ = key
406+ return "ignored"
407+
408+ client = _SyncScrapeClient (_Response (data = _BrokenContainsMapping ()))
409+
410+ with pytest .raises (
411+ HyperbrowserError ,
412+ match = "Failed to inspect scrape tool response field 'markdown'" ,
413+ ) as exc_info :
414+ WebsiteScrapeTool .runnable (client , {"url" : "https://example.com" })
415+
416+ assert exc_info .value .original_error is not None
417+
418+
361419def test_screenshot_tool_rejects_non_string_screenshot_field ():
362420 client = _SyncScrapeClient (_Response (data = SimpleNamespace (screenshot = 123 )))
363421
@@ -454,6 +512,9 @@ def __iter__(self):
454512 def __len__ (self ) -> int :
455513 return 1
456514
515+ def __contains__ (self , key : object ) -> bool :
516+ return key == "markdown"
517+
457518 def __getitem__ (self , key : str ) -> object :
458519 _ = key
459520 raise RuntimeError ("cannot read page field" )
@@ -469,6 +530,59 @@ def __getitem__(self, key: str) -> object:
469530 assert exc_info .value .original_error is not None
470531
471532
533+ def test_crawl_tool_wraps_mapping_page_keyerrors_after_membership_check ():
534+ class _InconsistentPage (Mapping [str , object ]):
535+ def __iter__ (self ):
536+ yield "markdown"
537+
538+ def __len__ (self ) -> int :
539+ return 1
540+
541+ def __contains__ (self , key : object ) -> bool :
542+ return key == "markdown"
543+
544+ def __getitem__ (self , key : str ) -> object :
545+ _ = key
546+ raise KeyError ("markdown" )
547+
548+ client = _SyncCrawlClient (_Response (data = [_InconsistentPage ()]))
549+
550+ with pytest .raises (
551+ HyperbrowserError ,
552+ match = "Failed to read crawl tool page field 'markdown' at index 0" ,
553+ ) as exc_info :
554+ WebsiteCrawlTool .runnable (client , {"url" : "https://example.com" })
555+
556+ assert exc_info .value .original_error is not None
557+
558+
559+ def test_crawl_tool_wraps_mapping_page_inspection_failures ():
560+ class _BrokenContainsPage (Mapping [str , object ]):
561+ def __iter__ (self ):
562+ yield "markdown"
563+
564+ def __len__ (self ) -> int :
565+ return 1
566+
567+ def __contains__ (self , key : object ) -> bool :
568+ _ = key
569+ raise RuntimeError ("cannot inspect markdown key" )
570+
571+ def __getitem__ (self , key : str ) -> object :
572+ _ = key
573+ return "ignored"
574+
575+ client = _SyncCrawlClient (_Response (data = [_BrokenContainsPage ()]))
576+
577+ with pytest .raises (
578+ HyperbrowserError ,
579+ match = "Failed to inspect crawl tool page field 'markdown' at index 0" ,
580+ ) as exc_info :
581+ WebsiteCrawlTool .runnable (client , {"url" : "https://example.com" })
582+
583+ assert exc_info .value .original_error is not None
584+
585+
472586def test_crawl_tool_rejects_non_string_page_urls ():
473587 client = _SyncCrawlClient (
474588 _Response (data = [SimpleNamespace (url = 42 , markdown = "body" )])
0 commit comments