From 18c04c1f86e18b2fc5272bfc23a1f065d39c7224 Mon Sep 17 00:00:00 2001 From: liumangmang Date: Sat, 30 May 2026 10:17:24 +0800 Subject: [PATCH] test: enhance multi-tab coverage for concurrency and events --- backend/test_browser_tabs.py | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/backend/test_browser_tabs.py b/backend/test_browser_tabs.py index 78bd12f..f7fdad0 100644 --- a/backend/test_browser_tabs.py +++ b/backend/test_browser_tabs.py @@ -145,3 +145,46 @@ async def test_ensure_open_discards_session_if_all_tabs_closed(service, session) await service.screenshot(session.id) assert session.id not in service._sessions + +@pytest.mark.asyncio +async def test_tab_revision_bumps_on_events(service, session): + # Setup listeners for the initial tab + service._setup_tab_listeners(session, session.tabs["tab1"].page) + + # Extract the "load" listener callback + calls = session.tabs["tab1"].page.on.call_args_list + load_callback = next(c[0][1] for c in calls if c[0][0] == "load") + + initial_revision = session.tab_revision + load_callback() + assert session.tab_revision == initial_revision + 1 + +@pytest.mark.asyncio +async def test_session_state_concurrency_with_popup(service, session): + # Setup: page.title() will trigger a new page registration in background + triggered = False + async def mock_title(): + nonlocal triggered + # Only trigger popup once (state() calls title twice: once for tabs list, once for top-level) + if not triggered: + triggered = True + # Simulate a popup arriving while title is being fetched + new_page = AsyncMock() + new_page.on = MagicMock() + new_page.is_closed = MagicMock(return_value=False) + service._handle_new_page(session, new_page) + # Yield to let the registration task start (it will block on the lock) + await asyncio.sleep(0.01) + return "Initial Tab" + + session.tabs["tab1"].page.title = mock_title + + # Call state() which takes the lock and calls _session_state (which calls mock_title) + state = await service.state(session.id) + + assert len(state["tabs"]) == 1 # Still 1 because popup registration is waiting for lock + + # Yield to let registration task finish after state() released the lock + await asyncio.sleep(0.1) + assert len(session.tabs) == 2 + assert session.tab_revision > 0