Files
fn_registry/python/functions/browser/cdp_eval_test.py
T
egutierrez 8742cb25be feat(browser): auto-commit con 60 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-07 11:42:31 +02:00

147 lines
4.2 KiB
Python

"""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"] == ""