Ir al contenido

proxymail-qa envuelve la REST API para JavaScript y TypeScript: Playwright, tareas de Cypress, Vitest, Jest, Detox o un script simple. No tiene dependencias, incluye sus propios tipos y funciona en Node 18 o posterior (o donde haya fetch). El código fuente está en github.com/aaquib90/proxymail-qa-js, y cada versión se publica desde GitHub Actions con procedencia de npm.

  1. npm install --save-dev proxymail-qa
  2. Crea una clave de API y configúrala como PROXYMAIL_QA_API_KEY, o pásala directamente.

  3. Crea una dirección, regístrate con ella en tu app y luego espera el código o enlace.

    Las esperas devuelven solo el código o enlace, con el servicio que lo envió y cuándo vence, nunca el texto del correo. Una espera dura hasta 30 segundos y los timeout más largos la repiten.

    import { ProxyMailQA } from 'proxymail-qa';
    const qa = new ProxyMailQA(); // reads PROXYMAIL_QA_API_KEY
    const { address, persona, password } = await qa.createAddress({ persona: true, password: true });
    const submitted = new Date();
    // …sign up in your app with address, persona and password…
    const { value: code } = await qa.waitForCode(address, { since: submitted });
    // or: const { value: link } = await qa.waitForLink(address, { type: 'verify', since: submitted });
    await qa.deleteAddress(address);

Ejemplos completos: Playwright y Cypress.

