feat(browser): auto-commit con 3 cambios

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-26 23:15:30 +02:00
parent 0dd2718c95
commit d0960bed70
3 changed files with 93 additions and 84 deletions
@@ -1,14 +1,14 @@
"""Tests para whatsapp_send_image.
whatsapp_send_image compone whatsapp_open_chat con cuatro primitivas CDP (cdp_eval,
cdp_click_xy, cdp_type_chars, cdp_set_file_input) y requiere un Chrome vivo. Aqui se
mockean las cinco con monkeypatch sobre el modulo `browser.whatsapp_send_image` (donde
quedan ligados los nombres por el `from browser.X import Y`), de modo que NO hace falta
Chrome.
whatsapp_send_image compone whatsapp_open_chat con tres primitivas CDP (cdp_eval,
cdp_click_xy, cdp_set_file_input) y `whatsapp_send_message` (para el caption de
seguimiento), y requiere un Chrome vivo. Aqui se mockean todas con monkeypatch sobre el
modulo `browser.whatsapp_send_image` (donde quedan ligados los nombres por el
`from browser.X import Y`), de modo que NO hace falta Chrome.
El fake de cdp_eval distingue cada expresion por un marcador de comentario JS embebido en
ella (`/*ADJUNTAR*/`, `/*PREVIEW*/`, `/*COMPOSER*/`, `/*SEND*/`, `/*LABEL*/`). El estado
`after_send` (que /*SEND*/ activa) hace que /*PREVIEW*/ devuelva 0 adjuntos tras el envio,
ella (`/*ADJUNTAR*/`, `/*PREVIEW*/`, `/*SEND*/`, `/*LABEL*/`). El estado `after_send` (que
/*SEND*/ activa) hace que /*PREVIEW*/ devuelva 0 adjuntos tras el envio de la imagen,
simulando el cierre de la bandeja.
"""
@@ -35,8 +35,7 @@ class _Spy:
return self.ret
def _make_eval(caption_value="", label="Escribir un mensaje para el grupo NOTAS WASAP",
state=None):
def _make_eval(label="Escribir un mensaje para el grupo NOTAS WASAP", state=None):
state = state if state is not None else {}
def fake(expr, *, port=9222, target_url_substr=""):
@@ -47,8 +46,6 @@ def _make_eval(caption_value="", label="Escribir un mensaje para el grupo NOTAS
return {"ok": True, "value": json.dumps({"x": 1136, "y": 578}), "error": ""}
if "/*PREVIEW*/" in expr:
return {"ok": True, "value": 0 if state.get("after_send") else 1, "error": ""}
if "/*COMPOSER*/" in expr:
return {"ok": True, "value": caption_value, "error": ""}
if "/*LABEL*/" in expr:
return {"ok": True, "value": label, "error": ""}
return {"ok": True, "value": None, "error": ""}
@@ -56,30 +53,32 @@ def _make_eval(caption_value="", label="Escribir un mensaje para el grupo NOTAS
return fake
def _patch_common(monkeypatch, *, eval_fn, set_ret={"ok": True}, open_ret=None):
def _patch_common(monkeypatch, *, eval_fn, set_ret={"ok": True}, open_ret=None,
send_msg_ret={"sent": True}):
open_spy = _Spy(ret=open_ret if open_ret is not None else {"opened": True, "name": "x"})
click_spy = _Spy(ret={"ok": True})
type_spy = _Spy(ret={"ok": True})
set_spy = _Spy(ret=set_ret)
sendmsg_spy = _Spy(ret=send_msg_ret)
monkeypatch.setattr(wsi, "whatsapp_open_chat", open_spy)
monkeypatch.setattr(wsi, "cdp_eval", eval_fn)
monkeypatch.setattr(wsi, "cdp_click_xy", click_spy)
monkeypatch.setattr(wsi, "cdp_type_chars", type_spy)
monkeypatch.setattr(wsi, "cdp_set_file_input", set_spy)
monkeypatch.setattr(wsi, "whatsapp_send_message", sendmsg_spy)
monkeypatch.setattr(wsi.time, "sleep", lambda *a, **k: None)
return open_spy, click_spy, type_spy, set_spy
return open_spy, click_spy, set_spy, sendmsg_spy
def test_golden_adjunta_caption_y_envia(monkeypatch):
def test_golden_envia_imagen_y_caption_de_seguimiento(monkeypatch):
cap = "item icon: potion"
state = {}
open_spy, click_spy, type_spy, set_spy = _patch_common(
monkeypatch, eval_fn=_make_eval(caption_value=cap, state=state))
open_spy, click_spy, set_spy, sendmsg_spy = _patch_common(
monkeypatch, eval_fn=_make_eval(state=state))
res = whatsapp_send_image("NOTAS WASAP", _IMG, caption=cap,
port=9222, target_url_substr="whatsapp")
assert res["sent"] is True and res["ok"] is True
assert res["caption_sent"] is True
assert res["recipient"] == "NOTAS WASAP"
assert res["image"] == _IMG
assert res["caption"] == cap
@@ -87,30 +86,32 @@ def test_golden_adjunta_caption_y_envia(monkeypatch):
# Se adjunto la imagen (ruta absoluta) por setFileInputFiles.
assert set_spy.calls[0][0][0] == 'input[type="file"][multiple]'
assert set_spy.calls[0][0][1] == _IMG
# Se tecleo el caption una vez.
assert len(type_spy.calls) == 1
assert type_spy.calls[0][0][0] == cap
# Dos clicks reales: Adjuntar y Enviar.
# Dos clicks reales: Adjuntar y Enviar (bandeja).
assert len(click_spy.calls) == 2
# El caption viaja como mensaje de texto de seguimiento, open_first=False.
assert len(sendmsg_spy.calls) == 1
assert sendmsg_spy.calls[0][0][0] == "NOTAS WASAP"
assert sendmsg_spy.calls[0][0][1] == cap
assert sendmsg_spy.calls[0][1].get("open_first") is False
def test_envia_sin_caption_no_teclea(monkeypatch):
def test_envia_sin_caption_no_manda_texto(monkeypatch):
state = {}
_, click_spy, type_spy, set_spy = _patch_common(
monkeypatch, eval_fn=_make_eval(caption_value="", state=state))
_, click_spy, set_spy, sendmsg_spy = _patch_common(
monkeypatch, eval_fn=_make_eval(state=state))
res = whatsapp_send_image("NOTAS WASAP", _IMG, caption="",
port=9222, target_url_substr="whatsapp")
assert res["sent"] is True
# Sin caption: NO se teclea nada, pero si se adjunta y se envia.
assert len(type_spy.calls) == 0
assert len(set_spy.calls) >= 1
assert res["caption_sent"] is False
# Sin caption: NO se manda mensaje de texto de seguimiento.
assert len(sendmsg_spy.calls) == 0
assert len(click_spy.calls) == 2
def test_edge_imagen_no_existe_error_sin_abrir(monkeypatch):
open_spy, click_spy, type_spy, set_spy = _patch_common(
open_spy, click_spy, set_spy, sendmsg_spy = _patch_common(
monkeypatch, eval_fn=_make_eval())
res = whatsapp_send_image("NOTAS WASAP", "/no/existe/foo.png",
@@ -124,7 +125,7 @@ def test_edge_imagen_no_existe_error_sin_abrir(monkeypatch):
def test_edge_open_fallido_error_sin_adjuntar(monkeypatch):
open_spy, click_spy, type_spy, set_spy = _patch_common(
open_spy, click_spy, set_spy, sendmsg_spy = _patch_common(
monkeypatch, eval_fn=_make_eval(),
open_ret={"opened": False, "name": "x",
"reason": "chat no encontrado en la lista (no cargado o nombre inexacto)"})
@@ -140,7 +141,7 @@ def test_edge_open_fallido_error_sin_adjuntar(monkeypatch):
def test_seguridad_open_first_false_label_no_coincide_aborta(monkeypatch):
open_spy, click_spy, type_spy, set_spy = _patch_common(
open_spy, click_spy, set_spy, sendmsg_spy = _patch_common(
monkeypatch,
eval_fn=_make_eval(label="Escribir un mensaje para el grupo OTRO CHAT"))
@@ -157,7 +158,7 @@ def test_seguridad_open_first_false_label_no_coincide_aborta(monkeypatch):
def test_error_set_file_input_falla_no_envia(monkeypatch):
state = {}
open_spy, click_spy, type_spy, set_spy = _patch_common(
open_spy, click_spy, set_spy, sendmsg_spy = _patch_common(
monkeypatch, eval_fn=_make_eval(state=state),
set_ret={"ok": False, "error": "no element matches selector"})
@@ -169,3 +170,4 @@ def test_error_set_file_input_falla_no_envia(monkeypatch):
# Se intento adjuntar (dos selectores) pero no se llego a enviar (solo el click de Adjuntar).
assert len(set_spy.calls) == 2
assert len(click_spy.calls) == 1
assert len(sendmsg_spy.calls) == 0