MoeMail
返回部落格

Playwright Email 測試:端對端教學

這篇教學示範如何在 Playwright 裡端對端測試一個真實的 email 流程——完全不靠 mock。我們替每個測試開一個拋棄式收件匣,跑完註冊,再透過 API 讀驗證信並取出代碼。它建立在拋棄式 email API 之上,也是我們開發者 email 測試指南的一部分。

做法

每個測試開一個收件匣(不是每個測試套件一個——共用又會把舊狀態問題帶回來)。在瀏覽器裡觸發流程,輪詢收件匣 API 抓訊息,用簡單的比對取出代碼,再把它填回頁面。

一個可重用的 fixture

把收件匣相關呼叫包進一個 Playwright fixture,這樣每個測試都會自動拿到一個新地址,測試本體也能專注在行為、而不是傳輸細節:

// inbox.ts — talks to your MoeMail-style API
export async function createInbox() {
  const res = await fetch(API + '/emails/generate', {
    method: 'POST',
    headers: { 'X-API-Key': process.env.MAIL_KEY },
  })
  return res.json() // { id, address }
}

export async function waitForMessage(id, timeoutMs) {
  const deadline = Date.now() + (timeoutMs || 30000)
  while (Date.now() < deadline) {
    const res = await fetch(API + '/emails/' + id + '/messages', {
      headers: { 'X-API-Key': process.env.MAIL_KEY },
    })
    const list = await res.json()
    if (list.messages && list.messages.length) return list.messages[0]
    await new Promise(r => setTimeout(r, 1500))
  }
  throw new Error('no email arrived in time')
}

測試本體

import { test, expect } from '@playwright/test'
import { createInbox, waitForMessage } from './inbox'

test('verifies signup email', async ({ page }) => {
  const inbox = await createInbox()
  await page.goto('/signup')
  await page.getByLabel('Email').fill(inbox.address)
  await page.getByRole('button', { name: 'Sign up' }).click()

  const msg = await waitForMessage(inbox.id, 30000)
  const code = msg.text.match(/[0-9]{6}/)[0]

  await page.getByLabel('Verification code').fill(code)
  await expect(page.getByText('Welcome')).toBeVisible()
})

注意 Date.now() 守住了輪詢迴圈,而比對抓的是一組六位數代碼;若要改抓連結,就比對 URL 再對它呼叫 page.goto()

讓它更不容易閃爍

能用 webhook 就別用輪詢,可以省掉延遲;給每次執行一個獨一無二的地址,平行測試才不會撞在一起;CI 逾時也要設緊一點。同一套寫法也能涵蓋 OTP 與 2FA 登入,而同一個 fixture 在 CypressSelenium 裡照樣能用。

開始動手

到個人頁面拿一把 API 金鑰,讀讀 OpenAPI 文件,把上面的 fixture 丟進你的測試套件就行。第一次接觸這套想法?先從開發者 email 測試看起。