otpbox

OTPBox / Guides / Cypress

Cypress

Vérification d'e-mail avec Cypress

Les specs Cypress s'exécutent dans le navigateur, mais un client HTTP qui crée des boîtes et interroge le courrier a sa place côté Node — le principe ici est donc une cy.task() qui enveloppe otpbox-sdk, appelée depuis une spec par ailleurs ordinaire. Aucun fournisseur d'e-mail simulé, aucune boîte partagée que deux fichiers de spec exécutés en parallèle se disputent.

Statut : un package dédié cypress-plugin/otpbox-cypress (un wrapper cy.task/commandes prêt à l'emploi) est en cours de développement mais n'est pas encore publié sur npm. Ce guide utilise otpbox-sdk directement, qui est déjà sur npm et suffit largement à une cy.task.

Pourquoi une task plutôt que cy.request()

cy.request() peut appeler directement l'API REST d'OTPBox et conviendrait pour créer une boîte en une seule fois. Mais attendre un e-mail implique du polling, et une cy.task() exécutée côté Node vous permet d'utiliser tel quel le waitForOtp() d'otpbox-sdk, au lieu d'écrire à la main une boucle de nouvelles tentatives dans la spec côté navigateur. Cela garde aussi votre clé API entièrement hors du contexte du navigateur.

1. Installer le SDK

npm install otpbox-sdk

2. Enregistrer les tasks dans 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;
        },
      });
    },
  },
});

Définissez OTPBOX_KEY dans votre shell, dans le coffre à secrets de votre CI, ou dans un cypress.env.json ignoré par git — ne le commitez jamais. Pas de clé ? otpbox-sdk n'a pas d'assistant de génération automatique intégré ; générez-en une avec curl -X POST https://otpbox.io/api/v1/keys/free (voir Obtenir une clé) et exportez-la, ou utilisez une clé d'organisation depuis le tableau de bord pour la CI.

3. Utiliser les tasks dans une 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);
  });
});

Deux particularités de Cypress à souligner : passez une option timeout par task à cy.task() supérieure au timeoutMs propre au SDK (le timeout de task par défaut de Cypress est de 60 secondes, mais mieux vaut être explicite), et utilisez this.inboxId/cy.wrap().as() plutôt qu'une variable au niveau du module, car les commandes Cypress sont asynchrones et mises en file d'attente plutôt qu'attendues (await) directement.

Vérifier via un lien plutôt qu'un code

Certains flux d'inscription envoient un lien de confirmation plutôt qu'un code à 6 chiffres. Ajoutez une task de plus aux côtés des autres, puis visitez l'URL qu'elle renvoie :

// 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');
});

Encapsuler les tasks dans une commande personnalisée

Une fois que vous avez écrit la même paire cy.task('otpboxCreateInbox') / cy.task('otpboxWaitForOtp', ...) dans plusieurs specs, il vaut la peine de les transformer en commandes personnalisées dans cypress/support/commands.ts, pour que les specs se lisent comme du texte simple :

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();
  });
});

Ce n'est qu'une fine couche de confort par-dessus les deux mêmes tasks — rien ne change côté OTPBox, c'est purement pour garder les fichiers de spec lisibles une fois que plusieurs tests ont besoin de la même séquence d'inscription et de vérification.

Notes pour la CI

Étapes suivantes

Prêt à l'essayer sur votre propre application ? Créer un compte gratuit

← Retour à OTPBox