Receive email in a Cypress test
Cypress runs your test in the browser. That raises a problem for reading an email: a key written in a spec is served to a page, and anyone who opens the developer tools of the browser reads it. A Node task solves it, because the task runs in the Cypress process and the key never reaches the page.
This page calls the API directly. No Facteur plugin for Cypress is published yet, so there is no package to install.
Before you start
Section titled “Before you start”- A read-only API key, kept in the secrets of your continuous integration as
FACTEUR_API_KEY. The quickstart shows how to create it. - The identifier of your inbox: the eight characters before
.inboxin its address. It isj3k9x2mqin the examples.
The configuration, where the key can stay
Section titled “The configuration, where the key can stay”import { defineConfig } from 'cypress'
export default defineConfig({ e2e: { setupNodeEvents(on) { on('task', { async waitForEmail({ sentTo }) { const search = new URLSearchParams({ inbox: 'j3k9x2mq', sentTo, wait: '30000' }) const response = await fetch(`https://api.facteur.eu/messages?${search}`, { headers: { authorization: `Bearer ${process.env.FACTEUR_API_KEY}` }, }) if (!response.ok) throw new Error(`Facteur answered ${response.status}`)
const { messages } = await response.json() return messages[0] ?? null }, }) }, },})The test
Section titled “The test”it('the code arrives by email', () => { const tag = `signup-a${Cypress.currentRetry ?? 0}-${Date.now()}`
// The sign-up journey of your own application. cy.visit('/signup') cy.get('[name=email]').type(`${tag}@j3k9x2mq.inbox.facteur.eu`) cy.contains('button', 'Create my account').click()
cy.task('waitForEmail', { sentTo: tag }, { timeout: 40000 }).should((message) => { expect(message, 'an email arrived').to.not.be.null expect(message.otp).to.match(/^[0-9]{6}$/) })})No cy.wait
Section titled “No cy.wait”The wait happens on the side of Facteur: the request stays open until the message arrives, for the time you set. A fixed pause is too short on a loaded machine, and time lost on every other run.
The timeout of cy.task must stay above the wait sent to the API: forty seconds for a wait of thirty. Otherwise Cypress gives up on the task before the answer arrives, and the failure blames the wrong thing.
In this test the search starts after the click, because Cypress runs its commands one after another. Nothing is lost: the search looks once before it waits, so a message that arrived during the click comes back on that first look.
The attempt number in the address
Section titled “The attempt number in the address”Cypress.currentRetry is 0 on the first attempt. Without it in the address, a retried test reads the email of its first attempt and passes for the wrong reason, precisely when something is already unstable.
One inbox is enough for the suite
Section titled “One inbox is enough for the suite”Any address that ends with the domain of the inbox arrives in it, with nothing to create. Each test receives at its own address in the same inbox, so a plan with a single inbox is enough for a whole suite.