Cypress
Waiting for mail without cy.wait
Cypress runs your test inside the browser, which raises two problems for reading email: the API key must not be there, and the network call answers to the origin of the page under test. One Node task solves both.
The config, then the test
The task lives in the Cypress process, where the key can stay. The test only ever sees a command.
// cypress.config.js — the key stays here, out of the browser.
import { defineConfig } from 'cypress';
export default defineConfig({
e2e: {
setupNodeEvents(on) {
on('task', {
async waitForMail({ sentTo }) {
const response = await fetch('https://api.facteur.eu/v1/messages/search', {
method: 'POST',
headers: {
authorization: 'Bearer ' + process.env.FACTEUR_API_KEY,
'content-type': 'application/json',
},
body: JSON.stringify({ inbox: 'j3k9x2mq', sentTo, wait: 30000 }),
});
const { messages } = await response.json();
return messages[0] ?? null;
},
});
},
},
});
// the test — the attempt number keeps a retry apart from the run before it.
it('the code arrives by email', () => {
const tag = 'signup-a' + (Cypress.currentRetry ?? 0) + '-' + Date.now();
cy.visit('/signup');
cy.get('[name=email]').type(tag + '@j3k9x2mq.inbox.facteur.eu');
cy.contains('button', 'Create my account').click();
cy.task('waitForMail', { sentTo: tag }, { timeout: 40000 }).should((message) => {
expect(message, 'no mail arrived').to.not.be.null;
expect(message.otp).to.match(/^[0-9]{6}$/);
});
});The key stays on the Node side
Your spec is served to a page: a key written into it would be a key in a page’s JavaScript. The task keeps it on the other side.
cy.task yields a value, so the chain continues
The message comes back into the usual command queue, and .should() behaves as it does on a cy.get().
The attempt number is in the address
Without it, a retried test reads the mail from its own first attempt and passes for the wrong reason, exactly when something is already flaky.
The application that sends this email
What is left is an application that sends the message this test waits for. La Belle Etoile is one, open to anyone: order a lamp, give it your test mailbox address, and the confirmation arrives with its code. It serves eight journeys, one of which mails nothing at all: that is the one that exercises a test asserting no email went out. It only ever mails a Facteur address, which is what stops it being a relay for anyone else.
Why there is no cy.wait
The waiting happens on our side
The request stays open until the message lands, with a maximum delay you set. A fixed cy.wait is either too short on a loaded machine or wasted time on every run.
The code is already extracted
The message arrives with its one-time code in a field of its own.
One mailbox is enough for the suite
Each test receives at its own address inside the same mailbox, with nothing to create. On a plan that includes a single mailbox, that is the difference between a suite that runs and one that hits a ceiling.
The Cypress plugin, if you want it
It installs in one line: npm i -D cypress-facteur. The version above is still the one that needs nothing installed, in about thirty lines you keep.
Write this test today
A test mailbox and a read-only key are enough. The free plan asks for no card.