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,166 @@
|
||||
"""Hace un click de raton real en coordenadas (x, y) de una pestana de Chrome via CDP.
|
||||
|
||||
Primitiva de input CDP: localiza un target (pestana) por substring de su URL, abre el
|
||||
WebSocket de depuracion y emite los eventos `Input.dispatchMouseEvent` que componen un
|
||||
click izquierdo sintetico real (mousePressed + mouseReleased), opcionalmente precedido
|
||||
de un mouseMoved para que la SPA registre el hover.
|
||||
|
||||
Necesario porque `element.click()` de JavaScript NO dispara los handlers de React de
|
||||
muchas SPAs (WhatsApp Web entre ellas): abrir un chat de la lista o un resultado de
|
||||
busqueda requiere un click de raton sintetico real. El caller resuelve las coordenadas
|
||||
del elemento con `cdp_eval_py_browser` (getBoundingClientRect -> centro) y las pasa aqui.
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
import urllib.request
|
||||
|
||||
import websocket
|
||||
|
||||
|
||||
def cdp_click_xy(
|
||||
x: int,
|
||||
y: int,
|
||||
*,
|
||||
port: int = 9222,
|
||||
target_url_substr: str = "",
|
||||
move_first: bool = True,
|
||||
timeout_s: float = 10.0,
|
||||
) -> dict:
|
||||
"""Hace un click izquierdo real en (x, y) del viewport de una pestana de Chrome.
|
||||
|
||||
Localiza el target `page` por substring de URL, abre el WebSocket CDP y emite un
|
||||
click izquierdo simple via `Input.dispatchMouseEvent`: si `move_first`, primero un
|
||||
`mouseMoved` a (x, y) (para que la SPA registre hover), luego `mousePressed` y
|
||||
`mouseReleased` con `button=left`, `buttons=1`, `clickCount=1`. Las coordenadas son
|
||||
CSS px del viewport; el caller las resuelve normalmente con `cdp_eval_py_browser`
|
||||
(getBoundingClientRect -> centro).
|
||||
|
||||
Args:
|
||||
x: Coordenada X en CSS px del viewport donde hacer click.
|
||||
y: Coordenada Y en CSS px del viewport donde hacer click.
|
||||
port: Puerto de remote debugging de Chrome. Default 9222.
|
||||
target_url_substr: Substring que debe contener la URL del target (pestana).
|
||||
Si "", usa el primer target de tipo "page".
|
||||
move_first: Si True, emite un mouseMoved a (x, y) antes del click para que la
|
||||
SPA registre el hover. Default True.
|
||||
timeout_s: Timeout (segundos) para la conexion WebSocket. Default 10.0.
|
||||
|
||||
Returns:
|
||||
dict con claves:
|
||||
ok: bool — True si los eventos de raton se emitieron sin error.
|
||||
error: str — mensaje de error (vacio si ok).
|
||||
x: int — coordenada X usada (eco del argumento).
|
||||
y: int — coordenada Y usada (eco del argumento).
|
||||
|
||||
Nunca lanza: errores de red/conexion/transport se devuelven en "error" con
|
||||
ok=False.
|
||||
"""
|
||||
# 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, "error": str(e), "x": x, "y": y}
|
||||
|
||||
# 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,
|
||||
"error": f"no target matching {target_url_substr}",
|
||||
"x": x,
|
||||
"y": y,
|
||||
}
|
||||
|
||||
ws_url = chosen.get("webSocketDebuggerUrl", "")
|
||||
|
||||
# 3. Abrir WS.
|
||||
try:
|
||||
ws = websocket.create_connection(ws_url, timeout=timeout_s)
|
||||
except Exception as e: # noqa: BLE001 — conexion WS
|
||||
return {"ok": False, "error": str(e), "x": x, "y": y}
|
||||
|
||||
try:
|
||||
msg_id = 1
|
||||
|
||||
# 3b. Hover previo opcional: ayuda a que la SPA registre el mouseover.
|
||||
if move_first:
|
||||
ws.send(json.dumps({
|
||||
"id": msg_id,
|
||||
"method": "Input.dispatchMouseEvent",
|
||||
"params": {"type": "mouseMoved", "x": x, "y": y},
|
||||
}))
|
||||
msg_id += 1
|
||||
time.sleep(0.03)
|
||||
|
||||
# 4. Click izquierdo real: mousePressed + mouseReleased.
|
||||
press_id = msg_id
|
||||
ws.send(json.dumps({
|
||||
"id": press_id,
|
||||
"method": "Input.dispatchMouseEvent",
|
||||
"params": {
|
||||
"type": "mousePressed",
|
||||
"x": x,
|
||||
"y": y,
|
||||
"button": "left",
|
||||
"buttons": 1,
|
||||
"clickCount": 1,
|
||||
},
|
||||
}))
|
||||
time.sleep(0.04)
|
||||
release_id = msg_id + 1
|
||||
ws.send(json.dumps({
|
||||
"id": release_id,
|
||||
"method": "Input.dispatchMouseEvent",
|
||||
"params": {
|
||||
"type": "mouseReleased",
|
||||
"x": x,
|
||||
"y": y,
|
||||
"button": "left",
|
||||
"buttons": 1,
|
||||
"clickCount": 1,
|
||||
},
|
||||
}))
|
||||
|
||||
# Drenar respuestas hasta ver el id del release (o agotar el stream).
|
||||
# Ignoramos eventos del server y frames no-JSON.
|
||||
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") == release_id:
|
||||
break
|
||||
except Exception as e: # noqa: BLE001 — fallo de transport durante send/recv
|
||||
return {"ok": False, "error": str(e), "x": x, "y": y}
|
||||
finally:
|
||||
try:
|
||||
ws.close()
|
||||
except Exception: # noqa: BLE001 — cierre best-effort
|
||||
pass
|
||||
|
||||
return {"ok": True, "error": "", "x": x, "y": y}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
arg_x = int(sys.argv[1]) if len(sys.argv) > 1 else 0
|
||||
arg_y = int(sys.argv[2]) if len(sys.argv) > 2 else 0
|
||||
substr = sys.argv[3] if len(sys.argv) > 3 else ""
|
||||
out = cdp_click_xy(arg_x, arg_y, port=9222, target_url_substr=substr)
|
||||
print(json.dumps(out, ensure_ascii=False, indent=2))
|
||||
Reference in New Issue
Block a user