Skip to content
Facteur

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".

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.

What about a Playwright plugin?

It is planned and it is not published: we would rather write it than let you search npm for a package that does not exist. Meanwhile the test above needs no dependency of ours, which stays true the day the plugin ships.

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.