"""Tests para cdp_eval. Como cdp_eval requiere un Chrome vivo con remote debugging, se mockean las dos fronteras de I/O: - urllib.request.urlopen -> devuelve un /json con 2 targets (uno whatsapp). - websocket.create_connection -> un fake que responde al id==1 con un value. """ import io import json import os import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) import urllib.request import websocket from browser.cdp_eval import cdp_eval # --- Fakes ----------------------------------------------------------------- def _targets_json(): """Dos targets de tipo page: uno de Google, otro de WhatsApp Web.""" return [ { "type": "page", "url": "https://www.google.com/", "webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/page/GOOGLE", }, { "type": "page", "url": "https://web.whatsapp.com/", "webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/page/WA", }, ] class _FakeHTTPResponse: """Context manager que imita la respuesta de urlopen con .read().""" def __init__(self, payload): self._buf = io.BytesIO(json.dumps(payload).encode()) def read(self): return self._buf.read() def __enter__(self): return self def __exit__(self, *exc): return False class _FakeWS: """WebSocket fake: guarda el ws_url usado y responde al evaluate con value. Antes de la respuesta con id==1, emite un evento intermedio (sin id) para verificar que cdp_eval drena eventos hasta encontrar su respuesta. """ last_url = None def __init__(self, url, value): _FakeWS.last_url = url self._value = value self._queue = [] def send(self, raw): msg = json.loads(raw) if msg.get("id") == 1: # Primero un evento de CDP sin id (debe drenarse), luego la respuesta. self._queue.append(json.dumps({ "method": "Runtime.consoleAPICalled", "params": {"type": "log"}, })) self._queue.append(json.dumps({ "id": 1, "result": {"result": {"type": "string", "value": self._value}}, })) def recv(self): if self._queue: return self._queue.pop(0) return "" def close(self): pass # --- Tests ----------------------------------------------------------------- def test_golden_selecciona_target_por_substr_y_devuelve_value(monkeypatch): monkeypatch.setattr( urllib.request, "urlopen", lambda url, timeout=5: _FakeHTTPResponse(_targets_json()), ) monkeypatch.setattr( websocket, "create_connection", lambda url, timeout=10.0: _FakeWS(url, "WhatsApp"), ) res = cdp_eval("document.title", port=9222, target_url_substr="whatsapp") assert res["ok"] is True assert res["value"] == "WhatsApp" assert res["error"] == "" assert res["target_url"] == "https://web.whatsapp.com/" # Confirma que eligio el target whatsapp, no el de google. assert _FakeWS.last_url.endswith("/WA") def test_edge_substr_sin_match_devuelve_ok_false(monkeypatch): monkeypatch.setattr( urllib.request, "urlopen", lambda url, timeout=5: _FakeHTTPResponse(_targets_json()), ) # create_connection no deberia llamarse; si lo hace, revienta el test. monkeypatch.setattr( websocket, "create_connection", lambda *a, **k: (_ for _ in ()).throw(AssertionError("no debe conectar")), ) res = cdp_eval("document.title", port=9222, target_url_substr="nope-no-existe") assert res["ok"] is False assert res["value"] is None assert "no target matching" in res["error"] assert "nope-no-existe" in res["error"] assert res["target_url"] == "" def test_error_urlopen_lanza_devuelve_ok_false(monkeypatch): def _boom(url, timeout=5): raise OSError("connection refused") monkeypatch.setattr(urllib.request, "urlopen", _boom) res = cdp_eval("document.title", port=9222, target_url_substr="whatsapp") assert res["ok"] is False assert res["value"] is None assert "connection refused" in res["error"] assert res["target_url"] == ""