OTPBox / Anleitungen / Cypress
CypressCypress-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.
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.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.
npm install otpbox-sdk
cypress.config.ts registrierenimport { 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.
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.
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');
});
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.
OTPBOX_KEY als maskierte/geschützte CI-Variable — siehe CI/CD für Beispiele zu GitHub Actions, GitLab CI, Jenkins und CircleCI (die gleichen Schritte zum Einschleusen der Variable gelten unabhängig davon, welcher Test-Runner den Schlüssel verwendet).429 quota_exceeded stößt.otpboxCreateInbox-Aufruf mit einem Idempotenzschlüssel, falls ein instabiles CI-Netzwerk deine Task wiederholen lässt — siehe Idempotenzschlüssel — indem du eine idempotencyKey-Option als zweites Argument von client.createInbox() durchreichst.afterEach/after-Hook wie oben gezeigt; alles Übriggebliebene räumt der 15-Minuten-Ablauf-Cronjob ohnehin auf.Bereit, es an deiner eigenen App auszuprobieren? Kostenloses Konto erstellen
← Zurück zu OTPBox