"""Tests para cdp_press_key — mockean urlopen + create_connection. Mockean la capa de red de CDP: urllib.request.urlopen (lista de targets) y websocket.create_connection (un fake que captura los mensajes enviados y devuelve las respuestas CDP con el id correspondiente). """ import json import os import sys from contextlib import contextmanager sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from browser import cdp_press_key as mod # noqa: E402 from browser.cdp_press_key import cdp_press_key # noqa: E402 class _FakeResp: """Context manager que imita la respuesta de urllib.request.urlopen.""" def __init__(self, payload: list): self._payload = payload def __enter__(self): return self def __exit__(self, *exc): return False def read(self): return json.dumps(self._payload).encode() class _FakeWS: """WebSocket falso: captura los mensajes enviados y responde por id.""" def __init__(self): self.sent = [] self._inbox = [] self.closed = False def send(self, raw: str): msg = json.loads(raw) self.sent.append(msg) # Encola una respuesta CDP vacia con el mismo id (como Chrome devuelve). self._inbox.append(json.dumps({"id": msg["id"], "result": {}})) def recv(self): if self._inbox: return self._inbox.pop(0) return "" def close(self): self.closed = True @contextmanager def _patch(monkeypatch_targets, ws_obj=None, urlopen_exc=None, create_conn_exc=None): """Parchea urlopen y create_connection del modulo. Restaura al salir.""" orig_urlopen = mod.urllib.request.urlopen orig_create = mod.websocket.create_connection def fake_urlopen(url, timeout=5): if urlopen_exc is not None: raise urlopen_exc return _FakeResp(monkeypatch_targets) def fake_create(ws_url, timeout=10): if create_conn_exc is not None: raise create_conn_exc return ws_obj mod.urllib.request.urlopen = fake_urlopen mod.websocket.create_connection = fake_create try: yield finally: mod.urllib.request.urlopen = orig_urlopen mod.websocket.create_connection = orig_create _TARGETS = [ {"type": "page", "url": "https://web.whatsapp.com/", "webSocketDebuggerUrl": "ws://x/1"}, ] def test_golden_enter_emite_rawkeydown_y_keyup_vk13(): """Enter envia rawKeyDown + keyUp con windowsVirtualKeyCode 13.""" ws = _FakeWS() with _patch(_TARGETS, ws_obj=ws): res = cdp_press_key("Enter", target_url_substr="whatsapp") assert res == {"ok": True, "error": ""} assert len(ws.sent) == 2 down, up = ws.sent assert down["method"] == "Input.dispatchKeyEvent" assert down["params"]["type"] == "rawKeyDown" assert down["params"]["key"] == "Enter" assert down["params"]["code"] == "Enter" assert down["params"]["windowsVirtualKeyCode"] == 13 assert down["params"]["nativeVirtualKeyCode"] == 13 assert down["params"]["modifiers"] == 0 assert up["params"]["type"] == "keyUp" assert up["params"]["windowsVirtualKeyCode"] == 13 assert ws.closed is True def test_edge_tecla_no_soportada_ok_false_sin_abrir_ws(): """Una tecla fuera del mapa devuelve ok=False sin tocar la red ni abrir WS.""" ws = _FakeWS() def fail_create(ws_url, timeout=10): raise AssertionError("create_connection no debe llamarse para tecla no soportada") def fail_urlopen(url, timeout=5): raise AssertionError("urlopen no debe llamarse para tecla no soportada") orig_urlopen = mod.urllib.request.urlopen orig_create = mod.websocket.create_connection mod.urllib.request.urlopen = fail_urlopen mod.websocket.create_connection = fail_create try: res = cdp_press_key("F13", target_url_substr="whatsapp") finally: mod.urllib.request.urlopen = orig_urlopen mod.websocket.create_connection = orig_create assert res["ok"] is False assert "unsupported key: F13" in res["error"] assert ws.sent == [] def test_error_create_connection_lanza_ok_false(): """Si create_connection lanza, se captura y devuelve ok=False sin relanzar.""" with _patch(_TARGETS, create_conn_exc=ConnectionRefusedError("ws down")): res = cdp_press_key("Escape", target_url_substr="whatsapp") assert res["ok"] is False assert "ws down" in res["error"]