MailSlurp logo

examples

Playwright email integration testing for signup and verification flows

Test signup, verification codes, magic links, and email release evidence with Playwright and MailSlurp programmable inboxes.

Playwright is a powerful end-to-end integration testing framework created by Microsoft with support for multiple browsers and frameworks. It's a modern alternative to browser testing frameworks like Cypress and Selenium. Add MailSlurp to your test suite to test email login and verification with Playwright for full application testing using private disposable email accounts.

Start with the Playwright email testing guide for the canonical workflow, then use this page for the complete implementation example. The email testing API provides the reusable inbox and wait controls, while Email Sandbox keeps staging SMTP traffic away from real recipients. For tests that combine email with SMS verification, use programmable email and SMS APIs.

Quick answer

Use Playwright for the browser journey and MailSlurp for the delivered email. The test should create an inbox, sign up, wait for the verification message, extract the code or link, submit it in the app, and keep evidence that the right message arrived.

When the email is customer-facing, add a follow-up render or audit check so the release gate proves more than receipt.

Testing front-end login

In this example we will test a demo application called the Playground that allows user sign up with an email address and password. Upon signing up the user is sent a verification code that must be extracted from the email and entered into the confirm user form.

Setup (installing playwright)

The easiest way to get started is to use the npm init methods in a new folder:

npm init playwright@latest

This will create a playwright.config.ts and an example test suite. The config for this example is as follows:

import type { PlaywrightTestConfig } from "@playwright/test";
import { devices } from "@playwright/test";
const config: PlaywrightTestConfig = {
  testDir: "./tests",
  timeout: 120_000,
  expect: {
    timeout: 5000,
  },
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: "html",
  use: {
    actionTimeout: 0,
    trace: "on-first-retry",
  },
  projects: [
    {
      name: "chromium",
      use: {
        ...devices["Desktop Chrome"],
      },
    },
  ],
};

export default config;

Install MailSlurp (email accounts)

Next we should install MailSlurp's free email library via NPM:

npm install --save mailslurp-client

Create a free account and copy your API Key from the dashboard. We will need this for the next step.

Configure a test using the authentication playground

Let's sign up in the Playground demo app. We'll create an address with MailSlurp, enter it in the signup form and receive the verification email. The test will read its code and submit it to confirm the account.

playwright smtp

Writing the test

Playwright tests use a Jest-like structure and a page object to select elements on a page and wait for conditions.

import { expect, test } from "@playwright/test";
import { MailSlurp, ExtractCodesOptionsMethodEnum, MatchOptionFieldEnum, MatchOptionShouldEnum } from "mailslurp-client";

test.describe("test email login with playwright", () => {
  test("can login and verify email address with mailslurp", async ({ page }) => {
    const apiKey = process.env.MAILSLURP_API_KEY;
    if (!apiKey) throw new Error("Set MAILSLURP_API_KEY");

    // load playground app
    await page.goto("https://playground.mailslurp.com");
    await page.click('[data-test="sign-in-create-account-link"]');

    // create a new inbox
    const mailslurp = new MailSlurp({ apiKey });
    const password = "test-password";
    const inbox = await mailslurp.createInboxWithOptions({ expiresIn: 600_000 });

    try {
      // fill sign up form
      await page.fill("input[name=email]", inbox.emailAddress);
      await page.fill("input[name=password]", password);
      const since = new Date();
      await page.click('[data-test="sign-up-create-account-button"]');

      // wait for this run's verification message, not merely the latest email
      const email = await mailslurp.waitController.waitForMatchingFirstEmail({
        inboxId: inbox.id,
        since,
        timeout: 60_000,
        unreadOnly: false,
        matchOptions: {
          matches: [{ field: MatchOptionFieldEnum.SUBJECT, should: MatchOptionShouldEnum.CONTAIN, value: "verification" }],
        },
      });

      expect(email.to).toContain(inbox.emailAddress);
      const result = await mailslurp.emailController.getEmailCodes({
        emailId: email.id,
        extractCodesOptions: {
          method: ExtractCodesOptionsMethodEnum.PATTERN,
          allowFallback: false,
          minLength: 6, maxLength: 6, maxCandidates: 5,
        },
      });
      const candidates = new Set(result.candidates.map(candidate => candidate.code));
      if (!result.found || typeof result.code !== "string" ||
          !/^[0-9]{6}$/.test(result.code) || candidates.size !== 1 ||
          !candidates.has(result.code)) {
        throw new Error("Expected one unambiguous six-digit confirmation code");
      }
      const code = result.code;

      // enter confirmation code
      await page.fill('[data-test="confirm-sign-up-confirmation-code-input"]', code);
      await page.click('[data-test="confirm-sign-up-confirm-button"]');

      // fill out username (email) and password
      await page.fill('[data-test="username-input"]', inbox.emailAddress);
      await page.fill('[data-test="sign-in-password-input"]', password);
      await page.click('[data-test="sign-in-sign-in-button"]');
      await page.waitForSelector("[data-test='greetings-nav']");
    } finally {
      await mailslurp.inboxController.deleteInbox({ inboxId: inbox.id });
    }
  });
});

Extracting the email verification code

The code API reads the received message rather than assuming the OTP is the final text in an HTML body. Keep the result as a string so a code such as 004219 retains its leading zeros. The example rejects messages with several plausible codes instead of choosing one arbitrarily.

A fresh inbox isolates this signup. The since timestamp is recorded before the send action, and the subject rule selects its verification email. unreadOnly: false allows the test to work even if someone has opened the message in the dashboard. Reading status does not identify a test run.

The 120-second Playwright test timeout includes browser setup, the 60-second message wait and the remaining sign-in steps. Adapt the subject rule and final assertion for your own application. Check its authenticated account email or user ID when available; the playground's greeting is only its demonstration success state.

Keep parallel Playwright runs isolated

Create a new inbox inside each test attempt, use a bounded message match instead of a fixed sleep, and delete the inbox in finally. A run-specific subject token gives even stronger evidence when the application can include one. Keep inbox and message IDs in restricted failure artifacts. Browser traces may contain credentials or OTPs; do not share them publicly. The inbox expiry is a fallback for an interrupted runner, and application users need their own cleanup.

verify user account

Add render and audit checks after receipt

Once Playwright proves the verification code works, extend the same run when the message itself matters:

That keeps visual review and link evidence close to the browser assertion instead of leaving them as a separate manual step.

Running the tests

To run the tests we can create a Makefile like so:

.PHONY: test

node_modules:
	npm install

test: node_modules
	MAILSLURP_API_KEY=$(MAILSLURP_API_KEY) npx playwright test

Then run the tests with MAILSLURP_API_KEY=your-api-key make test. A successful login will show this friendly dog and a welcome message:

playwright read email