Método Qué hace
new ProxyMailQA({ apiKey?, baseUrl?, fetch?, run? }) El cliente. apiKey tiene como valor predeterminado PROXYMAIL_QA_API_KEY, y run, PROXYMAIL_QA_RUN
createAddress(options?) Una nueva dirección de prueba. Opciones: label, name (un nombre personalizado antes de la @; se rechazan nombres genéricos como test por sí solos), persona, password (se devuelve una vez y se tipa como string cuando pasas password: true), lifetimeDays, domain, age o dateOfBirth, country, y dónde se usa: site y environment (nuevo en 0.9.0)
waitForCode(address, options?) El código de verificación o de inicio de sesión más reciente. Opciones: since, service, timeout (segundos, 30 por defecto), unused (omite códigos ya usados: devueltos por una espera anterior, o copiados o llenados en la app o en Proxy Mail QA for Chrome; nuevo en 0.5.1), y type: 'coupon' para un cupón o código promocional, con su discount, valid_until y after_seconds (nuevo en 0.10.0)
waitForLink(address, options?) Lo mismo para un enlace. También type (verify, magic_signin, invite, reset o any, o una lista) y app (true para el enlace que abre una app móvil)
messages(address, options?) Correos con sus códigos y enlaces. includeText y includeHtml agregan el contenido, para pruebas de plantillas; limit
countEmails(address, options?) Cuántos correos llegaron, cada uno resumido sin su contenido. Opciones: since, service, type (code o link), subtype y, desde 0.10.0, category (auth, transactional o marketing)
expectNoEmail(address, options?) Se resuelve si no llega ningún correo coincidente en timeout segundos (30 por defecto, y también sirven minutos: vuelve a preguntar); lanza ProxyMailQAError (409, email_arrived) en cuanto llega uno. Mismos filtros, y since tiene como valor predeterminado ahora
markUnsubscribed(address, options?) Labs. Registra que la dirección se dio de baja de un remitente (emailId o service; at para una fecha anterior; grace: '1d' para correo ya en camino), después de que la prueba lo hiciera en su navegador. Cualquier correo de marketing posterior de ese remitente no pasa after_unsubscribe. Nuevo en 0.10.0
markResubscribed(address, options?) Labs. La dirección se volvió a suscribir: termina la vigilancia. Nuevo en 0.10.0
recordConsent(address, consent) Labs. Registra lo que la cuenta aceptó al registrarse (marketing, prechecked, doubleOptIn), para las reglas de marketing por país. consent(address) lo lee. Nuevo en 0.10.0
consent(address) Labs. Lo que la cuenta aceptó, según lo registrado. Nuevo en 0.10.0
recordCouponResult(address, result, options?) Labs. Tras aplicar un cupón en el pago (staging, pagos de prueba), registra accepted, rejected o error con el message de la app (code, oneTime); check es el veredicto de coupon_result. Nuevo en 0.10.0
checkSequence(address, steps, options?) Labs. Si una serie de bienvenida o campaña de goteo llegó en orden y a tiempo después de since; cada paso puede indicar subject, category, type, after (‘1d’) y within. Nuevo en 0.10.0
offers(address) Labs. Los cupones de cada remitente y a qué variante de la dirección (simple, mayúsculas, +tag) fue cada uno; passed es false cuando una persona recibió una oferta dos veces. Nuevo en 0.10.0
checkUnsubscribe(address) Labs. Cada baja y los correos de marketing que llegaron después; passed es false cuando llegó alguno. Nuevo en 0.10.0
forTest(name), recordTest(result), recordTestTotals(totals) Un cliente que nombra su prueba en cada llamada, y resultados de prueba para el informe de la ejecución; proxymail-qa/playwright y su reporter los usan. Nuevo en 0.8.0 (más)
formValues({ fields, address?, age?, country?, … }) Qué escribir o elegir en cada campo de un formulario de registro, a partir de una dirección nueva creada para él. Nuevo en 0.7.0 (más)
checkEmail(address, options?) Revisa el correo coincidente más reciente (o emailId): SPF, DKIM, DMARC, enlaces locales y http simples, adónde lleva su enlace (expectHost), un vencimiento indicado (y, desde 0.10.0, cuándo termina un cupón), una parte de texto plano, el tamaño y correos solo de imagen. Desde 0.7.0, frases que debe contener (expectText) y su idioma (expectLanguage); desde 0.10.0, cuánto tardó en llegar después de since (expectWithin: '5m') y sus adjuntos (expectAttachment: ['.pdf']). Un correo de marketing también recibe list_unsubscribe, one_click_unsubscribe, unsubscribe_link y preheader, y todos los correos harmful_attachment y spam_risk (una estimación de si llega a spam). Solo veredictos, nunca su contenido. Nuevo en 0.4.0
checkDelivery(address) Lo que llegó a la dirección y lo que se rechazó, sin esperar
variants(address) Variantes inusuales de la dirección que aún reciben su correo, y variantes no válidas (más)
nameVariants({ field?, maxLength? }) Nombres difíciles para un campo de nombre: los que se deben aceptar (apóstrofos, acentos, otros alfabetos), los que se deben rechazar, HTML o SQL que se deben guardar como texto, y los que varían según la app. Nuevo en 0.11.0 (más)
passwordVariants({ minLength?, maxLength?, symbols? }) Contraseñas difíciles dentro de las reglas del campo: el mínimo y el máximo, cada clase de símbolo, espacios, caracteres no ASCII y las que varían según la app; nuevas en cada llamada. Nuevo en 0.11.0 (más)
getAddress(address), listAddresses({ limit?, before?, tags?, country?, minAge?, maxAge?, hasPassword?, search?, site?, environment? }) Lee direcciones o busca las creadas antes: deben coincidir todos los filtros indicados
updateAddress(address, patch) Cambia label, notes, tags, first_name, last_name, username, date_of_birth o country; null borra uno
setPassword(address, { password?, label? }) Guarda la contraseña que usa ahora la cuenta de prueba, después de que la app la cambiara o restableciera. Si no indicas una, se genera una contraseña segura y se devuelve una vez
savedPassword(address, label?) Lee una contraseña guardada. Necesita una clave con “Puede leer contraseñas guardadas”; cada lectura queda registrada
signInAccount({ site?, environment?, tags? }) La cuenta de prueba guardada para iniciar sesión, y su contraseña, en una sola llamada: primero la que se usó en este sitio; null cuando ninguna sirve. Necesita una clave con “Puede leer contraseñas guardadas”. Nuevo en 0.9.0 (más)
markDevHost(host) Marca un host como localhost:3000 como entorno de desarrollo del proyecto, para que checkEmail acepte los enlaces a él. Nuevo en 0.9.0
deleteAddress(address) Eliminar la dirección y su correo
project() El proyecto de la clave: sus dominios de correo, prefijo de correo y límites
updateProject({ name?, website?, environments?, emailPrefix?, mailDomain? }) Cambia el proyecto, como en Configuración del proyecto; null borra website o emailPrefix. Requiere al responsable del proyecto, o a un propietario o administrador de la organización
checkOutAccount({ tags?, country?, minAge?, maxAge?, hasPassword?, search?, site?, environment?, minutes? }), extendCheckout(account, minutes?), returnAccount(account, { addTags?, removeTags? }), withAccount(options, fn) El grupo de cuentas: toma prestada una cuenta en un estado conocido que ninguna otra prueba tenga, y devuélvela. Nuevo en 0.6.0 (más). Desde 0.7.0, withAccount acepta los hooks create, check y reset: cuentas en un estado conocido
startRun({ name?, source?, meta? }), finishRun({ failed? }), runReport(), addRunNote(text, { failed? }), getRun(), listRuns() Una ejecución: las llamadas de este cliente añaden lo que vieron, para un informe en el pull request. Nuevo en 0.5.0

Todos los métodos aceptan una dirección simple o una variante de ella. Esperar en una dirección +tag solo encuentra el correo enviado a esa etiqueta, así que una dirección puede servir para toda una ejecución de pruebas: consulta Ejecución en CI.

