diff --git a/python/functions/browser/whatsapp_send_image.md b/python/functions/browser/whatsapp_send_image.md
index 25be405a..84d046f5 100644
--- a/python/functions/browser/whatsapp_send_image.md
+++ b/python/functions/browser/whatsapp_send_image.md
@@ -6,9 +6,9 @@ domain: browser
version: "1.0.0"
purity: impure
signature: "def whatsapp_send_image(name: str, image_path: str, *, caption: str = '', port: int = 9222, target_url_substr: str = 'whatsapp', open_first: bool = True) -> dict"
-description: "Envia una imagen (con caption opcional) a un chat de WhatsApp Web en una pestana ya logueada del navegador diario via CDP, sin abrir ventana nueva ni darle foco. Abre el chat por nombre exacto (whatsapp_open_chat) y verifica el destinatario (salvaguarda anti-envio-equivocado), hace click real en 'Adjuntar' para exponer el vivo, asigna la imagen con cdp_set_file_input (DOM.setFileInputFiles), espera el preview de la bandeja inline, teclea el caption opcional con teclado CDP real, y hace click en el boton enviar (icono wds-ic-send-filled) verificando que la bandeja se cerro. Accion con efecto: envia la imagen DE VERDAD, no reversible."
+description: "Envia una imagen (con caption opcional) a un chat de WhatsApp Web en una pestana ya logueada del navegador diario via CDP, sin abrir ventana nueva ni darle foco. Abre el chat por nombre exacto (whatsapp_open_chat) y verifica el destinatario (salvaguarda anti-envio-equivocado), hace click real en 'Adjuntar' para exponer el vivo, asigna la imagen con cdp_set_file_input (DOM.setFileInputFiles), espera la bandeja inline y hace click en el boton enviar (icono wds-ic-send-filled) verificando que la bandeja se cerro. Si hay caption, lo envia como mensaje de texto de seguimiento via whatsapp_send_message (en la WhatsApp Web compacta actual el caption embebido en la imagen no es automatizable de forma fiable, asi que viaja como segunda burbuja [imagen][caption]). Accion con efecto: envia la imagen DE VERDAD, no reversible."
tags: [whatsapp, cdp, browser, automation, image, upload, python, navegator]
-uses_functions: [whatsapp_open_chat_py_browser, cdp_eval_py_browser, cdp_click_xy_py_browser, cdp_type_chars_py_browser, cdp_set_file_input_py_browser]
+uses_functions: [whatsapp_open_chat_py_browser, cdp_eval_py_browser, cdp_click_xy_py_browser, cdp_set_file_input_py_browser, whatsapp_send_message_py_browser]
uses_types: []
returns: []
returns_optional: false
@@ -21,16 +21,16 @@ params_schema:
- name: image_path
desc: "Ruta de la imagen a enviar. Se expande (~) y se convierte a ruta ABSOLUTA; debe existir en disco o aborta con error sin abrir el chat."
- name: caption
- desc: "Texto opcional que acompana la imagen. Se teclea en el composer con teclado CDP real caracter a caracter; '' (default) envia la imagen sin caption."
+ desc: "Texto opcional descriptivo. Se envia como un MENSAJE DE TEXTO de seguimiento (segunda burbuja [imagen][caption]) via whatsapp_send_message; '' (default) envia solo la imagen. La WhatsApp Web compacta actual no permite automatizar el caption embebido en la imagen de forma fiable."
- name: port
desc: "Puerto de remote debugging de Chrome. Default 9222."
- name: target_url_substr
desc: "Substring que debe contener la URL del target (pestana). Default 'whatsapp'."
- name: open_first
desc: "Si True (default), abre el chat por su nombre antes de adjuntar. Si False, asume el chat ya abierto pero verifica el aria-label del composer contra name (aborta si no coincide)."
- output: "dict {ok: bool (alias de sent), sent: bool, recipient: str, image: str (ruta absoluta), caption: str, error: str (motivo del fallo, vacio si sent=True)}. sent=True solo si la imagen se adjunto, el caption (si lo hay) se verifico y se pulso enviar dejando la bandeja vacia. Nunca lanza: los fallos se reportan en 'sent'/'ok' + 'error'."
+ output: "dict {ok: bool (imagen + caption enviados), sent: bool (imagen enviada), caption_sent: bool (caption de seguimiento enviado, False si no habia o fallo), recipient: str, image: str (ruta absoluta), caption: str, error: str (motivo del fallo, vacio si todo ok)}. sent=True solo si la imagen se adjunto y se envio dejando la bandeja vacia. Nunca lanza: los fallos se reportan en 'sent'/'ok' + 'error'."
tested: true
-tests: ["test_golden_adjunta_caption_y_envia", "test_envia_sin_caption_no_teclea", "test_edge_imagen_no_existe_error_sin_abrir", "test_edge_open_fallido_error_sin_adjuntar", "test_seguridad_open_first_false_label_no_coincide_aborta", "test_error_set_file_input_falla_no_envia"]
+tests: ["test_golden_envia_imagen_y_caption_de_seguimiento", "test_envia_sin_caption_no_manda_texto", "test_edge_imagen_no_existe_error_sin_abrir", "test_edge_open_fallido_error_sin_adjuntar", "test_seguridad_open_first_false_label_no_coincide_aborta", "test_error_set_file_input_falla_no_envia"]
test_file_path: "python/functions/browser/whatsapp_send_image_test.py"
file_path: "python/functions/browser/whatsapp_send_image.py"
---
@@ -76,8 +76,15 @@ usa `whatsapp_send_message`; para leer/confirmar lo enviado, `whatsapp_read_chat
vacia aunque la sesion siga logueada.
- **El input vivo solo existe tras pulsar "Adjuntar".** Por eso la funcion hace el click real en
el boton "Adjuntar" antes de `setFileInputFiles`; asignar al input persistente decoy no abre el
- preview. La WhatsApp Web actual usa una **bandeja de medios INLINE** sobre el composer (no un
- drawer a pantalla completa): el caption se escribe en el MISMO composer del footer.
+ preview. La WhatsApp Web actual usa una **bandeja de medios INLINE compacta** sobre el composer
+ (no un drawer a pantalla completa).
+- **El caption NO se embebe en la imagen: viaja como mensaje de texto de seguimiento.** En esta
+ WhatsApp Web compacta hay dos botones de envio cuando hay media: "Enviar N seleccionados" (envia
+ la bandeja, IGNORA el texto del composer) y "Enviar"/Enter (envia el texto como burbuja aparte,
+ descartando la media en cola). No hay un campo de caption por-imagen automatizable de forma
+ fiable. Por eso la funcion envia primero la imagen (boton de la bandeja) y, si hay `caption`, lo
+ manda despues como mensaje de texto via `whatsapp_send_message` (`open_first=False`): el resultado
+ es [imagen][caption] como dos burbujas. `caption_sent` indica si esa segunda burbuja salio.
- **Selector de aria-label en espanol.** El preview se detecta por `[aria-label="Quitar archivo
adjunto"]` y el boton de adjuntar por `[aria-label="Adjuntar"]`: dependen del idioma de la UI
(espanol). En otro locale habria que ajustar los aria-labels.
@@ -87,8 +94,6 @@ usa `whatsapp_send_message`; para leer/confirmar lo enviado, `whatsapp_read_chat
`sent=False` con "envio incierto".
- **Salvaguarda anti-destinatario-equivocado**: con `open_first=True` abre y verifica el chat; con
`open_first=False` lee el aria-label del composer y aborta si no contiene `name`.
-- **Caption verificado**: tras teclear, re-lee el `innerText` del composer y solo envia si coincide
- EXACTAMENTE con `caption`; si no, devuelve `sent=False` sin enviar.
- **Funciona con la ventana minimizada o sin foco**: CDP opera la pestana sin traerla a primer plano.
- **Viola los ToS de WhatsApp**: automatizar la web tiene riesgo de ban del numero personal. Usar
con cautela y bajo tu responsabilidad.
diff --git a/python/functions/browser/whatsapp_send_image.py b/python/functions/browser/whatsapp_send_image.py
index 81f60a3e..279b26ca 100644
--- a/python/functions/browser/whatsapp_send_image.py
+++ b/python/functions/browser/whatsapp_send_image.py
@@ -1,8 +1,9 @@
"""Envia una imagen (con caption opcional) a un chat de WhatsApp Web via Chrome DevTools Protocol.
Compone `whatsapp_open_chat` (abrir + verificar destinatario) con primitivas CDP del
-registry (`cdp_eval`, `cdp_click_xy`, `cdp_set_file_input`, `cdp_type_chars`) para adjuntar
-y enviar una imagen a un contacto/grupo SIN abrir ventana nueva ni darle foco al sistema.
+registry (`cdp_eval`, `cdp_click_xy`, `cdp_set_file_input`) y `whatsapp_send_message` para
+adjuntar y enviar una imagen a un contacto/grupo SIN abrir ventana nueva ni darle foco al
+sistema.
Flujo (modelo de bandeja de medios INLINE de la WhatsApp Web actual), con salvaguarda
anti-envio-al-contacto-equivocado:
@@ -14,15 +15,17 @@ anti-envio-al-contacto-equivocado:
`` "vivo" que escucha la SPA (antes de pulsarlo el input persistente
no dispara el preview).
3. Asigna la imagen al input via `cdp_set_file_input` (`DOM.setFileInputFiles`): la
- imagen aparece como miniatura en la bandeja inline sobre el composer.
- 4. Espera a que el preview aparezca (boton "Quitar archivo adjunto" presente).
- 5. Si `caption` no esta vacio, enfoca el composer del footer y teclea el texto con
- teclado CDP real (`cdp_type_chars`); verifica el `innerText` antes de enviar.
- 6. Click de raton real en el boton enviar (icono `wds-ic-send-filled`) y verifica que la
- bandeja se cerro (sin adjuntos) para confirmar el envio.
+ imagen aparece como miniatura en la bandeja inline.
+ 4. Espera a que la bandeja aparezca (boton "Quitar archivo adjunto" presente) y hace click
+ real en el boton de enviar la bandeja (icono `wds-ic-send-filled`); verifica que la
+ bandeja se cerro (sin adjuntos) para confirmar el envio de la imagen.
+ 5. Si `caption` no esta vacio, lo envia como un MENSAJE DE TEXTO de seguimiento via
+ `whatsapp_send_message` (con `open_first=False`, el chat ya esta abierto). En la
+ WhatsApp Web compacta actual el caption embebido en la imagen no es automatizable de
+ forma fiable, asi que la descripcion viaja como una segunda burbuja: [imagen][caption].
Validado contra WhatsApp Web real. Accion CON EFECTO REAL E IRREVERSIBLE: envia la imagen
-de verdad.
+(y el caption) de verdad.
"""
import json
@@ -34,9 +37,9 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from browser.cdp_eval import cdp_eval
from browser.cdp_click_xy import cdp_click_xy
-from browser.cdp_type_chars import cdp_type_chars
from browser.cdp_set_file_input import cdp_set_file_input
from browser.whatsapp_open_chat import whatsapp_open_chat
+from browser.whatsapp_send_message import whatsapp_send_message
def _center(expr: str, port: int, substr: str):
@@ -70,7 +73,7 @@ def whatsapp_send_image(
target_url_substr: str = "whatsapp",
open_first: bool = True,
) -> dict:
- """Envia una imagen (con caption opcional) a un chat de WhatsApp Web ya logueado.
+ """Envia una imagen (con caption opcional de seguimiento) a un chat de WhatsApp Web.
Accion CON EFECTO: envia la imagen DE VERDAD (no reversible). Verifica `name`.
@@ -80,8 +83,9 @@ def whatsapp_send_image(
al destinatario correcto antes de adjuntar.
image_path: Ruta de la imagen a enviar. Se expande (`~`) y se convierte a ruta
ABSOLUTA; debe existir en disco.
- caption: Texto opcional que acompana la imagen. Se teclea en el composer con teclado
- CDP real; "" (default) envia la imagen sin caption.
+ caption: Texto opcional descriptivo. Se envia como un MENSAJE DE TEXTO de seguimiento
+ (segunda burbuja [imagen][caption]) via `whatsapp_send_message`; "" (default)
+ envia solo la imagen.
port: Puerto de remote debugging de Chrome. Default 9222.
target_url_substr: Substring que debe contener la URL del target (pestana). Default
"whatsapp".
@@ -91,12 +95,14 @@ def whatsapp_send_image(
Returns:
dict con claves:
- ok: bool — alias de sent (True si la imagen se envio).
- sent: bool — True si la imagen se adjunto, (caption escrito) y se envio.
+ ok: bool — True si la imagen se envio y (si habia caption) el caption tambien.
+ sent: bool — True si la IMAGEN se envio.
+ caption_sent: bool — True si el caption de seguimiento se envio (False si no
+ habia caption o si fallo).
recipient: str — el nombre solicitado.
image: str — ruta absoluta de la imagen.
caption: str — caption solicitado.
- error: str — motivo del fallo (vacio si sent=True).
+ error: str — motivo del fallo (vacio si todo ok).
Nunca lanza: los fallos se reportan en "sent"/"ok" + "error".
"""
@@ -104,7 +110,7 @@ def whatsapp_send_image(
abs_img = os.path.abspath(os.path.expanduser(image_path))
def fail(error: str) -> dict:
- return {"ok": False, "sent": False, "recipient": name,
+ return {"ok": False, "sent": False, "caption_sent": False, "recipient": name,
"image": abs_img, "caption": caption, "error": error}
# 0. La imagen debe existir.
@@ -147,7 +153,8 @@ def whatsapp_send_image(
if not r.get("ok"):
return fail("no se pudo adjuntar la imagen: " + r.get("error", ""))
- # 4. Esperar a que el preview/bandeja aparezca (adjunto presente).
+ # 4. Esperar a que la bandeja aparezca (adjunto presente). El composer queda VACIO,
+ # asi que el unico boton wds-ic-send-filled es el de enviar la bandeja.
attached = False
for _ in range(15):
time.sleep(0.2)
@@ -157,27 +164,7 @@ def whatsapp_send_image(
if not attached:
return fail("el preview no aparecio tras adjuntar la imagen")
- # 5. Caption opcional: enfocar el composer y teclear con teclado real.
- if caption:
- cdp_eval(
- '/*FOCUS*/var b=document.querySelector(\'div[contenteditable="true"]\'); '
- "if(b){b.focus();}",
- port=port, target_url_substr=S,
- )
- time.sleep(0.25)
- cdp_type_chars(caption, port=port, target_url_substr=S, delay_ms=15)
- time.sleep(0.3)
- chk = cdp_eval(
- '/*COMPOSER*/var b=document.querySelector(\'div[contenteditable="true"]\'); '
- "b?b.innerText.replace(/\\n/g,''):''",
- port=port, target_url_substr=S,
- )
- composer = chk.get("value") or ""
- if composer != caption:
- return fail("el composer no contiene el caption esperado (no enviado): "
- + repr(composer))
-
- # 6. Click real en el boton enviar (icono wds-ic-send-filled).
+ # 5. Click real en el boton de enviar la bandeja (icono wds-ic-send-filled).
snd = _center(
'/*SEND*/(() => {const e=document.querySelector(\'span[data-icon="wds-ic-send-filled"]\');'
"if(!e)return null;const b=e.getBoundingClientRect();"
@@ -189,14 +176,29 @@ def whatsapp_send_image(
return fail("boton de enviar (wds-ic-send-filled) no encontrado")
cdp_click_xy(snd["x"], snd["y"], port=port, target_url_substr=S)
- # 7. Verificar que la bandeja se cerro (sin adjuntos) -> envio confirmado.
+ # 6. Verificar que la bandeja se cerro (sin adjuntos) -> imagen enviada.
+ image_sent = False
for _ in range(15):
time.sleep(0.2)
if _attachment_count(port, S) == 0:
- return {"ok": True, "sent": True, "recipient": name,
- "image": abs_img, "caption": caption, "error": ""}
+ image_sent = True
+ break
+ if not image_sent:
+ return fail("la bandeja no se cerro tras pulsar enviar; envio incierto")
- return fail("la bandeja no se cerro tras pulsar enviar; envio incierto")
+ # 7. Caption opcional como mensaje de texto de seguimiento (segunda burbuja).
+ caption_sent = False
+ if caption:
+ m = whatsapp_send_message(name, caption, port=port, target_url_substr=S,
+ open_first=False)
+ caption_sent = bool(m.get("sent"))
+ if not caption_sent:
+ return {"ok": False, "sent": True, "caption_sent": False, "recipient": name,
+ "image": abs_img, "caption": caption,
+ "error": "imagen enviada pero el caption fallo: " + m.get("reason", "")}
+
+ return {"ok": True, "sent": True, "caption_sent": caption_sent, "recipient": name,
+ "image": abs_img, "caption": caption, "error": ""}
if __name__ == "__main__":
diff --git a/python/functions/browser/whatsapp_send_image_test.py b/python/functions/browser/whatsapp_send_image_test.py
index 4e9f6408..4af99b61 100644
--- a/python/functions/browser/whatsapp_send_image_test.py
+++ b/python/functions/browser/whatsapp_send_image_test.py
@@ -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