mirror of
https://github.com/lexmount/moli.git
synced 2026-09-28 16:01:39 +00:00
178 lines
6.7 KiB
Python
178 lines
6.7 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import json
|
|
import unittest
|
|
|
|
from moli_benchmark.raw_cdp import (
|
|
RawCdpCommandError,
|
|
RawCdpTimeoutError,
|
|
RoutedRawCdpClient,
|
|
)
|
|
|
|
|
|
class _FakeWebSocket:
|
|
def __init__(self) -> None:
|
|
self.sent: list[dict[str, object]] = []
|
|
self.incoming: asyncio.Queue[object] = asyncio.Queue()
|
|
self.sent_event = asyncio.Event()
|
|
|
|
async def send(self, raw: str) -> None:
|
|
self.sent.append(json.loads(raw))
|
|
self.sent_event.set()
|
|
|
|
async def recv(self) -> str:
|
|
value = await self.incoming.get()
|
|
if isinstance(value, BaseException):
|
|
raise value
|
|
return json.dumps(value)
|
|
|
|
async def close(self) -> None:
|
|
await self.incoming.put(RuntimeError("closed"))
|
|
|
|
|
|
class RoutedRawCdpClientTests(unittest.IsolatedAsyncioTestCase):
|
|
async def test_routes_out_of_order_responses_and_preserves_event(self) -> None:
|
|
websocket = _FakeWebSocket()
|
|
client = RoutedRawCdpClient(websocket) # type: ignore[arg-type]
|
|
client.start()
|
|
try:
|
|
first = asyncio.create_task(client.command("Runtime.evaluate"))
|
|
second = asyncio.create_task(client.command("Page.getFrameTree"))
|
|
while len(websocket.sent) < 2:
|
|
websocket.sent_event.clear()
|
|
await websocket.sent_event.wait()
|
|
|
|
first_id = int(websocket.sent[0]["id"])
|
|
second_id = int(websocket.sent[1]["id"])
|
|
await websocket.incoming.put(
|
|
{
|
|
"method": "Page.frameNavigated",
|
|
"sessionId": "session-1",
|
|
"params": {"frame": {"url": "http://fixture/next"}},
|
|
}
|
|
)
|
|
await websocket.incoming.put({"id": second_id, "result": {"second": True}})
|
|
await websocket.incoming.put({"id": first_id, "result": {"first": True}})
|
|
|
|
first_result, second_result = await asyncio.gather(first, second)
|
|
event = await client.wait_for_event(
|
|
"Page.frameNavigated",
|
|
session_id="session-1",
|
|
timeout=0.2,
|
|
)
|
|
|
|
self.assertEqual(first_result.response["result"], {"first": True})
|
|
self.assertEqual(second_result.response["result"], {"second": True})
|
|
self.assertEqual(event.payload["params"]["frame"]["url"], "http://fixture/next")
|
|
self.assertEqual(
|
|
[record["payload"].get("id") for record in client.recorded_messages()],
|
|
[None, second_id, first_id],
|
|
)
|
|
self.assertEqual(
|
|
[record.sequence for record in client.messages_since(1)],
|
|
[2, 3],
|
|
)
|
|
finally:
|
|
await client.close()
|
|
|
|
async def test_event_waiter_checks_each_candidate_only_once(self) -> None:
|
|
websocket = _FakeWebSocket()
|
|
client = RoutedRawCdpClient(websocket) # type: ignore[arg-type]
|
|
client.start()
|
|
try:
|
|
seed = asyncio.create_task(client.command("Runtime.enable"))
|
|
await websocket.sent_event.wait()
|
|
seed_id = int(websocket.sent[0]["id"])
|
|
for label in ("old-1", "old-2"):
|
|
await websocket.incoming.put(
|
|
{"method": "Page.loadEventFired", "params": {"label": label}}
|
|
)
|
|
await websocket.incoming.put({"id": seed_id, "result": {}})
|
|
await seed
|
|
|
|
checked: list[str] = []
|
|
initial_checked = asyncio.Event()
|
|
new_checked = asyncio.Event()
|
|
|
|
def matches_target(payload: dict[str, object]) -> bool:
|
|
params = payload.get("params")
|
|
label = str(params.get("label")) if isinstance(params, dict) else ""
|
|
checked.append(label)
|
|
if len(checked) == 2:
|
|
initial_checked.set()
|
|
if label == "new-ignore":
|
|
new_checked.set()
|
|
return label == "target"
|
|
|
|
waiter = asyncio.create_task(
|
|
client.wait_for_event(
|
|
"Page.loadEventFired",
|
|
predicate=matches_target,
|
|
timeout=0.2,
|
|
)
|
|
)
|
|
await initial_checked.wait()
|
|
await websocket.incoming.put(
|
|
{
|
|
"method": "Page.loadEventFired",
|
|
"params": {"label": "new-ignore"},
|
|
}
|
|
)
|
|
await new_checked.wait()
|
|
await websocket.incoming.put(
|
|
{"method": "Page.loadEventFired", "params": {"label": "target"}}
|
|
)
|
|
|
|
event = await waiter
|
|
self.assertEqual(event.payload["params"]["label"], "target")
|
|
self.assertEqual(checked, ["old-1", "old-2", "new-ignore", "target"])
|
|
finally:
|
|
await client.close()
|
|
|
|
async def test_command_error_retains_exact_cdp_error_and_frames(self) -> None:
|
|
websocket = _FakeWebSocket()
|
|
client = RoutedRawCdpClient(websocket) # type: ignore[arg-type]
|
|
client.start()
|
|
try:
|
|
task = asyncio.create_task(client.command("Runtime.evaluate"))
|
|
await websocket.sent_event.wait()
|
|
message_id = int(websocket.sent[0]["id"])
|
|
await websocket.incoming.put(
|
|
{
|
|
"id": message_id,
|
|
"error": {"code": -32000, "message": "Promise was collected"},
|
|
}
|
|
)
|
|
with self.assertRaises(RawCdpCommandError) as raised:
|
|
await task
|
|
self.assertEqual(raised.exception.error["code"], -32000)
|
|
self.assertEqual(raised.exception.error["message"], "Promise was collected")
|
|
self.assertEqual(raised.exception.messages[-1]["payload"]["id"], message_id)
|
|
finally:
|
|
await client.close()
|
|
|
|
async def test_timeout_keeps_intervening_events(self) -> None:
|
|
websocket = _FakeWebSocket()
|
|
client = RoutedRawCdpClient(websocket) # type: ignore[arg-type]
|
|
client.start()
|
|
try:
|
|
task = asyncio.create_task(client.command("Runtime.evaluate", timeout=0.03))
|
|
await websocket.sent_event.wait()
|
|
await websocket.incoming.put(
|
|
{"method": "Runtime.consoleAPICalled", "params": {"type": "log"}}
|
|
)
|
|
with self.assertRaises(RawCdpTimeoutError) as raised:
|
|
await task
|
|
self.assertEqual(raised.exception.method, "Runtime.evaluate")
|
|
self.assertEqual(
|
|
raised.exception.messages[-1]["payload"]["method"],
|
|
"Runtime.consoleAPICalled",
|
|
)
|
|
finally:
|
|
await client.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|