Playwright
Complete guide to using Stamp0 with Playwright for E2E testing. Test signup flows, password resets, and OTP verification.
This guide shows you how to integrate Stamp0 with Playwright for end-to-end testing of email workflows like user signup, password reset, and OTP verification.
Installation
Install the Stamp0 SDK alongside Playwright:
npm install @stamp0/sdk @playwright/testConfiguration
Environment Variables
Set up your environment variables:
# .env or .env.local
STAMP0_API_KEY=sk_live_your_api_key_here
STAMP0_PROJECT_ID=prj_abc123Playwright Config
Configure Playwright to load environment variables:
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
baseURL: 'http://localhost:3000',
},
// Load environment variables
globalSetup: require.resolve('./global-setup'),
});import dotenv from 'dotenv';
async function globalSetup() {
dotenv.config();
}
export default globalSetup;Basic Usage
Creating a Test Helper
Create a helper to manage Stamp0 inboxes in your tests:
import { Stamp0 } from '@stamp0/sdk';
const stamp0 = new Stamp0({
apiKey: process.env.STAMP0_API_KEY!,
});
export async function createTestInbox() {
return stamp0.createInbox({
projectId: process.env.STAMP0_PROJECT_ID!,
});
}
export async function waitForEmail(inboxId: string, timeout = 30000) {
return stamp0.waitForEmail({
inboxId,
timeout,
});
}
export async function getEmails(inboxId: string) {
return stamp0.getEmails({ inboxId });
}
export { stamp0 };Example: User Signup Flow
Test a complete user signup with email verification:
import { test, expect } from '@playwright/test';
import { createTestInbox, waitForEmail } from './helpers/stamp0';
test('user can sign up and verify email', async ({ page }) => {
// 1. Create a fresh inbox for this test
const inbox = await createTestInbox();
// 2. Fill out the signup form
await page.goto('/signup');
await page.fill('[name="name"]', 'Test User');
await page.fill('[name="email"]', inbox.address);
await page.fill('[name="password"]', 'SecurePassword123!');
await page.click('button[type="submit"]');
// 3. Verify signup success message
await expect(page.locator('.success-message')).toContainText(
'Check your email to verify your account'
);
// 4. Wait for the verification email
const email = await waitForEmail(inbox.id);
// 5. Extract the verification link
const verifyLink = email.body.match(/https?:\/\/[^\s"]+verify[^\s"]*/)?.[0];
expect(verifyLink).toBeTruthy();
// 6. Click the verification link
await page.goto(verifyLink!);
// 7. Verify account is now verified
await expect(page.locator('h1')).toContainText('Email Verified');
});Example: Password Reset Flow
Test the forgot password flow:
import { test, expect } from '@playwright/test';
import { createTestInbox, waitForEmail } from './helpers/stamp0';
test('user can reset password', async ({ page }) => {
// 1. Create inbox and trigger password reset
const inbox = await createTestInbox();
// First, create an account with this email
await page.goto('/signup');
await page.fill('[name="email"]', inbox.address);
await page.fill('[name="password"]', 'OldPassword123!');
await page.click('button[type="submit"]');
// Clear the signup email
await waitForEmail(inbox.id);
// 2. Request password reset
await page.goto('/forgot-password');
await page.fill('[name="email"]', inbox.address);
await page.click('button[type="submit"]');
// 3. Wait for reset email
const resetEmail = await waitForEmail(inbox.id);
// 4. Extract reset link
const resetLink = resetEmail.body.match(/https?:\/\/[^\s"]+reset[^\s"]*/)?.[0];
expect(resetLink).toBeTruthy();
// 5. Go to reset page and set new password
await page.goto(resetLink!);
await page.fill('[name="password"]', 'NewPassword456!');
await page.fill('[name="confirmPassword"]', 'NewPassword456!');
await page.click('button[type="submit"]');
// 6. Verify success
await expect(page.locator('.success-message')).toContainText(
'Password updated successfully'
);
// 7. Verify can login with new password
await page.goto('/login');
await page.fill('[name="email"]', inbox.address);
await page.fill('[name="password"]', 'NewPassword456!');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
});Example: OTP Verification
Test one-time password verification:
import { test, expect } from '@playwright/test';
import { createTestInbox, waitForEmail } from './helpers/stamp0';
test('user can verify with OTP', async ({ page }) => {
const inbox = await createTestInbox();
// 1. Trigger OTP flow (e.g., 2FA setup or login)
await page.goto('/login');
await page.fill('[name="email"]', inbox.address);
await page.fill('[name="password"]', 'Password123!');
await page.click('button[type="submit"]');
// 2. Wait for OTP email
const otpEmail = await waitForEmail(inbox.id);
// 3. Extract 6-digit OTP from email body
const otpMatch = otpEmail.body.match(/\b(\d{6})\b/);
expect(otpMatch).toBeTruthy();
const otp = otpMatch![1];
// 4. Enter OTP
await page.fill('[name="otp"]', otp);
await page.click('button[type="submit"]');
// 5. Verify successful login
await expect(page).toHaveURL('/dashboard');
});Fixtures
Create a Playwright fixture for cleaner tests:
import { test as base } from '@playwright/test';
import { Stamp0 } from '@stamp0/sdk';
type Inbox = Awaited<ReturnType<Stamp0['createInbox']>>;
interface Stamp0Fixtures {
inbox: Inbox;
stamp0: Stamp0;
}
export const test = base.extend<Stamp0Fixtures>({
stamp0: async ({}, use) => {
const stamp0 = new Stamp0({
apiKey: process.env.STAMP0_API_KEY!,
});
await use(stamp0);
},
inbox: async ({ stamp0 }, use) => {
// Create inbox before test
const inbox = await stamp0.createInbox({
projectId: process.env.STAMP0_PROJECT_ID!,
});
await use(inbox);
// Cleanup after test (optional)
// await stamp0.deleteInbox({ inboxId: inbox.id });
},
});
export { expect } from '@playwright/test';Now use the fixture in your tests:
import { test, expect } from './fixtures';
test('signup with fixture', async ({ page, inbox, stamp0 }) => {
await page.goto('/signup');
await page.fill('[name="email"]', inbox.address);
await page.fill('[name="password"]', 'Password123!');
await page.click('button[type="submit"]');
const email = await stamp0.waitForEmail({
inboxId: inbox.id,
timeout: 30000,
});
expect(email.subject).toContain('Verify');
});CI Integration
GitHub Actions
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run E2E tests
run: npx playwright test
env:
STAMP0_API_KEY: ${{ secrets.STAMP0_API_KEY }}
STAMP0_PROJECT_ID: ${{ secrets.STAMP0_PROJECT_ID }}Add STAMP0_API_KEY and STAMP0_PROJECT_ID to your GitHub repository secrets.
Best Practices
1. Create Fresh Inboxes Per Test
Each test should create its own inbox to avoid interference:
test('test 1', async ({ page }) => {
const inbox = await createTestInbox(); // Fresh inbox
// ...
});
test('test 2', async ({ page }) => {
const inbox = await createTestInbox(); // Another fresh inbox
// ...
});2. Use Appropriate Timeouts
Email delivery is fast but not instant. Use reasonable timeouts:
// Default 30 second timeout is usually sufficient
const email = await stamp0.waitForEmail({
inboxId: inbox.id,
timeout: 30000,
});3. Handle Multiple Emails
When testing flows that send multiple emails:
// Get all emails and find the one you need
const emails = await stamp0.getEmails({ inboxId: inbox.id });
const verificationEmail = emails.find(e =>
e.subject.includes('Verify')
);4. Extract Links Safely
Use robust regex patterns for link extraction:
// Extract verification link
const verifyLink = email.body.match(
/https?:\/\/[^\s"<>]+(?:verify|confirm)[^\s"<>]*/i
)?.[0];
// Extract reset link
const resetLink = email.body.match(
/https?:\/\/[^\s"<>]+(?:reset|password)[^\s"<>]*/i
)?.[0];Troubleshooting
Email not arriving?
- Check that the inbox address is correct
- Verify your application is actually sending the email
- Increase the timeout if your email service is slow
- Check your API key has the correct project access
Rate limit errors?
- Create a dedicated project for CI tests
- Use separate API keys for different environments
- Contact support if you need higher limits
Next Steps
- Cypress Guide - Using Stamp0 with Cypress
- Selenium Guide - Using Stamp0 with Selenium
- SDK Reference - Full SDK documentation
- API Keys - Managing API access