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.

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.

Add render and audit checks after receipt
Once Playwright proves the verification code works, extend the same run when the message itself matters:
- use Device previews to review the delivered HTML in supported preview contexts
- use Email integration testing to assert sender, subject, recipients, headers, and links
- use Email deliverability testing before important auth, billing, or lifecycle launches
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:
