# Receive email in a Playwright test

> The whole test that waits for an email, reads the code inside it and carries on, with no sleep and no package to install.
> https://facteur.eu/en/help/tests-playwright

A sign-up journey ends in a mailbox, and your test has to read what lands there. Here is
the whole file, with nothing of ours to install.

## 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`. The test below creates nothing: it reads. See
   [API keys](cles-api).
2. **Your inbox's identifier**, the eight characters shown at the top of the inbox
   view.

```ts
import { expect, test } from '@playwright/test';

const KEY = process.env.FACTEUR_API_KEY!;
const INBOX = 'j3k9x2mq';

/** Stays open until the message lands, thirty seconds at most. */
const waitForMail = (sentTo: string) =>
  fetch('https://api.facteur.eu/v1/messages/search', {
    method: 'POST',
    headers: {
      authorization: `Bearer ${KEY}`,
      'content-type': 'application/json',
    },
    body: JSON.stringify({ inbox: INBOX, sentTo, wait: 30_000 }),
  }).then((response) => response.json());

test('the verification link activates the account', async ({ page }) => {
  const tag = `run-${Date.now()}`;

  // Issued before the click: it answers as soon as the mail arrives.
  const arriving = waitForMail(`signup+${tag}`);

  await page.goto('/signup');
  await page.getByLabel('Email').fill(`signup+${tag}@${INBOX}.inbox.facteur.eu`);
  await page.getByRole('button', { name: 'Create my account' }).click();

  const { messages } = await arriving;
  expect(messages[0].status).toBe('parsed');
  await page.goto(messages[0].links[0].href);

  await expect(page.getByText('Account verified')).toBeVisible();
});
```

## Start the wait before the action

That is the one line whose order matters. The request goes out before the click, stays
open, and answers the moment the mail arrives. Issued afterwards, it would chase a
message that may already have been handled, and you would meet that defect the day your
pipeline gets slow.

## One address per test, with nothing to create

Every address ending in your inbox's domain arrives there. So there is no call to make
before using `signup+run-1734@j3k9x2mq.inbox.facteur.eu`: writing it is enough. That is
what lets sixteen parallel tests share one inbox without stealing each other's mail.

Put the test name and the attempt number in the tag. Without the attempt, a retried test
reads the mail from its own first run and passes for the wrong reason, exactly when
something is already flaky.

## Read `status` before anything else

An accepted email exists before it is parsed. For a few milliseconds it has no subject,
no code and no links. The `status` field separates "there is no code in this message"
from "the code is not extracted yet". Search returns parsed messages only by default, so
the case arises only if you ask for `status: any`.

## The code is already out of the message

`messages[0].otp` carries the one-time code, extracted on reception. You write no
regular expression, and you have nothing to fix the day the email template changes.

The links are there too: `messages[0].links` carries one object per link, in the order
they appear, with its `href` and its `text`.

## Install the Playwright plugin

The plugin writes each test's address and the wait for you. It needs Node 20 or later and
Playwright 1.40 or later.

1. Install it: `npm i -D playwright-facteur`.
2. Mint a key under **Settings → API keys**, and set it as `FACTEUR_API_KEY` in your CI
   environment.
3. Import `test` and `expect` from `playwright-facteur` rather than from
   `@playwright/test`.

You get an `inbox` fixture that hands one address to each test, and matchers such as
`expect(message).toHaveOtp()` that Playwright's own engine retries. The test above does
not need the plugin and keeps working as it stands.

The state of each integration is on the [Integrations](https://facteur.eu/en/integrations) page.
