Skip to content
Facteur
FRGet started

Type at least two characters.

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:

  1. 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.
  2. Your inbox’s identifier, the eight characters shown at the top of the inbox view. It is j3k9x2mq in 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.

  1. Install it: npm i -D cypress-facteur.
  2. Mint a key under Settings → API keys, and set it as FACTEUR_API_KEY in your CI environment.
  3. In cypress.config.js, pass setupNodeEvents to facteurTasks, imported from cypress-facteur/plugin.
  4. In cypress/support/e2e.js, add import '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?