diff --git a/moli-benchmark/moli_benchmark/wpt_cross/runner.py b/moli-benchmark/moli_benchmark/wpt_cross/runner.py index c031d97558..6c2c7b68c0 100644 --- a/moli-benchmark/moli_benchmark/wpt_cross/runner.py +++ b/moli-benchmark/moli_benchmark/wpt_cross/runner.py @@ -259,7 +259,14 @@ def _target_ids(infos: list[dict[str, Any]]) -> frozenset[str]: async def _close_target(client: RawCdpClient, target_id: str) -> None: command_id = await client.send("Target.closeTarget", {"targetId": target_id}) - response, _ = await client.recv_until_id(command_id, timeout=5) + try: + response, _ = await client.recv_until_id(command_id, timeout=5) + except RawCdpError: + # Another target/context cleanup can win this race. Confirm that the + # requested target is gone before accepting the close failure. + if target_id not in _target_ids(await _target_infos(client)): + return + raise success = (response.get("result") or {}).get("success") if success is False: raise RawCdpError(f"Target.closeTarget rejected target {target_id}") diff --git a/moli-benchmark/tests/test_wpt_target_cleanup.py b/moli-benchmark/tests/test_wpt_target_cleanup.py new file mode 100644 index 0000000000..e81c1d6758 --- /dev/null +++ b/moli-benchmark/tests/test_wpt_target_cleanup.py @@ -0,0 +1,31 @@ +import unittest +from types import SimpleNamespace +from unittest.mock import AsyncMock + +from moli_benchmark.raw_cdp import RawCdpError +from moli_benchmark.wpt_cross.runner import _close_target + + +class WptTargetCloseRaceTests(unittest.IsolatedAsyncioTestCase): + async def test_concurrent_target_removal_is_accepted_after_confirmation(self): + client = SimpleNamespace( + send=AsyncMock(side_effect=[1, 2]), + recv_until_id=AsyncMock(side_effect=[ + RawCdpError("No target with given id found"), + ({"result": {"targetInfos": [{"targetId": "unrelated"}]}}, []), + ]), + ) + await _close_target(client, "case-target") + + async def test_close_errors_for_a_live_target_are_preserved(self): + error = RawCdpError("close rejected") + client = SimpleNamespace( + send=AsyncMock(side_effect=[1, 2]), + recv_until_id=AsyncMock(side_effect=[ + error, + ({"result": {"targetInfos": [{"targetId": "case-target"}]}}, []), + ]), + ) + with self.assertRaises(RawCdpError) as caught: + await _close_target(client, "case-target") + self.assertIs(caught.exception, error)