Los métodos marcados con Labs forman parte de Correos de marketing y ofertas, una función de Labs que el creador de la clave activa en la app en Account settings → Labs; hasta entonces, lanzan un error 403 que lo indica. checkEmail omite las comprobaciones de las funciones de Labs que están desactivadas.

formValues da lo que hay que escribir o elegir en cada campo de un formulario, a partir de una dirección nueva creada para ello: un nombre de usuario y una contraseña que cumplen las reglas de los campos, la fecha de nacimiento en el formato del campo, un país que ofrece la lista desplegable. En Playwright, lee los campos de la página y rellena cada uno:

const form = page.locator('form#sign-up');
const fields = await form.locator('input, select').evaluateAll((elements) =>
elements.map((el, i) => {
const input = el as HTMLInputElement & HTMLSelectElement;
input.dataset.field = String(i);
return {
id: String(i),
type: input.tagName === 'SELECT' ? 'select' : input.type,
name: input.name,
label: input.labels?.[0]?.textContent ?? input.getAttribute('aria-label') ?? '',
placeholder: input.placeholder,
autocomplete: input.getAttribute('autocomplete') ?? '',
pattern: input.pattern || undefined,
minLength: input.minLength > 0 ? input.minLength : undefined,
maxLength: input.maxLength > 0 ? input.maxLength : undefined,
options: input.tagName === 'SELECT' ? Array.from(input.options, (o) => ({ value: o.value, label: o.text })) : undefined,
};
}),
);
const { address, values } = await qa.formValues({ fields });
for (const { id, kind, value } of values) {
if (value === null) continue;
const field = form.locator(`[data-field="${id}"]`);
if (await field.evaluate((el) => el.tagName === 'SELECT')) await field.selectOption(value);
else await field.fill(value);
}

Pasa address para usar una cuenta creada antes, y age o country para una nueva. Más información en la guía REST API.

Comprobar que no llega nada, o que llega un correo

Sección titulada «Comprobar que no llega nada, o que llega un correo»
// An unsubscribed user gets no newsletter.
const sent = new Date();
await sendNewsletter();
await qa.expectNoEmail(unsubscribed.address, { since: sent, timeout: 20 });
// A sign-up sends one verification email, not two.
await qa.waitForLink(address, { since: submitted });
await new Promise((resolve) => setTimeout(resolve, 10_000)); // time for a duplicate
expect((await qa.countEmails(address, { since: submitted, type: 'link' })).count).toBe(1);

Ninguno devuelve lo que dicen los correos, solo cuándo llegó cada uno, quién lo envió y el tipo de su código o enlace. Más información en la guía REST API.

Comprobar el envío y la construcción de un correo

Sección titulada «Comprobar el envío y la construcción de un correo»
await qa.waitForLink(address, { since: submitted });
const result = await qa.checkEmail(address, { since: submitted, type: 'link', expectHost: 'staging.example.com' });
expect(result.failed).toEqual([]); // e.g. ['local_links'] when the link goes to localhost
// A localised email: the right language, with the user's name in it.
const german = await qa.checkEmail(address, { since: submitted, expectLanguage: 'de', expectText: [persona.first_name] });
expect(german.failed).toEqual([]);

Cada comprobación pasa, falla, avisa o se omite, con un motivo de una línea; lo que significa cada una está en la guía REST API. ¿Pruebas una app que se ejecuta en tu máquina? await qa.markDevHost('localhost:3000') una vez, y los enlaces a ella pasan local_links; los enlaces a cualquier otro localhost siguen fallando.

Un grupo de cuentas para pruebas en paralelo

Sección titulada «Un grupo de cuentas para pruebas en paralelo»

Etiqueta las cuentas con su estado y deja que cada prueba tome una prestada. withAccount reserva una, ejecuta tu función y siempre la devuelve; devuelve { addTags, removeTags } para registrar lo que cambió la prueba:

await qa.withAccount({ tags: ['verified', 'premium'] }, async (account) => {
const { secret } = await qa.savedPassword(account.address);
await signIn(page, account.address, secret);
await cancelSubscription(page);
return { addTags: ['cancelled'], removeTags: ['premium'] };
});

Ninguna otra prueba recibe la misma cuenta mientras está reservada (10 minutos salvo que pases minutes), y una que nunca se devuelve se libera sola cuando se acaba su tiempo.

Cuando una prueba necesita una cuenta en un estado concreto en tu app (verificada, en el plan premium, sin pedidos), dale a withAccount tres hooks. Tu código de prueba los ejecuta con su propio acceso a tu app; Proxy Mail QA nunca inicia sesión en ella. Aquí app representa tu propio código que se comunica con tu app: una API de administración, un seed de la base de datos o la UI.

