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.
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.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.
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;
},
});
},
},
});
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.
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.
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');
});
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.
OTPBOX_KEY comme variable de CI masquée/protégée — voir CI/CD pour des exemples avec GitHub Actions, GitLab CI, Jenkins et CircleCI (les mêmes étapes d'injection de variable s'appliquent quel que soit le test runner qui consomme la clé).429 quota_exceeded.otpboxCreateInbox avec une clé d'idempotence si un réseau de CI instable fait relancer votre task — voir Clés d'idempotence — en passant une option idempotencyKey en second argument de client.createInbox().afterEach/after comme montré ci-dessus ; tout ce qui reste est de toute façon nettoyé par le cron d'expiration de 15 minutes.Prêt à l'essayer sur votre propre application ? Créer un compte gratuit
← Retour à OTPBox