Receive email in a Cypress test
A Node task that keeps the key out of the browser, and a test that waits for the mail instead of pausing for a fixed number of seconds.
Cypress runs your test inside the browser, which raises two problems for reading email: your API key has no business in a page, and the network call answers to the origin of the site under test. One Node task solves both.
Before you start
Two things, and nothing else:
- A read-only API key. Create it in
Settings → API keys and put it in
your CI secrets, as
FACTEUR_API_KEY. See API keys. - Your inbox’s identifier, the eight characters shown at the top of the inbox
view. It is
j3k9x2mqin the examples below.
The config, where the key can stay
// cypress.config.js
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;
},
});
},
},
});
This file runs in the Cypress process, not in the page. The key is read from the environment there and never reaches the browser.
The test
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}$/);
});
});
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 pause is either too short on a loaded machine or wasted time on every run.
The timeout given to cy.task has to stay above the wait sent to the API: forty
seconds for a thirty-second wait. Otherwise Cypress abandons the task before the answer
arrives, and the failure blames the wrong thing.
The attempt number in the address
Cypress.currentRetry is 0 on the first run. 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.
One inbox is enough for the whole suite
Every address ending in the inbox’s domain arrives there, with nothing to create. Each test therefore receives at its own address inside the same inbox. On a plan that includes a single inbox, that is the difference between a suite that runs and one that hits a ceiling.
Install the Cypress plugin
The plugin keeps the API key out of the browser and hands you chainable commands, which the version above does by hand in about thirty lines.
- Install it:
npm i -D cypress-facteur. - Mint a key under Settings → API keys, and set it as
FACTEUR_API_KEYin your CI environment. - In
cypress.config.js, passsetupNodeEventstofacteurTasks, imported fromcypress-facteur/plugin. - In
cypress/support/e2e.js, addimport 'cypress-facteur'.
Your tests then have cy.facteurAddress() and cy.facteurWaitFor(). The version above
keeps working with nothing installed.
The state of each integration is on the Integrations page.
Checked on Is this article wrong or incomplete?