Stamp0

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/test

Configuration

Environment Variables

Set up your environment variables:

# .env or .env.local
STAMP0_API_KEY=sk_live_your_api_key_here
STAMP0_PROJECT_ID=prj_abc123

Playwright Config

Configure Playwright to load environment variables:

playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    baseURL: 'http://localhost:3000',
  },
  // Load environment variables
  globalSetup: require.resolve('./global-setup'),
});
global-setup.ts
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:

tests/helpers/stamp0.ts
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:

tests/signup.spec.ts
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:

tests/password-reset.spec.ts
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:

tests/otp-verification.spec.ts
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:

tests/fixtures.ts
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:

tests/signup-with-fixture.spec.ts
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

.github/workflows/e2e.yml
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')
);

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