Skyvern/tests/unit/test_real_browser_state_navigate.py

112 lines
3.5 KiB
Python

"""Unit tests for skyvern.webeye.real_browser_state.navigate_to_url.
Covers SKY-8818: pages whose subresources never finish loading must still
succeed if the DOM has parsed, via progressive wait_until degradation.
"""
from unittest.mock import AsyncMock, MagicMock
import pytest
from playwright.async_api import TimeoutError as PlaywrightTimeoutError
from skyvern.exceptions import FailedToNavigateToUrl
from skyvern.webeye.real_browser_state import RealBrowserState
@pytest.fixture
def browser_state() -> RealBrowserState:
# Bypass __init__; navigate_to_url only uses `self` for LOG context and _wait_for_settle.
state = RealBrowserState.__new__(RealBrowserState)
return state
@pytest.mark.asyncio
async def test_navigate_to_url_progresses_from_load_to_domcontentloaded(
browser_state: RealBrowserState,
) -> None:
"""If wait_until='load' times out but 'domcontentloaded' succeeds, we succeed."""
page = MagicMock()
calls: list[str] = []
async def fake_goto(url: str, timeout: int, wait_until: str = "load") -> None:
calls.append(wait_until)
if wait_until == "load":
raise PlaywrightTimeoutError("Page.goto: Timeout 60000ms exceeded (load)")
return None
page.goto = AsyncMock(side_effect=fake_goto)
await browser_state.navigate_to_url(
page=page,
url="https://example.test/slow-subresources",
wait_until="load",
)
assert "load" in calls
assert "domcontentloaded" in calls
assert calls.index("domcontentloaded") > calls.index("load")
@pytest.mark.asyncio
async def test_navigate_to_url_raises_when_all_strategies_fail(
browser_state: RealBrowserState,
) -> None:
"""If every wait_until strategy times out, raise FailedToNavigateToUrl."""
page = MagicMock()
async def always_timeout(url: str, timeout: int, wait_until: str = "load") -> None:
raise PlaywrightTimeoutError(f"Page.goto: Timeout 60000ms exceeded ({wait_until})")
page.goto = AsyncMock(side_effect=always_timeout)
with pytest.raises(FailedToNavigateToUrl):
await browser_state.navigate_to_url(
page=page,
url="https://example.test/fully-dead",
wait_until="load",
)
@pytest.mark.asyncio
async def test_navigate_to_url_honors_caller_supplied_wait_until_on_first_try(
browser_state: RealBrowserState,
) -> None:
"""FileDownloadBlock passes wait_until='domcontentloaded' — it must be honored on first try."""
page = MagicMock()
calls: list[str] = []
async def fake_goto(url: str, timeout: int, wait_until: str = "load") -> None:
calls.append(wait_until)
return None
page.goto = AsyncMock(side_effect=fake_goto)
await browser_state.navigate_to_url(
page=page,
url="https://example.test/fast",
wait_until="domcontentloaded",
)
assert calls == ["domcontentloaded"]
@pytest.mark.asyncio
async def test_navigate_to_url_succeeds_on_first_try_with_default_load(
browser_state: RealBrowserState,
) -> None:
"""Existing callers that use default wait_until='load' must keep working untouched."""
page = MagicMock()
calls: list[str] = []
async def fake_goto(url: str, timeout: int, wait_until: str = "load") -> None:
calls.append(wait_until)
return None
page.goto = AsyncMock(side_effect=fake_goto)
await browser_state.navigate_to_url(
page=page,
url="https://example.test/fast-load",
)
assert calls == ["load"]