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.
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.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.
npm install otpbox-sdk
cypress.config.tsimport { 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.
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.
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');
});
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.
OTPBOX_KEY como uma variável de CI mascarada/protegida — veja CI/CD para exemplos com GitHub Actions, GitLab CI, Jenkins e CircleCI (os mesmos passos de injeção de variável se aplicam independentemente de qual test runner consome a chave).429 quota_exceeded.otpboxCreateInbox com uma chave de idempotência se uma rede de CI instável fizer sua task tentar novamente — veja Chaves de idempotência — passando uma opção idempotencyKey como segundo argumento de client.createInbox().afterEach/after como mostrado acima; qualquer coisa deixada para trás é limpa pelo cron de expiração de 15 minutos de qualquer forma.Pronto para testar no seu próprio app? Crie uma conta gratuita
← Voltar à OTPBox