# 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.
> https://facteur.eu/en/help/tests-cypress

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**](https://app.facteur.eu/settings/api-keys) and put it in
   your CI secrets, as `FACTEUR_API_KEY`. See [API keys](cles-api).
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

```js
// 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

```js
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](https://facteur.eu/en/integrations) page.
