Skyvern/tests/unit_tests/test_streaming_screencast.py
Andrew Neilson 86a4f6aa0d
Some checks are pending
Run tests and pre-commit / Run tests and pre-commit hooks (push) Waiting to run
Run tests and pre-commit / Frontend Lint and Build (push) Waiting to run
Publish Fern Docs / run (push) Waiting to run
Add CDP screencast streaming and interactive input for local mode (#4904)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Shuchang Zheng <wintonzheng0325@gmail.com>
2026-03-24 00:48:11 +00:00

127 lines
5.2 KiB
Python

from __future__ import annotations
from types import SimpleNamespace
from unittest.mock import AsyncMock, Mock
import pytest
from tests.unit_tests._stub_streaming import import_with_stubs
screencast = import_with_stubs(
"skyvern.forge.sdk.routes.streaming.screencast",
extra_stubs=["skyvern.forge.sdk.routes.streaming.screenshot"],
)
def _make_app(browser_manager=None, persistent_sessions_manager=None):
"""Build a fake app namespace to replace screencast.app (an AppHolder proxy)."""
return SimpleNamespace(
BROWSER_MANAGER=browser_manager or SimpleNamespace(),
PERSISTENT_SESSIONS_MANAGER=persistent_sessions_manager or SimpleNamespace(),
)
@pytest.mark.asyncio
async def test_resolve_browser_state_for_workflow_run(monkeypatch: pytest.MonkeyPatch) -> None:
expected_state = object()
fake_app = _make_app(
browser_manager=SimpleNamespace(get_for_workflow_run=Mock(return_value=expected_state), get_for_task=Mock()),
persistent_sessions_manager=SimpleNamespace(get_browser_state=AsyncMock()),
)
monkeypatch.setattr(screencast, "app", fake_app)
result = await screencast._resolve_browser_state("wr_123", "workflow_run")
assert result is expected_state
fake_app.BROWSER_MANAGER.get_for_workflow_run.assert_called_once_with("wr_123")
fake_app.BROWSER_MANAGER.get_for_task.assert_not_called()
fake_app.PERSISTENT_SESSIONS_MANAGER.get_browser_state.assert_not_awaited()
@pytest.mark.asyncio
async def test_resolve_browser_state_for_task(monkeypatch: pytest.MonkeyPatch) -> None:
expected_state = object()
fake_app = _make_app(
browser_manager=SimpleNamespace(get_for_workflow_run=Mock(), get_for_task=Mock(return_value=expected_state)),
persistent_sessions_manager=SimpleNamespace(get_browser_state=AsyncMock()),
)
monkeypatch.setattr(screencast, "app", fake_app)
result = await screencast._resolve_browser_state("task_123", "task", workflow_run_id="wr_123")
assert result is expected_state
fake_app.BROWSER_MANAGER.get_for_task.assert_called_once_with("task_123", "wr_123")
fake_app.BROWSER_MANAGER.get_for_workflow_run.assert_not_called()
fake_app.PERSISTENT_SESSIONS_MANAGER.get_browser_state.assert_not_awaited()
@pytest.mark.asyncio
async def test_resolve_browser_state_for_browser_session(monkeypatch: pytest.MonkeyPatch) -> None:
expected_state = object()
fake_app = _make_app(
browser_manager=SimpleNamespace(get_for_workflow_run=Mock(), get_for_task=Mock()),
persistent_sessions_manager=SimpleNamespace(get_browser_state=AsyncMock(return_value=expected_state)),
)
monkeypatch.setattr(screencast, "app", fake_app)
result = await screencast._resolve_browser_state("bs_123", "browser_session")
assert result is expected_state
fake_app.PERSISTENT_SESSIONS_MANAGER.get_browser_state.assert_awaited_once_with("bs_123")
fake_app.BROWSER_MANAGER.get_for_workflow_run.assert_not_called()
fake_app.BROWSER_MANAGER.get_for_task.assert_not_called()
@pytest.mark.asyncio
async def test_resolve_browser_state_unknown_entity_type(monkeypatch: pytest.MonkeyPatch) -> None:
fake_app = _make_app(
browser_manager=SimpleNamespace(get_for_workflow_run=Mock(), get_for_task=Mock()),
persistent_sessions_manager=SimpleNamespace(get_browser_state=AsyncMock()),
)
monkeypatch.setattr(screencast, "app", fake_app)
result = await screencast._resolve_browser_state("id_123", "unknown")
assert result is None
fake_app.BROWSER_MANAGER.get_for_workflow_run.assert_not_called()
fake_app.BROWSER_MANAGER.get_for_task.assert_not_called()
fake_app.PERSISTENT_SESSIONS_MANAGER.get_browser_state.assert_not_awaited()
@pytest.mark.asyncio
async def test_wait_for_browser_state_returns_when_working_page_is_available(
monkeypatch: pytest.MonkeyPatch,
) -> None:
browser_state = SimpleNamespace(get_working_page=AsyncMock(return_value=object()))
resolve_mock = AsyncMock(return_value=browser_state)
sleep_mock = AsyncMock()
monkeypatch.setattr(screencast, "_resolve_browser_state", resolve_mock)
monkeypatch.setattr(screencast.asyncio, "sleep", sleep_mock)
result = await screencast.wait_for_browser_state("wr_123", "workflow_run", timeout=1, poll_interval=0.1)
assert result is browser_state
resolve_mock.assert_awaited_once_with("wr_123", "workflow_run", None)
browser_state.get_working_page.assert_awaited_once()
sleep_mock.assert_not_awaited()
@pytest.mark.asyncio
async def test_wait_for_browser_state_returns_none_on_timeout(monkeypatch: pytest.MonkeyPatch) -> None:
browser_state = SimpleNamespace(get_working_page=AsyncMock(return_value=None))
resolve_mock = AsyncMock(return_value=browser_state)
sleep_mock = AsyncMock()
monkeypatch.setattr(screencast, "_resolve_browser_state", resolve_mock)
monkeypatch.setattr(screencast.asyncio, "sleep", sleep_mock)
result = await screencast.wait_for_browser_state(
"bs_123",
"browser_session",
timeout=0.3,
poll_interval=0.1,
)
assert result is None
assert resolve_mock.await_count == 3
assert browser_state.get_working_page.await_count == 3
assert sleep_mock.await_count == 3