Skip to content

Commit 74f8e98

Browse files
authored
Merge pull request #380 from dean985/fix/go-to-navigation-error
fix: raise NavigationError when go_to fails
2 parents 9e236b8 + d51576c commit 74f8e98

3 files changed

Lines changed: 45 additions & 54 deletions

File tree

pydoll/browser/tab.py

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
InvalidScriptWithElement,
5252
InvalidTabInitialization,
5353
MissingScreenshotPath,
54+
NavigationError,
5455
NetworkEventsNotEnabled,
5556
NoDialogPresent,
5657
NotAnIFrame,
@@ -112,6 +113,7 @@
112113
CaptureScreenshotResponse,
113114
GetResourceContentResponse,
114115
GetResourceTreeResponse,
116+
NavigateResponse,
115117
PrintToPDFResponse,
116118
)
117119
from pydoll.protocol.runtime.methods import CallFunctionOnResponse, EvaluateResponse
@@ -892,22 +894,20 @@ async def go_to(self, url: str, timeout: int = 300):
892894
"""
893895
Navigate to URL and wait for loading to complete.
894896
895-
Refreshes if URL matches current page.
896-
897897
Args:
898898
url: Target URL to navigate to.
899899
timeout: Maximum seconds to wait for page load (default 300).
900900
901901
Raises:
902+
NavigationError: If the navigation fails (e.g., DNS error).
902903
PageLoadTimeout: If page doesn't finish loading within timeout.
903904
"""
904905
logger.info(f'Navigating to URL: {url} (timeout={timeout}s)')
905-
if await self._refresh_if_url_not_changed(url):
906-
logger.debug('URL matches current page; refreshing instead')
907-
return
908-
909906
async with self._wait_page_load(timeout=timeout):
910-
await self._execute_command(PageCommands.navigate(url))
907+
response: NavigateResponse = await self._execute_command(PageCommands.navigate(url))
908+
error_text = response['result'].get('errorText')
909+
if error_text:
910+
raise NavigationError(url, error_text)
911911
logger.info(f'Navigation complete: {url}')
912912

