MoeMail
ブログに戻る

Cypress によるメールテスト:E2E テストでメールを検証する

Cypress はブラウザを操作するのが得意ですが、認証メールはその外側にあります。本ガイドでは、使い捨て受信トレイを払い出して API 経由で読むカスタムコマンドを用意し、Cypress でのメールテストを実現します。開発者のためのメールテストガイドの一部で、Playwright 版や Selenium 版と並ぶ内容です。

アプローチ

受信トレイをテストごとにひとつ作り、フローを進め、受信トレイ API をメッセージが届くまでポーリングします。これをカスタムコマンドに包めば、spec がきれいに読めます。

カスタムコマンド

// cypress/support/commands.js
Cypress.Commands.add('createInbox', () => {
  return cy.request({
    method: 'POST',
    url: API + '/emails/generate',
    headers: { 'X-API-Key': Cypress.env('MAIL_KEY') },
  }).its('body')
})

Cypress.Commands.add('waitForEmail', (id, tries = 20) => {
  const poll = (n) => cy.request({
    url: API + '/emails/' + id + '/messages',
    headers: { 'X-API-Key': Cypress.env('MAIL_KEY') },
  }).then((res) => {
    if (res.body.messages.length) return res.body.messages[0]
    if (n === 0) throw new Error('no email arrived')
    cy.wait(1500)
    return poll(n - 1)
  })
  return poll(tries)
})

テスト本体

it('verifies the signup email', () => {
  cy.createInbox().then((inbox) => {
    cy.visit('/signup')
    cy.get('#email').type(inbox.address)
    cy.contains('Sign up').click()
    cy.waitForEmail(inbox.id).then((msg) => {
      const code = msg.text.match(/[0-9]{6}/)[0]
      cy.get('#code').type(code)
    })
    cy.contains('Welcome').should('be.visible')
  })
})

再帰的な poll は、固定タイマーの場当たり的な cy.wait に頼らずに Cypress のコマンドキューと折り合いをつけます。同じコマンドはパスワードリセットメールのテストにも使えます。コードの代わりにリンクをマッチさせるだけです。

ヒント

テストごとに固有のアドレスを与え、可能なときは webhook を優先し、CI のタイムアウトは短めに保ちましょう。メールボックスを作成し、OpenAPI ドキュメントを読んでつなぎ込んでみてください。