await qa.withAccount(
{
tags: ['verified', 'premium'],
// When none is free: sign up with the new address, verify it, upgrade it.
create: async (account) => {
const submitted = new Date();
await app.signUp(account.address, account.password);
const { value: link } = await qa.waitForLink(account.address, { type: 'verify', since: submitted });
await app.open(link);
await app.admin.setPlan(account.address, 'premium');
},
// Is it still in that state? Return false or throw if not.
check: async (account) => (await app.admin.user(account.address)).plan === 'premium',
// After the test: put it back.
reset: async (account) => app.admin.resetUser(account.address, { plan: 'premium', orders: [] }),
},
async (account) => {
// …the test…
},
);
  • check se ejecuta antes de que la prueba reciba la cuenta. Si falla, se ejecuta reset y se vuelve a comprobar; si sigue fallando, se prueba la siguiente cuenta (hasta attempts, 3), y la que falló pierde las etiquetas del grupo y recibe la etiqueta check-failed.
  • create se ejecuta cuando ninguna cuenta coincide o todas las que coinciden están reservadas. La nueva dirección queda reservada para esta prueba desde el principio y recibe las etiquetas cuando create y check tienen éxito. Pasa newAccount para su edad, etiqueta o dominio; recibe el país del filtro.
  • reset se ejecuta después de la prueba, haya pasado o no. Si reset falla, la cuenta pierde las etiquetas del grupo y recibe la etiqueta needs-reset, para que ninguna otra prueba la reciba en un estado desconocido.

En una ejecución, una cuenta nueva, un check fallido y un reset fallido añaden cada uno una línea al informe.

Las llamadas hechas mientras una ejecución está abierta le añaden lo que vieron: las direcciones, qué correos llegaron y cuánto después de since, los códigos, adónde lleva cada enlace, las comprobaciones, las comprobaciones de baja, ofertas y cupones, las pruebas fallidas y los correos que nunca llegaron. En GitHub Actions, la acción de Proxy Mail QA inicia y termina la ejecución y publica el informe en el pull request; el cliente toma la ejecución de PROXYMAIL_QA_RUN sin cambios en tus pruebas. En otros entornos, ejecútala tú:

const run = await qa.startRun({ name: 'Sign-up tests', meta: { branch: process.env.BRANCH } });
// …the tests, using qa…
await qa.finishRun({ failed: testsFailed });
console.log(await qa.runReport(run.id)); // Markdown, for a pull request comment

Otros procesos se unen a la ejecución cuando les pasas su id como PROXYMAIL_QA_RUN.

Para mostrar de qué prueba vino cada correo, usa un cliente por prueba: qa.forTest('sign-up › sends one code') nombra la prueba en cada llamada. En Playwright, proxymail-qa/playwright da a cada prueba un fixture qa que hace esto, y proxymail-qa/playwright/reporter registra las pruebas fallidas e inestables y los totales en la ejecución: consulta Ejecución en CI. Otros ejecutores de pruebas pueden enviar los resultados ellos mismos con qa.recordTest({ name, status, durationMs, error }) y qa.recordTestTotals({ passed, failed, skipped, flaky }). Nuevo en 0.8.0.

Las llamadas fallidas lanzan ProxyMailQAError con el status HTTP, un code y un mensaje legible. Cuando una espera no encuentra nada, el mensaje también dice por qué, hasta donde Proxy Mail QA puede saberlo (error.hint), por ejemplo que el proveedor de correo de la app solo envía a direcciones verificadas, y error.refused lista el correo rechazado por ser demasiado grande, superar un límite o venir de un sitio con el que Proxy Mail QA no funciona (refused[].site lo nombra):

import { ProxyMailQAError } from 'proxymail-qa';
try {
await qa.waitForCode(address, { since: submitted });
} catch (error) {
if (error instanceof ProxyMailQAError && error.status === 404) console.log(error.hint);
throw error;
}

Reintentar no servirá hasta que la app pueda enviar a cualquier dirección. Consulta ¿No llega el correo de prueba?

Las pruebas y los agentes pueden volver a una cuenta que crearon antes en lugar de crear otra. Todos los filtros indicados deben coincidir; las edades son años cumplidos a día de hoy, según la fecha de nacimiento de la persona, y search busca en la dirección, la etiqueta, las notas, el nombre y el nombre de usuario.

// An under-18 in the UK, made for the age-gate tests, with a saved password.
const [teen] = await qa.listAddresses({ tags: ['age-gate'], country: 'GB', maxAge: 17, hasPassword: true });
// Record where an account is used, then sign in with it again later: the
// account used on this site first, with its password (needs a key with
// "Can read saved passwords").
await qa.createAddress({ password: true, site: 'staging.example.com', environment: 'staging' });
const account = await qa.signInAccount({ site: 'staging.example.com', tags: ['verified'] });

¿Tienes una pregunta sobre esta página?

Pregunta en cualquier idioma. La documentación puede responderte de inmediato, o puedes enviar tu pregunta al equipo de Proxy Mail QA, que responde en inglés.