feat(browser): auto-commit con 60 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
"""Evalua una expresion JavaScript en una pestana de Chrome via Chrome DevTools Protocol.
|
||||
|
||||
Primitiva de transport CDP: localiza un target (pestana) por substring de su URL,
|
||||
abre el WebSocket de depuracion, ejecuta `Runtime.evaluate` y devuelve el valor
|
||||
serializado. Base reutilizable para automatizar el navegador diario sin abrir
|
||||
ventana nueva ni darle foco.
|
||||
"""
|
||||
|
||||
import json
|
||||
import urllib.request
|
||||
|
||||
import websocket
|
||||
|
||||
|
||||
def cdp_eval(
|
||||
expression: str,
|
||||
*,
|
||||
port: int = 9222,
|
||||
target_url_substr: str = "",
|
||||
await_promise: bool = False,
|
||||
timeout_s: float = 10.0,
|
||||
) -> dict:
|
||||
"""Evalua una expresion JS en una pestana de Chrome elegida por substring de URL.
|
||||
|
||||
Args:
|
||||
expression: Expresion JavaScript a evaluar en el contexto de la pagina.
|
||||
port: Puerto de remote debugging de Chrome. Default 9222.
|
||||
target_url_substr: Substring que debe contener la URL del target. Si "",
|
||||
usa el primer target de tipo "page".
|
||||
await_promise: Si True, espera a que se resuelva una Promise antes de
|
||||
devolver el valor (`awaitPromise` de CDP).
|
||||
timeout_s: Timeout (segundos) para la conexion WebSocket.
|
||||
|
||||
Returns:
|
||||
dict con claves:
|
||||
ok: bool — True si la evaluacion produjo un valor sin excepcion.
|
||||
value: valor Python serializable devuelto por la expresion, o None.
|
||||
error: str — mensaje de error (vacio si ok).
|
||||
target_url: str — URL del target usado (vacio si ninguno).
|
||||
"""
|
||||
# 1. Listar targets via HTTP.
|
||||
try:
|
||||
with urllib.request.urlopen(
|
||||
f"http://127.0.0.1:{port}/json", timeout=5
|
||||
) as resp:
|
||||
targets = json.loads(resp.read().decode())
|
||||
except Exception as e: # noqa: BLE001 — red/HTTP/JSON, no relanzar
|
||||
return {"ok": False, "value": None, "error": str(e), "target_url": ""}
|
||||
|
||||
# 2. Elegir el primer target type=="page" cuya url contenga el substring.
|
||||
chosen = None
|
||||
for t in targets:
|
||||
if t.get("type") != "page":
|
||||
continue
|
||||
url = t.get("url", "")
|
||||
if target_url_substr == "" or target_url_substr in url:
|
||||
chosen = t
|
||||
break
|
||||
|
||||
if chosen is None:
|
||||
return {
|
||||
"ok": False,
|
||||
"value": None,
|
||||
"error": f"no target matching {target_url_substr}",
|
||||
"target_url": "",
|
||||
}
|
||||
|
||||
target_url = chosen.get("url", "")
|
||||
ws_url = chosen.get("webSocketDebuggerUrl", "")
|
||||
|
||||
# 3-4. Abrir WS, enviar Runtime.evaluate, drenar eventos hasta id==1.
|
||||
try:
|
||||
ws = websocket.create_connection(ws_url, timeout=timeout_s)
|
||||
except Exception as e: # noqa: BLE001 — conexion WS
|
||||
return {"ok": False, "value": None, "error": str(e), "target_url": ""}
|
||||
|
||||
try:
|
||||
ws.send(json.dumps({
|
||||
"id": 1,
|
||||
"method": "Runtime.evaluate",
|
||||
"params": {
|
||||
"expression": expression,
|
||||
"returnByValue": True,
|
||||
"awaitPromise": await_promise,
|
||||
},
|
||||
}))
|
||||
|
||||
msg = None
|
||||
# Drenar eventos intermedios hasta encontrar la respuesta con id==1.
|
||||
while True:
|
||||
raw = ws.recv()
|
||||
if not raw:
|
||||
break
|
||||
try:
|
||||
parsed = json.loads(raw)
|
||||
except Exception: # noqa: BLE001 — frame no-JSON, ignorar
|
||||
continue
|
||||
if parsed.get("id") == 1:
|
||||
msg = parsed
|
||||
break
|
||||
except Exception as e: # noqa: BLE001 — fallo de transport durante recv/send
|
||||
return {"ok": False, "value": None, "error": str(e), "target_url": target_url}
|
||||
finally:
|
||||
try:
|
||||
ws.close()
|
||||
except Exception: # noqa: BLE001 — cierre best-effort
|
||||
pass
|
||||
|
||||
if msg is None:
|
||||
return {
|
||||
"ok": False,
|
||||
"value": None,
|
||||
"error": "no response for evaluate (id=1)",
|
||||
"target_url": target_url,
|
||||
}
|
||||
|
||||
result = msg.get("result", {})
|
||||
|
||||
# 5. Si hubo excepcion en la evaluacion, devolverla como error.
|
||||
exc = result.get("exceptionDetails")
|
||||
if exc:
|
||||
text = exc.get("text", "evaluation exception")
|
||||
exception = exc.get("exception", {})
|
||||
detail = exception.get("description") or exception.get("value")
|
||||
error = f"{text}: {detail}" if detail else text
|
||||
return {"ok": False, "value": None, "error": error, "target_url": target_url}
|
||||
|
||||
# 6. Extraer el valor serializado por returnByValue.
|
||||
value = result.get("result", {}).get("value")
|
||||
return {"ok": True, "value": value, "error": "", "target_url": target_url}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
expr = sys.argv[1] if len(sys.argv) > 1 else "document.title"
|
||||
substr = sys.argv[2] if len(sys.argv) > 2 else ""
|
||||
out = cdp_eval(expr, port=9222, target_url_substr=substr)
|
||||
print(json.dumps(out, ensure_ascii=False, indent=2))
|
||||
Reference in New Issue
Block a user