otpbox

OTPBox / Anleitungen / Cypress

Cypress

E-Mail-Verifizierung mit Cypress

Cypress-Specs laufen im Browser, aber ein HTTP-Client, der Postfächer erstellt und auf Post pollt, gehört nach Node — das Muster hier ist also eine cy.task(), die otpbox-sdk umschließt und aus einer ansonsten gewöhnlichen Spec heraus aufgerufen wird. Kein gemockter E-Mail-Anbieter, kein gemeinsam genutztes Postfach, um das zwei parallele Spec-Dateien konkurrieren.

Status: ein eigenes Paket cypress-plugin/otpbox-cypress (ein fertiger cy.task-/Command-Wrapper) ist in Arbeit, aber noch nicht auf npm veröffentlicht. Diese Anleitung nutzt otpbox-sdk direkt, das schon heute auf npm ist und alles bietet, was eine cy.task braucht.

Warum eine Task und nicht cy.request()

cy.request() kann die REST-API von OTPBox direkt aufrufen und würde für eine einmalige Postfach-Erstellung funktionieren. Aber das Warten auf eine E-Mail erfordert Polling, und eine cy.task(), die in Node läuft, erlaubt dir, waitForOtp() aus otpbox-sdk unverändert zu nutzen, statt eine Retry-Schleife von Hand in der browserseitigen Spec zu schreiben. Außerdem bleibt dein API-Schlüssel dadurch vollständig aus dem Browser-Kontext heraus.

1. SDK installieren

npm install otpbox-sdk

2. Tasks in cypress.config.ts registrieren

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

Setze OTPBOX_KEY in deiner Shell, im Secret-Speicher deiner CI oder in einer von git ignorierten cypress.env.json — committe ihn niemals. Kein Schlüssel? otpbox-sdk hat keinen eigenen Auto-Mint-Helfer; erstelle einen mit curl -X POST https://otpbox.io/api/v1/keys/free (siehe Einen Schlüssel erhalten) und exportiere ihn, oder nutze für CI einen Organisations-Schlüssel aus dem Dashboard.

3. Die Tasks in einer Spec verwenden

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

Zwei Cypress-Besonderheiten, die man kennen sollte: übergib cy.task() eine Task-eigene timeout-Option, die länger ist als das timeoutMs des SDK (Cypress' Standard-Task-Timeout liegt bei 60 Sekunden, aber es lohnt sich, explizit zu sein), und nutze this.inboxId/cy.wrap().as() statt einer Variable auf Modulebene, da Cypress-Befehle asynchron und in eine Warteschlange eingereiht statt direkt awaited werden.

Verifizierung per Link statt per Code

Manche Registrierungsabläufe verschicken einen Bestätigungslink statt eines sechsstelligen Codes. Füge den anderen Tasks eine weitere hinzu und besuche dann die zurückgegebene URL:

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

Tasks in einem eigenen Command bündeln

Sobald du dasselbe Paar cy.task('otpboxCreateInbox') / cy.task('otpboxWaitForOtp', ...) in mehr als einer Spec geschrieben hast, lohnt es sich, sie zu eigenen Commands in cypress/support/commands.ts zu machen, damit sich Specs wie einfacher Text lesen:

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

Das ist nur eine dünne Komfortschicht über denselben zwei Tasks — auf OTPBox-Seite ändert sich nichts, es geht rein darum, Spec-Dateien lesbar zu halten, sobald mehrere Tests dieselbe Registrierungs- und Verifizierungssequenz brauchen.

Hinweise für CI

Nächste Schritte

Bereit, es an deiner eigenen App auszuprobieren? Kostenloses Konto erstellen

← Zurück zu OTPBox