copilot: wait for chromium boot in ensure_browser_session (SKY-9272) (#5667)
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

This commit is contained in:
Andrew Neilson 2026-04-25 16:15:12 -07:00 committed by GitHub
parent a6203ef52b
commit 0cd99204ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 74 additions and 0 deletions

View file

@ -35,6 +35,8 @@ if TYPE_CHECKING:
LOG = structlog.get_logger()
_SESSION_CLEANUP_TIMEOUT_SECONDS = 5.0
_BROWSER_BOOT_WAIT_SECONDS = 15.0
_BROWSER_BOOT_POLL_INTERVAL_SECONDS = 0.25
@dataclass
@ -195,6 +197,19 @@ async def ensure_browser_session(ctx: AgentContext) -> dict[str, Any] | None:
)
ctx.browser_session_id = session.persistent_browser_session_id
# DefaultPersistentSessionsManager schedules chromium in a background
# task and returns from create_session before browser_context is set,
# so the next mcp_browser_context lookup raises. Wait for it.
async with asyncio.timeout(_BROWSER_BOOT_WAIT_SECONDS):
while True:
state = await app.PERSISTENT_SESSIONS_MANAGER.get_browser_state(
session_id=ctx.browser_session_id,
organization_id=ctx.organization_id,
)
if state and state.browser_context:
break
await asyncio.sleep(_BROWSER_BOOT_POLL_INTERVAL_SECONDS)
sc = skyvern_context.current()
if sc:
sc.run_id = ctx.browser_session_id

View file

@ -122,6 +122,65 @@ async def test_ensure_browser_session_error_dict_omits_raw_exception(monkeypatch
assert "internal:" not in error_text
@pytest.mark.asyncio
async def test_ensure_browser_session_waits_for_browser_context(monkeypatch: pytest.MonkeyPatch) -> None:
# DefaultPersistentSessionsManager.create_session returns before chromium
# has finished booting; ensure_browser_session must poll until
# browser_context is set so the next mcp_browser_context lookup succeeds.
import skyvern.forge.sdk.copilot.runtime as runtime
session = MagicMock()
session.persistent_browser_session_id = "bs_1"
pending_state = MagicMock()
pending_state.browser_context = None
ready_state = MagicMock()
ready_state.browser_context = MagicMock()
mock_manager = MagicMock()
mock_manager.create_session = AsyncMock(return_value=session)
mock_manager.get_browser_state = AsyncMock(side_effect=[None, pending_state, ready_state])
mock_app = MagicMock()
mock_app.PERSISTENT_SESSIONS_MANAGER = mock_manager
monkeypatch.setattr(runtime, "app", mock_app)
monkeypatch.setattr(runtime, "_BROWSER_BOOT_POLL_INTERVAL_SECONDS", 0.0)
ctx = _make_ctx()
result = await ensure_browser_session(ctx)
assert result is None
assert ctx.browser_session_id == "bs_1"
assert mock_manager.get_browser_state.await_count == 3
@pytest.mark.asyncio
async def test_ensure_browser_session_times_out_and_cleans_up(monkeypatch: pytest.MonkeyPatch) -> None:
# If chromium never boots within _BROWSER_BOOT_WAIT_SECONDS, fall into the
# cleanup branch so the agent does not keep building on a phantom session.
import skyvern.forge.sdk.copilot.runtime as runtime
session = MagicMock()
session.persistent_browser_session_id = "bs_2"
mock_manager = MagicMock()
mock_manager.create_session = AsyncMock(return_value=session)
mock_manager.get_browser_state = AsyncMock(return_value=None)
mock_manager.close_session = AsyncMock()
mock_app = MagicMock()
mock_app.PERSISTENT_SESSIONS_MANAGER = mock_manager
monkeypatch.setattr(runtime, "app", mock_app)
monkeypatch.setattr(runtime, "_BROWSER_BOOT_WAIT_SECONDS", 0.05)
monkeypatch.setattr(runtime, "_BROWSER_BOOT_POLL_INTERVAL_SECONDS", 0.0)
ctx = _make_ctx()
result = await ensure_browser_session(ctx)
assert result == {"ok": False, "error": "Failed to create browser session"}
assert ctx.browser_session_id is None
mock_manager.close_session.assert_awaited_once_with(
organization_id="org_1",
browser_session_id="bs_2",
)
@pytest.mark.asyncio
async def test_mcp_browser_context_rejects_missing_api_key(monkeypatch: pytest.MonkeyPatch) -> None:
"""Silently skipping set_api_key_override when ctx.api_key is None would