913913
async def refresh(
@@ -1821,14 +1821,6 @@ def _get_evaluate_command(
18211821
serialization_options=serialization_options,
18221822
)
18231823

1824-
async def _refresh_if_url_not_changed(self, url: str) -> bool:
1825-
"""Refresh page if URL hasn't changed."""
1826-
current_url = await self.current_url
1827-
if current_url == url:
1828-
await self.refresh()
1829-
return True
1830-
return False
1831-
18321824
@staticmethod
18331825
def _validate_argument_error(response: EvaluateResponse) -> None:
18341826
"""

pydoll/exceptions.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,17 @@ class DownloadTimeout(TimeoutException):
211211
message = 'Timed out waiting for download to complete'
212212

213213

214+
class NavigationError(PydollException):
215+
"""Raised when page navigation fails (e.g., DNS resolution failure)."""
216+
217+
def __init__(self, url: str, error_text: str):
218+
self.url = url
219+
self.error_text = error_text
220+
super().__init__(
221+
message=f'Navigation to {url} failed: {error_text}',
222+
)
223+
224+
214225
class ConfigurationException(PydollException):
215226
"""Base class for exceptions related to configuration and options."""
216227

tests/test_browser/test_browser_tab.py

Lines changed: 27 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,6 @@ class TestTabNavigation:
389389
async def test_go_to_new_url(self, tab):
390390
"""Test navigating to a new URL."""
391391
tab._connection_handler.execute_command.side_effect = [
392-
{'result': {'result': {'value': 'https://old-url.com'}}}, # current_url
393392
{'result': {}}, # Page.enable
394393
{'result': {'frameId': 'frame-id'}}, # navigate command
395394
{'result': {}}, # Page.disable
@@ -403,15 +402,37 @@ async def fire_callback(event_name, callback, temporary=False):
403402

404403
await tab.go_to('https://example.com')
405404

406-
assert tab._connection_handler.execute_command.call_count == 4
405+
assert tab._connection_handler.execute_command.call_count == 3
406+
407+
@pytest.mark.asyncio
408+
async def test_go_to_navigation_error(self, tab):
409+
"""Test that navigation errors raise NavigationError."""
410+
from pydoll.exceptions import NavigationError
411+
412+
tab._connection_handler.execute_command.side_effect = [
413+
{'result': {}}, # Page.enable
414+
{'result': {'frameId': 'f', 'errorText': 'net::ERR_NAME_NOT_RESOLVED'}},
415+
{'result': {}}, # Page.disable
416+
]
417+
418+
async def fire_callback(event_name, callback, temporary=False):
419+
callback({'method': event_name, 'params': {}})
420+
return 1
421+
422+
tab._connection_handler.register_callback = AsyncMock(
423+
side_effect=fire_callback
424+
)
425+
426+
with pytest.raises(NavigationError) as exc_info:
427+
await tab.go_to('https://nonexistent.invalid')
428+
assert 'net::ERR_NAME_NOT_RESOLVED' in str(exc_info.value)
407429

408430
@pytest.mark.asyncio
409431
async def test_go_to_same_url(self, tab):
410-
"""Test navigating to the same URL (should refresh)."""
432+
"""Test navigating to the same URL works the same as a new URL."""
411433
tab._connection_handler.execute_command.side_effect = [
412-
{'result': {'result': {'value': 'https://example.com'}}}, # current_url
413434
{'result': {}}, # Page.enable
414-
{'result': {}}, # refresh command
435+
{'result': {'frameId': 'frame-id'}}, # navigate command
415436
{'result': {}}, # Page.disable
416437
]
417438

@@ -423,13 +444,12 @@ async def fire_callback(event_name, callback, temporary=False):
423444

424445
await tab.go_to('https://example.com')
425446

426-
assert tab._connection_handler.execute_command.call_count == 4
447+
assert tab._connection_handler.execute_command.call_count == 3
427448

428449
@pytest.mark.asyncio
429450
async def test_go_to_timeout(self, tab):
430451
"""Test navigation timeout."""
431452
tab._connection_handler.execute_command.side_effect = [
432-
{'result': {'result': {'value': 'https://old-url.com'}}}, # current_url
433453
{'result': {}}, # Page.enable
434454
{'result': {'frameId': 'frame-id'}}, # navigate command
435455
{'result': {}}, # Page.disable
@@ -1827,38 +1847,6 @@ async def fire_callback(event_name, callback, temporary=False):
18271847

18281848
assert tab._page_events_enabled is False
18291849

1830-
@pytest.mark.asyncio
1831-
async def test_refresh_if_url_not_changed_same_url(self, tab):
1832-
"""Test _refresh_if_url_not_changed with same URL."""
1833-
tab._connection_handler.execute_command.side_effect = [
1834-
{'result': {'result': {'value': 'https://example.com'}}}, # current_url call
1835-
{'result': {}}, # Page.enable
1836-
{'result': {}}, # refresh call
1837-
{'result': {}}, # Page.disable
1838-
]
1839-
1840-
async def fire_callback(event_name, callback, temporary=False):
1841-
callback({'method': event_name, 'params': {}})
1842-
return 1
1843-
1844-
tab._connection_handler.register_callback = AsyncMock(side_effect=fire_callback)
1845-
1846-
result = await tab._refresh_if_url_not_changed('https://example.com')
1847-
1848-
assert result is True
1849-
assert tab._connection_handler.execute_command.call_count == 4
1850-
1851-
@pytest.mark.asyncio
1852-
async def test_refresh_if_url_not_changed_different_url(self, tab):
1853-
"""Test _refresh_if_url_not_changed with different URL."""
1854-
tab._connection_handler.execute_command.return_value = {
1855-
'result': {'result': {'value': 'https://different.com'}}
1856-
}
1857-
1858-
result = await tab._refresh_if_url_not_changed('https://example.com')
1859-
1860-
assert result is False
1861-
assert_mock_called_at_least_once(tab._connection_handler)
18621850

18631851

18641852
class TestTabRequestManagement:

0 commit comments

Comments
 (0)