Skip to content
Facteur
FRGet started

Playwright

Your test waits for the mail. It does not sleep.

A sign-up journey ends in a mailbox. Without one the test can read, there are two options left: mock the mail away, which proves nothing about the part most likely to break, or loop on a fixed delay. Here is the third.

The whole test

Nothing is trimmed: this is the complete file, and it imports Playwright only.

signup.spec.tsts
import { expect, test } from '@playwright/test';

const KEY = process.env.FACTEUR_API_KEY!;
const INBOX = 'j3k9x2mq';
const DOMAIN = `${INBOX}.inbox.facteur.eu`;

/** 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}@${DOMAIN}`);
  await page.getByRole('button', { name: 'Create my account' }).click();

  const { messages } = await arriving;
  const message = messages[0];

  // The status before anything derived from the body.
  expect(message.status).toBe('parsed');
  await page.goto(message.links[0].href);

  await expect(page.getByText('Account verified')).toBeVisible();
});
  • The request goes out before the click

    It stays open and answers the moment the mail lands, not at the deadline. Issued after the click, it would chase a message that may already have been handled.

  • Every test has its own address

    Everything after the "+" is yours and still arrives. Two tests running at once cannot steal each other’s mail, and nothing has to be created first.

  • The code is already extracted

    The message arrives with its one-time code in a field of its own. You are not rewriting the regular expression that sits in every test suite.

  • Read status before anything else

    An accepted email exists before it is parsed: for a few milliseconds it has no subject and no code. That field separates "there is no code in this message" from "the code is not extracted yet".

The application that sends this email

The test above goes nowhere without an application that really sends the message, and yours may not be ready. La Belle Etoile is one, open to anyone: order a lamp, give it your test mailbox address, and the confirmation arrives with its code. No account to create, nothing to install. The shop only ever mails a Facteur address, which is what stops it being a relay for anyone else, and its code knows nothing about us: it behaves like your application rather than like a showcase.

Open the demo shop

Why this test fits in thirty lines

The waiting happens on our side

You set a maximum delay, the request stays open, and it answers on arrival. Your test carries no retry loop, and nothing to tune the day CI gets slow.

The extraction happens on reception

One-time codes, links and buttons are pulled out of the body as the message lands. Your test reads a field instead of cutting up HTML.

Addresses are unlimited

Every address ending in your test mailbox’s domain arrives, with no API call to create it. That is what makes one address per test free.

The Playwright plugin, if you want it

It installs in one line: npm i -D playwright-facteur. It derives one address per test, retries its assertions the way Playwright does, and attaches the message it received to the report, so a failure shows the mail that arrived instead of a red line on its own. The test above does not use it and keeps working: the plugin buys comfort, not capability.

See the state of each integration

Write this test today

The free plan opens a test mailbox, with no card. By the time the account exists, you have the address to paste into the test above.