otpbox

OTPBox / Guias / Cypress

Cypress

Verificação de e-mail com Cypress

As specs do Cypress rodam dentro do navegador, mas um cliente HTTP que cria caixas e faz polling de e-mails pertence ao Node — então o padrão aqui é uma cy.task() que envolve o otpbox-sdk, chamada a partir de uma spec normal. Sem provedor de e-mail simulado, sem uma caixa compartilhada disputada por dois arquivos de spec em paralelo.

Status: um pacote dedicado cypress-plugin/otpbox-cypress (um wrapper pronto de cy.task/comandos) está sendo desenvolvido, mas ainda não foi publicado no npm. Este guia usa o otpbox-sdk diretamente, que já está no npm hoje e é tudo o que uma cy.task precisa.

Por que uma task, e não cy.request()

cy.request() pode chamar a API REST do OTPBox diretamente e funcionaria para criar uma caixa uma única vez. Mas aguardar um e-mail exige polling, e uma cy.task() que roda no Node permite usar o waitForOtp() do otpbox-sdk como está, em vez de escrever manualmente um loop de retentativa na spec do lado do navegador. Também mantém sua chave de API completamente fora do contexto do navegador.

1. Instale o SDK

npm install otpbox-sdk

2. Registre as tasks em 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;
        },
      });
    },
  },
});

Defina OTPBOX_KEY no seu shell, no cofre de segredos do CI, ou em um cypress.env.json ignorado pelo git — nunca faça commit dela. Sem chave? O otpbox-sdk não tem um helper próprio de geração automática; gere uma com curl -X POST https://otpbox.io/api/v1/keys/free (veja Obter uma chave) e exporte-a, ou use uma chave de organização a partir do painel para o CI.

3. Use as tasks em uma 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);
  });
});

Duas particularidades do Cypress que vale mencionar: passe uma opção timeout por task para cy.task() que seja maior do que o timeoutMs próprio do SDK (o timeout de task padrão do Cypress é de 60 segundos, mas vale ser explícito), e use this.inboxId/cy.wrap().as() em vez de uma variável no nível do módulo, já que os comandos do Cypress são assíncronos e enfileirados em vez de aguardados (await) diretamente.

Verificando por link em vez de código

Alguns fluxos de cadastro enviam um link de confirmação em vez de um código de 6 dígitos. Adicione mais uma task junto das outras, e então visite a URL que ela retorna:

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

Encapsulando as tasks em um comando personalizado

Depois de escrever o mesmo par cy.task('otpboxCreateInbox') / cy.task('otpboxWaitForOtp', ...) em mais de uma spec, vale a pena transformá-los em comandos personalizados em cypress/support/commands.ts para que as specs fiquem lendo como texto simples:

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

Isso é apenas uma camada fina de conveniência sobre as mesmas duas tasks — nada muda do lado do OTPBox, é puramente para manter os arquivos de spec legíveis quando vários testes precisam da mesma sequência de cadastro e verificação.

Notas para CI

Próximos passos

Pronto para testar no seu próprio app? Crie uma conta gratuita

← Voltar à OTPBox