"""Offline protocol/error regressions for the live browser helper.""" import importlib.machinery import importlib.util from pathlib import Path import struct import os import subprocess import sys import unittest from unittest.mock import Mock, patch loader = importlib.machinery.SourceFileLoader("page_browser", str(Path(__file__).with_name("rakazo-page-browser"))) spec = importlib.util.spec_from_loader(loader.name, loader) helper = importlib.util.module_from_spec(spec) loader.exec_module(helper) class PageBrowserTest(unittest.TestCase): def test_closed_stdin_cancels_helper_without_a_browser(self): with subprocess.Popen([sys.executable, str(Path(__file__).with_name("rakazo-page-browser")), "snapshot", "{}"], env={**os.environ, "RAKAZO_BROWSER_WATCH_STDIN": "1", "RAKAZO_CDP_PORT": "0"}, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as process: process.stdin.close() self.assertEqual(process.wait(timeout=3), 130) def test_closed_extended_frame_does_not_spin(self): for partial in (b"\x81\x7e", b"\x81\x7f", b"\x81\x80"): with self.subTest(partial=partial): client = helper.CdpClient("ws://127.0.0.1:9222/test") client._sock = Mock() client._sock.recv.return_value = b"" client._buf.extend(partial) with self.assertRaisesRegex(RuntimeError, "closed"): client._read_frame() def test_bounds_frame_before_reading_payload(self): client = helper.CdpClient("ws://127.0.0.1:9222/test") client._sock = Mock() client._buf.extend(b"\x81\x7f" + struct.pack("!Q", 10_000_000)) with self.assertRaisesRegex(RuntimeError, "too large"): client._read_frame() client._sock.recv.assert_not_called() def test_rejects_url_credentials_before_navigation(self): client = Mock() for url in ("http://example:fake-password@example.test", "https://example:fake-password@example.test"): with self.assertRaisesRegex(RuntimeError, "credentials"): helper.navigate(client, "session", url) client.call.assert_not_called() def test_rejects_navigation_error(self): client = Mock() client.call.return_value = {"errorText": "net::ERR_NAME_NOT_RESOLVED"} with self.assertRaisesRegex(RuntimeError, "ERR_NAME_NOT_RESOLVED"): helper.navigate(client, "session", "https://example.test") def test_preserves_completed_actions_when_next_response_is_lost(self): with patch.object(helper, "ensure_helpers"), patch.object(helper, "eval_json") as evaluate: evaluate.side_effect = [True, {}, True, TimeoutError("lost response")] result = helper.act(Mock(), "session", [ {"kind": "click", "ref": "first"}, {"kind": "click", "ref": "second"}, ]) self.assertEqual(result["completed"], 1) self.assertTrue(result["uncertain"]) self.assertFalse(result["ok"]) def test_rejects_missing_text_before_any_action(self): with patch.object(helper, "ensure_helpers") as ensure: with self.assertRaisesRegex(RuntimeError, "Text"): helper.act(Mock(), "session", [{"kind": "fill", "ref": "first"}]) ensure.assert_not_called() def test_discovery_cannot_leave_loopback_endpoint(self): with patch.object(helper, "http_get_json", return_value={"webSocketDebuggerUrl": "ws://example.test:9222/session"}): with self.assertRaisesRegex(RuntimeError, "Unexpected CDP"): helper.discover_ws_url(9222) def test_ensure_world_reuses_named_isolated_context(self): client = helper.CdpClient("ws://127.0.0.1:9222/test") calls = [] def fake_call(method, params=None, *, session_id=None, timeout_s=30.0): calls.append(method) if method == "Page.getFrameTree": return {"frameTree": {"frame": {"id": "frame-1"}}} if method == "Runtime.enable": client._handle_event({ "method": "Runtime.executionContextCreated", "sessionId": session_id, "params": { "context": { "id": 42, "name": helper.WORLD_NAME, "auxData": {"frameId": "frame-1", "isDefault": False}, } }, }) return {} raise AssertionError(f"unexpected CDP call: {method}") client.call = fake_call # type: ignore[method-assign] helper.ensure_world(client, "session-a") self.assertEqual(client.context_id, 42) self.assertNotIn("Page.createIsolatedWorld", calls) def test_context_lookup_rejects_default_foreign_and_unbound_contexts(self): client = helper.CdpClient("ws://127.0.0.1:9222/test") for aux, session in (({"frameId": "frame-1", "isDefault": True}, "session-a"), ({"frameId": "other-frame", "isDefault": False}, "session-a"), ({"frameId": "frame-1", "isDefault": False}, "session-b"), ({}, "session-a")): client._handle_event({"method": "Runtime.executionContextCreated", "sessionId": session, "params": {"context": {"id": 42, "name": helper.WORLD_NAME, "auxData": aux}}}) self.assertIsNone(client.find_world_context(helper.WORLD_NAME, "frame-1", "session-a")) def test_ensure_world_creates_when_missing(self): client = helper.CdpClient("ws://127.0.0.1:9222/test") def fake_call(method, params=None, *, session_id=None, timeout_s=30.0): if method == "Page.getFrameTree": return {"frameTree": {"frame": {"id": "frame-1"}}} if method == "Runtime.enable": return {} if method == "Page.createIsolatedWorld": self.assertEqual(params["worldName"], helper.WORLD_NAME) return {"executionContextId": 7} raise AssertionError(f"unexpected CDP call: {method}") client.call = fake_call # type: ignore[method-assign] helper.ensure_world(client, "session-a") self.assertEqual(client.context_id, 7) if __name__ == "__main__": unittest.main()