otpbox

OTPBox / Guías / Cypress

Cypress

Verificación de email con Cypress

Las specs de Cypress se ejecutan dentro del navegador, pero un cliente HTTP que crea bandejas y sondea el correo pertenece a Node — así que el patrón aquí es una cy.task() que envuelve otpbox-sdk, llamada desde una spec por lo demás normal. Sin proveedor de correo simulado, sin una bandeja compartida por la que compitan dos archivos de specs en paralelo.

Estado: se está construyendo un paquete dedicado cypress-plugin/otpbox-cypress (un wrapper de cy.task/comandos ya listo), pero todavía no está publicado en npm. Esta guía usa otpbox-sdk directamente, que ya está en npm y es todo lo que necesita una cy.task.

Por qué una task y no cy.request()

cy.request() puede llamar a la API REST de OTPBox directamente y funcionaría para crear una bandeja una sola vez. Pero esperar un correo implica sondeo, y una cy.task() que se ejecuta en Node te permite usar waitForOtp() de otpbox-sdk tal cual, en lugar de escribir a mano un bucle de reintentos en la spec del lado del navegador. También mantiene tu clave de API completamente fuera del contexto del navegador.

1. Instala el SDK

npm install otpbox-sdk

2. Registra las tasks en cypress.config.ts

import { defineConfig } from 'cypress';
import { OTPBox } from 'otpbox-sdk';

export default defineConfig({
  e2e: {
    setupNodeEvents(on) {
      const client = new OTPBox({ apiKey: process.env.OTPBOX_KEY! });
      let currentInboxId: string | null = null;

      on('task', {
        async otpboxCreateInbox() {
          const inbox = await client.createInbox();
          currentInboxId = inbox.id;
          return inbox; // { id, address, domain, expiresAt }
        },
        async otpboxWaitForOtp(inboxId: string) {
          return client.waitForOtp(inboxId, { timeoutMs: 20_000 });
        },
        async otpboxDeleteInbox(inboxId: string) {
          await client.deleteInbox(inboxId);
          return null;
        },
      });
    },
  },
});

Define OTPBOX_KEY en tu shell, en el almacén de secretos de CI, o en un cypress.env.json ignorado por git — nunca lo subas al repositorio. ¿Sin clave? otpbox-sdk no tiene un ayudante de generación automática propio; genera una con curl -X POST https://otpbox.io/api/v1/keys/free (ver Obtener una clave) y expórtala, o usa una clave de organización desde el panel para CI.

3. Usa las tasks en una spec

describe('sign up', () => {
  it('verifies with a real OTP', () => {
    cy.task('otpboxCreateInbox').then((inbox: any) => {
      cy.wrap(inbox.id).as('inboxId');

      cy.visit('https://your-app.example.com/signup');
      cy.get('[name="email"]').type(inbox.address);
      cy.get('button[type="submit"]').click();

      // Blocks Node-side until the SDK sees a message with an extracted code
      cy.task('otpboxWaitForOtp', inbox.id, { timeout: 25000 }).then((code) => {
        expect(code).to.be.a('string');
        cy.get('[name="otp"]').type(code as string);
        cy.get('button[type="submit"]').click();
        cy.contains('Welcome').should('be.visible');
      });
    });
  });

  afterEach(function () {
    if (this.inboxId) cy.task('otpboxDeleteInbox', this.inboxId);
  });
});

Dos particularidades de Cypress que vale la pena mencionar: pasa una opción timeout por task a cy.task() que sea mayor que el timeoutMs propio del SDK (el timeout de task por defecto de Cypress es 60 segundos, pero conviene ser explícito), y usa this.inboxId/cy.wrap().as() en lugar de una variable a nivel de módulo, ya que los comandos de Cypress son asíncronos y se encolan en lugar de esperarse (await) directamente.

Verificar con un enlace en lugar de un código

Algunos flujos de registro envían un enlace de confirmación en lugar de un código de 6 dígitos. Añade una task más junto a las demás, y luego visita la URL que devuelve:

// cypress.config.ts, inside setupNodeEvents
on('task', {
  // ...otpboxCreateInbox, otpboxDeleteInbox as above...
  async otpboxWaitForLink(inboxId: string) {
    const message = await client.waitForEmail(inboxId, { timeoutMs: 20_000 });
    if (!message) return null;
    const full = await client.getMessage(message.id);
    return full.link?.url ?? null;
  },
});
cy.task('otpboxWaitForLink', inbox.id).then((url: any) => {
  expect(url).to.be.a('string');
  cy.visit(url);
  cy.contains('Verified').should('be.visible');
});

Envolver las tasks en un comando personalizado

Una vez que hayas escrito el mismo par cy.task('otpboxCreateInbox') / cy.task('otpboxWaitForOtp', ...) en más de una spec, vale la pena convertirlos en comandos personalizados en cypress/support/commands.ts para que las specs se lean como inglés (o español) llano:

Cypress.Commands.add('otpboxSignUp', (email: string) => {
  cy.get('[name="email"]').type(email);
  cy.get('button[type="submit"]').click();
});

Cypress.Commands.add('otpboxEnterCode', (inboxId: string) => {
  cy.task('otpboxWaitForOtp', inboxId, { timeout: 25000 }).then((code: any) => {
    cy.get('[name="otp"]').type(code);
    cy.get('button[type="submit"]').click();
  });
});

Esto es solo una capa fina de conveniencia sobre las dos mismas tasks — nada cambia en el lado de OTPBox, es puramente para mantener los archivos de specs legibles una vez que varias pruebas necesitan la misma secuencia de registro y verificación.

Notas para CI

Próximos pasos

¿Listo para probarlo en tu propia app? Crea una cuenta gratuita

← Volver a OTPBox