feat(browser): auto-commit con 3 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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 <input type=file> 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 <input type=file> 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.
|
||||
|
||||
@@ -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:
|
||||
`<input type=file>` "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__":
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user