Si tus pruebas de correo electrónico funcionan perfectamente en tu portátil y colapsan en cuanto llegan a la CI, no estás solo. La respuesta habitual es esparcir llamadas sleep por el código de prueba o aumentar el número de reintentos hasta que la compilación pase. Eso puede calmar el ruido durante un día, pero no soluciona el error. Solo lo oculta.
El verdadero problema es cómo tu prueba identifica qué correo electrónico debe abrir.
El problema de la bandeja de entrada compartida
En tu máquina local, ejecutas una prueba a la vez. Llega un correo. Lo tomas. Simple.
La CI es un entorno completamente distinto. Un solo pull request podría activar cuatro, ocho o dieciséis trabajos en paralelo. Si todos comparten una bandeja de entrada de prueba —ya sea un servidor de Mailosaur, una bandeja de Mailtrap o una cuenta real en un dominio de staging—, todos están escribiendo en el mismo contenedor al mismo tiempo. El trabajo A envía un restablecimiento de contraseña. El trabajo B envía una invitación. El trabajo C reintenta un flujo de bienvenida fallido. Mientras tanto, los procesos en segundo plano y las colas de entrega añaden un jitter que no puedes controlar.
Cuando cada trabajo accede a esa bandeja de entrada compartida y solicita el mensaje más reciente con el asunto "Reset your password", se convierte en una carrera. La prueba que gana obtiene el correo correcto. La prueba que pierde hace clic en un enlace destinado a otro trabajo, realiza la aserción sobre el contenido incorrecto y falla con un error que parece un problema de sincronización. No es un problema de sincronización. Es un problema de identidad.
Por qué falla el método de "mensaje más reciente"
Es fácil caer en este patrón frágil porque parece intuitivo:
- Activar el flujo de usuario.
- Consultar la bandeja de entrada cada pocos segundos.
- Abrir el mensaje más reciente que coincida con la línea de asunto.
- Hacer clic en el primer enlace y ejecutar las aserciones.
Esto se desmorona por varias razones más allá del simple paralelismo. Un reintento de una ejecución fallida anterior puede llegar tarde, convirtiéndose de repente en el mensaje más reciente justo cuando tu prueba actual realiza la consulta. Los procesos en segundo plano dentro de tu aplicación podrían encolar dos correos y entregar el segundo antes que el primero. Las líneas de asunto por sí solas son identificadores débiles; tu aplicación de staging podría enviar correos similares desde diferentes rutas. Ordenar por marca de tiempo es peor de lo que parece, porque el desajuste de reloj (clock skew) entre el ejecutor de la CI y el proveedor de correo es real, y las API de correo suelen cachear o procesar sus índices por lotes.
Las marcas de tiempo se vuelven imprecisas en entornos con mucha actividad. Necesitas algo directo.
Qué es realmente un run token
Un run token no es más que una cadena única generada al inicio de tu prueba e inyectada en el correo electrónico que envía tu aplicación. No necesita ser visible para el usuario ni tiene por qué ser elegante. Solo necesita garantizar que puedes demostrar que este mensaje específico pertenece a esta ejecución de prueba específica.
Los ejemplos concretos funcionan mejor. Antes de que comience la prueba, genera un token como:
- Un UUID:
550e8400-e29b-41d4-a716-446655440001 - Un ID de solicitud con alcance de compilación:
req_ci_build_4821_a7f3 - Un slug de invitación o sufijo de metadatos:
signup-token-8k2m9n - Una cadena hexadecimal aleatoria generada por el ejecutor de pruebas:
test-run-a4f9c2d1
Si controlas el código del backend, pasa el token al contexto del correo y renderízalo en alguna parte del cuerpo. Si estás realizando pruebas contra una aplicación de caja negra (black-box), comprueba si la aplicación ya acepta un campo de referencia que puedas interceptar. Si no es así, a veces puedes incrustar el token en la parte local del destinatario usando el direccionamiento con signo más (plus addressing)—testuser+a4f9c2d1@example.com—aunque esto solo funciona si tu aplicación lo preserva y lo devuelve en el correo.
El objetivo es dejar de buscar coincidencias en metadatos que el sistema de correo ya posee. Busca coincidencias en datos que tu prueba posea.
El patrón fiable
Reemplaza el algoritmo de "mensaje más reciente" con una búsqueda estrecha impulsada por tokens:
- Genera el run token antes de activar cualquier flujo.
- Inicia la acción del usuario, asegurándote de que la aplicación incluirá el token en el correo saliente.
- Consulta al proveedor de correo con filtros limitados a ese token. Si la API admite la búsqueda en el cuerpo, úsala. Si no, obtén los mensajes candidatos y realiza un grep de sus cuerpos en el lado del cliente.
- Realiza una aserción de que el token existe en el cuerpo del mensaje antes de tocar cualquier enlace, botón o código de verificación.
- Solo entonces extrae la URL o el código de confirmación y continúa.
Esta secuencia es importante. Si extraes un enlace primero y compruebas el token después, ya habrás hecho clic en el correo incorrecto. La aserción es tu guardián.
En la práctica, tu helper debería buscar Subject:"Welcome to AppName" AND Body:"a4f9c2d1" en lugar de Subject:"Welcome to AppName" sort:-received. Muchos servicios de prueba de correo exponen APIs de búsqueda que aceptan filtros de contenido en el cuerpo. Úsalos. Si estás trabajando con un proveedor más sencillo, mantén tu lógica de polling en un solo lugar para poder añadir filtrado en el lado del cliente de forma consistente en cada prueba.
Tres reglas para mantener la integridad del sistema
Un token de ejecución (run token) fija la selección, pero aun así necesitas disciplina en cuanto a cómo realizas el polling y qué haces cuando algo sale mal.
Registra el estado de la bandeja de entrada en caso de fallo. Cuando una prueba falle, muestra el identificador de la bandeja de entrada, la línea de asunto que consultaste, la ventana de tiempo exacta y cuántos mensajes coincidieron con tus criterios. Esto convierte un vago error de "email no encontrado" en una historia concreta. Si el job 7823 recogió un mensaje de reintento del job 7821 porque llegó tres segundos más tarde, tus logs deberían hacerlo evidente. Sin este contexto, culparás al factor tiempo y añadirás otro sleep.
Mantén todo el polling de correos en un único archivo helper. No disperses llamadas a setTimeout y cy.task por veinte archivos de prueba. Centraliza la lógica que espera mensajes, reintenta la llamada a la API y aplica el backoff. Si cada prueba utiliza el mismo helper, tus reglas de filtrado se mantendrán consistentes y, cuando mejores la lógica de búsqueda, todas las pruebas se beneficiarán. También facilita la aplicación de la comprobación del token; si el helper requiere un argumento de token, nadie podrá recurrir accidentalmente a la muleta del "último mensaje".
Vigila tus reintentos. Los reintentos de las pruebas son comunes en CI, pero cada reintento crea otro correo en la bandeja de entrada. Si tu prueba pasa en el tercer intento, es posible que celebres y sigas adelante. Lo que no ves es que los intentos uno y dos expusieron un error real —una condición de carrera, un envío duplicado o un índice faltante— que los mensajes adicionales enmascararon. Si debes usar reintentos, comprueba si la bandeja de entrada contiene duplicados inesperados tras un fallo. Mejor aún, considera limpiar la bandeja de entrada o usar una dirección única por job si tu proveedor admite bandejas de entrada dinámicas. Los reintentos no deben convertirse en una estrategia para absorber una lógica de selección poco fiable.
La conclusión real
Ordenar una bandeja de entrada por fecha y tomar el primer resultado no es testing. Es adivinar disfrazado de código. Un token de ejecución cuesta casi nada —una variable de tipo string, un parámetro de filtro adicional, tal vez un pequeño cambio en la plantilla— y le otorga a tu prueba una identidad determinista. Demuestra que el mensaje que tienes delante pertenece a la ejecución que estás realizando en este momento.
Deja de añadir sleeps y de esperar que la red se comporte. Genera un token, ponlo en el correo y búscalo directamente. Tus ejecuciones de CI serán más rápidas, tus logs serán legibles y, finalmente, confiarás en lo que la suite de correos te está diciendo.
