블로그로 돌아가기
QA Playwright Email Testing Automation

Playwright에서 이메일 인증 테스트 방법 (2026 완전 가이드)

· 8분 읽기

End-to-end tests that involve email verification are notoriously fragile. You click "Register," an email gets sent, and now your test has to somehow read that email, extract a 6-digit code, and type it into a form — all in an automated, reproducible way. If you've tried to solve this before, you know the pain.

According to the 2024 World Quality Report by Capgemini, 41% of QA teams cite end-to-end test automation as their top challenge — with email verification flows ranked among the most frequently skipped tests in CI pipelines.

Source: Capgemini / Sogeti, World Quality Report 2024

Teams that skip email verification tests catch authentication regressions in production, where fixes cost 6× more.

전통적인 접근 방식의 문제

Most teams encounter one of these dead ends when testing email flows:

  • Shared test accounts (Gmail): Works locally, breaks in CI because Google detects bot logins. You also get OTP codes mixed across parallel test runs.
  • Mailinator free plan: Public inboxes with no API access on the free tier. Any bot can read your test emails. Some services (including Amazon and Stripe) actively block Mailinator domains.
  • Self-hosted MailHog / MailDev: Requires SMTP configuration changes in your app, doesn't work for third-party auth flows (OAuth providers send real emails), and adds infra overhead.
  • Mocking the email entirely: You miss real template rendering bugs, broken links, and character encoding issues that only appear in actual emails.
  • Ephemeral email services: No stable API, rate-limited, and domains change unpredictably — a recipe for flaky tests.

What you actually need is a private, API-accessible mailbox that you own, can create programmatically, and can poll reliably in test code.

A 2023 Sauce Labs testing survey found that 62% of development teams experience at least one CI/CD pipeline failure per week caused by flaky tests — email-dependent tests being disproportionately represented.

Source: Sauce Labs, State of Digital Quality Report 2023

섹션 1: 테스트를 위한 실제 메일박스 API 사용

The cleanest solution is to provision dedicated test mailboxes through an API before each test suite runs, use them during the test, and optionally clean them up afterward. GridInbox provides exactly this: a REST API to create mailboxes, list messages, and extract parsed OTP codes from incoming emails.

The key advantages over alternatives:

  • Each test gets its own isolated inbox — no cross-contamination between parallel runs
  • Inboxes are private (not publicly accessible like Mailinator)
  • OTP codes are parsed server-side and returned in a structured JSON field
  • Works with any email provider — the receiving infrastructure is real SMTP/SES
  • Custom domains are supported — use your own @test.yourdomain.com

섹션 2: 완전한 Playwright 예시

Let's walk through a full end-to-end test: register a new user, receive the verification email, extract the OTP, submit it, and assert the account is verified.

1단계: API를 통해 테스트 받은 편지함 생성

// helpers/email.ts
const API_BASE = 'https://api.gridinbox.com/api/v1';
const API_KEY  = process.env.GRIDINBOX_API_KEY!;

export async function createTestInbox(label: string) {
  const res = await fetch(`${API_BASE}/mailboxes`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': API_KEY,
    },
    body: JSON.stringify({ name: label }),
  });
  const data = await res.json();
  // Returns { id, address, name, ... }
  return data as { id: string; address: string };
}

2단계: OTP 이메일 폴링

export async function waitForOtp(
  mailboxId: string,
  options = { timeoutMs: 30_000, intervalMs: 2_000 }
): Promise<string> {
  const deadline = Date.now() + options.timeoutMs;

  while (Date.now() < deadline) {
    const res = await fetch(`${API_BASE}/mailboxes/${mailboxId}/messages?limit=1`, {
      headers: { 'X-API-Key': API_KEY },
    });
    const { messages } = await res.json();

    if (messages?.length > 0) {
      const msg = messages[0];
      // GridInbox parses OTPs server-side
      if (msg.otp) return msg.otp as string;
      // Fallback: regex extraction from subject/text
      const match = (msg.subject + ' ' + msg.text_body).match(/\b(\d{4,8})\b/);
      if (match) return match[1];
    }

    await new Promise(r => setTimeout(r, options.intervalMs));
  }

  throw new Error(`OTP not received within ${options.timeoutMs}ms`);
}

3단계: 전체 Playwright 테스트

// tests/auth/email-verification.spec.ts
import { test, expect } from '@playwright/test';
import { createTestInbox, waitForOtp } from '../helpers/email';

test('user can verify email after registration', async ({ page }) => {
  // 1. Create a unique test inbox for this run
  const inbox = await createTestInbox(`pw-test-${Date.now()}`);
  const testEmail = inbox.address;

  // 2. Register with the test email
  await page.goto('/register');
  await page.fill('[name="email"]', testEmail);
  await page.fill('[name="password"]', 'TestPass123!');
  await page.click('[type="submit"]');

  // 3. Assert we're on the verification step
  await expect(page.locator('text=Check your email')).toBeVisible();

  // 4. Fetch the OTP from GridInbox API
  const otp = await waitForOtp(inbox.id);

  // 5. Fill in the OTP form
  await page.fill('[name="otp"]', otp);
  await page.click('[type="submit"]');

  // 6. Assert success
  await expect(page).toHaveURL('/dashboard');
  await expect(page.locator('text=Welcome')).toBeVisible();
});

섹션 3: CI/CD에서 실행

Add your API key as a repository secret in GitHub Actions and reference it in your workflow:

# .github/workflows/e2e.yml
name: E2E Tests

on: [push, pull_request]

jobs:
  playwright:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npx playwright install --with-deps
      - name: Run E2E tests
        env:
          GRIDINBOX_API_KEY: ${{ secrets.GRIDINBOX_API_KEY }}
        run: npx playwright test

Because each test creates its own isolated inbox, parallel test execution works perfectly. Playwright's built-in sharding (--shard=1/4) will distribute tests across runners without any inbox collisions.

섹션 4: 모범 사례

"The single most reliable pattern for email-dependent E2E tests is owning the inbox. Any solution that involves shared accounts, public disposable addresses, or mocking the SMTP layer will eventually create flaky tests that erode team confidence in the entire test suite."

Gleb Bahmutov, VP of Engineering, Cypress.io
  • Name inboxes semantically: Use a prefix like pw-{testName}-{timestamp} so you can identify which test a mailbox belongs to when debugging.
  • Set a polling timeout appropriate for your SES delivery speed: AWS SES typically delivers within 2–5 seconds. A 30-second timeout gives plenty of buffer. For slower SMTP relays, increase to 60 seconds.
  • Clean up after yourself: Add a test.afterAll hook that deletes test inboxes via the API. This keeps your GridInbox dashboard clean and avoids storage accumulation.
  • Use fixture-based inbox provisioning: Playwright fixtures let you share a single inbox across multiple tests in a file without re-creating it each time — a good pattern for "login once, test multiple pages" scenarios.
  • Don't hardcode email addresses: Always generate unique addresses per run. Hardcoded addresses lead to race conditions when tests run concurrently in a CI matrix.

결론

Email verification testing doesn't have to be the flaky, manual-intervention-required nightmare that most teams experience. With a real mailbox API, you can make email flows a first-class part of your Playwright test suite — deterministic, parallel-safe, and CI-ready.

The pattern described here — create inbox → trigger email → poll API → extract OTP → assert — works for any email-based flow: verification, password reset, magic links, notification emails, and more.

오늘부터 이메일 플로우 테스트 시작

무료 GridInbox 계정을 얻고 몇 분 안에 API를 통해 격리된 테스트 받은편지함을 만들기 시작하세요. 신용카드 불필요.

무료 계정 만들기 →