メール OTP と TOTP は分けて考えます

メール OTP は、メールで届く使い捨ての認証コードです。Playwright のメール認証自動化では、コードの取得だけでなく、入力後に認証が成立したことまで確認します。

方法コード・状態の取得元検証する範囲本記事での扱い
メール OTPテスト用メールボックスメール送信から受信、入力、ログインまで対象です
TOTP認証アプリなど時間ベースのコードによる認証生成手順は扱いません
認証済み状態の再利用保存したログイン状態ログイン後の機能メール認証経路の代用にはしません

Mailera の仕組みは、既存のメールボックスを API につなぐものです。接続構成の全体像は既存メールプロバイダーを接続するガイド、取得の基本は自分の受信箱から OTP を読む手順を参照してください。

Playwright の送信要求から、既存メールボックスでの受信、API 取得、コード入力、ログイン成功確認までの流れ
メールの取得とログイン成功の判定は別の処理です。配送と同期を待つ期限も設けます。

テスト前の準備

  1. 自分が管理するアプリと、テスト専用メールボックスを用意します。通常業務のメールが届く個人用受信箱は使いません。
  2. プロバイダー別の手順に沿ってテスト用メールボックスを接続します。Outlook・Microsoft 365 の接続条件は、Microsoft の接続手順とMicrosoft 365 の Graph 設定チェックリストで確認してください。
  3. 「設定」の「API key」タブから「キーを作成」を開き、「メールボックスのスコープ」をテスト用グループに限定します。読み取り権限(read)を選び、「有効期限」も設定します。キーは一度しか表示されないため、その場で CI のシークレットに保存します。

Gmail の接続は Gmail の接続ガイドを使ってください。Mailera ではアプリパスワード(app password)を使います。Google のアプリパスワードの説明では、利用条件として 2 段階認証の有効化が示されています。

テスト専用グループへの範囲限定と読み取り権限を設定する Mailera の API キー作成画面
OTP の取得には read 権限を使い、テスト対象以外のメールボックスをキーの範囲から外します。

接続後は GET /mailboxes を呼び、data 内の対象メールボックスの status と last_sync_at を確認します。Mailera の新着確認の頻度はメールボックスの状態によって変わります。リアルタイム監視に対応する場合は使用中に数秒以内で新着を検知し、対応しない場合は使用中に 2 分ごとに確認します。未使用のメールボックスはスリープする場合があります。送信直後に読めることを前提にせず、待機処理を組み込みます。

Playwright からコードを取得して入力する

GET /mailboxes/{id}/otp は最新の OTP を取得する read 権限の API です。from、subject、since_minutes で対象を絞れます。ただし、最新であることと、今回のログイン要求に対応することは別です。

以下の例では、コードだけでなく受信日時と件名を確認し、候補が複数あれば失敗させるため、GET /messages/search に extract=otp を指定します。検索 API には mailbox、from、since を渡し、返された各メッセージの subject を TypeScript 側で OTP_SUBJECT と完全一致で比較します。検索 API に subject パラメーターは渡しません。コードは data 内の各メッセージの otp.code から読みます。

最新コードの取得には /mailboxes/{id}/otp、メッセージ情報を使って候補を選別するこのテストには /messages/search を使う、という使い分けです。API の入口は 開発者向けページ、各操作は API ドキュメントにあります。

送信前に環境変数を設定する

@playwright/test が導入済みの既存の TypeScript テストプロジェクトで、次の値を環境変数に設定します。

変数内容
MAILERA_API_KEYテスト用グループに限定した read キー
MAILBOX_ID接続したテスト用メールボックスの ID
TEST_APP_URL自分が管理するアプリのログイン画面 URL
TEST_EMAILそのアプリに登録したテスト用メールアドレス
OTP_FROM認証メールの送信元アドレス
OTP_SUBJECT認証メールの件名全文

期限付きの取得ヘルパーを用意する

次を既存のテストプロジェクト内の otp-helper.ts に保存します。待機期限の 60 秒と確認間隔の 3 秒は、この例の設定値であり、到着時間の保証ではありません。リアルタイム監視に対応しないメールボックスでは確認間隔が 2 分のため、アプリ側のコード有効期限も踏まえて待機期限を調整してください。候補が複数ある場合は、コードを推測して選ばず失敗させます。

import type { APIRequestContext } from '@playwright/test';
export async function waitOtp(api: APIRequestContext, started: number) {
  const deadline = Date.now() + 60_000;
  while (Date.now() < deadline) {
    const res = await api.get('https://mailera.app/api/public/v1/messages/search', { timeout: Math.min(5000, deadline - Date.now()), params: {
      mailbox: process.env.MAILBOX_ID!, from: process.env.OTP_FROM!, since: new Date(started).toISOString(),
      extract: 'otp', limit: 20 } });
    if (!res.ok()) throw new Error(`Mailera HTTP ${res.status()}`);
    const { data } = await res.json();
    const matches = data.filter((m: { subject: string; received_at: string; otp?: { code: string } }) => m.subject === process.env.OTP_SUBJECT && Date.parse(m.received_at) >= started);
    if (matches.length > 1) throw new Error('認証メールの候補が複数あります');
    if (matches[0]?.otp?.code) return matches[0].otp.code;
    await new Promise(resolve => setTimeout(resolve, Math.min(3000, Math.max(0, deadline - Date.now()))));
  } throw new Error('認証メールの待機期限に達しました');
}

候補がなければ再確認します。HTTP エラーや通信エラーは未着として扱わず、その時点で終了します。コードやレスポンス本文はエラーメッセージに含めません。

既存のログインテストに組み込む

ヘルパーは単独で実行せず、既存の Playwright テストから呼び出します。画面要素は対象アプリに合わせ、次の順序で処理します。

  1. Mailera 専用の APIRequestContext を作成します。baseURL は https://mailera.app/api/public/v1、認証ヘッダーは Authorization: Bearer $MAILERA_API_KEY とし、環境変数の実際の値を渡します。上のヘルパーは取得先を完全な URL で指定しているため、baseURL の末尾の / に依存しません。認証ヘッダーを対象アプリへの通信に混在させません。
  2. ログイン画面を開いてテスト用メールアドレスを入力します。コード送信を要求する直前に Date.now() を記録し、started として保持します。
  3. 送信要求後に waitOtp(api, started) を呼び、返されたコードを認証コード欄へ入力します。テスト全体の制限時間は、ヘルパーの待機時間に画面操作と成功確認の時間を加えて設定します。
  4. ログイン操作を行い、認証後だけ表示される要素で成功を検証します。コードを取得できたことだけでテストを成功させません。
  5. 成否にかかわらず、finally で Mailera 専用コンテキストの dispose() を呼びます。

このテストは自動再試行なしで扱い、同じメールボックスを別の CI ジョブでも同時に使わないでください。

古いコードや別テストのコードを避ける

直列実行だけでは、前回のメールを除外できません。since_minutes も相対的な時間範囲であり、ログイン要求の識別子ではありません。

上の例は送信要求の直前の時刻、送信元、件名を使います。それでも、前回要求したメールが遅れて届けば候補に入る余地があります。厳密に対応付けるなら、テスト対象アプリ側で実行ごとの識別子を件名などに入れ、OTP_SUBJECT にその件名を渡す設計にします。

調査には GET /mailboxes/{id}/messages の received_at と id、必要に応じて GET /mailboxes/{id}/messages/{msgId} の本文を使います。件名は subject、送信元は from.address を確認し、今回の要求に対応するメールか照合してください。候補が多い場合は next_cursor による続きを確認する設計も必要です。上の短い例は、専用メールボックスで候補を少なく保つ前提です。

メールが届かない・認証に失敗する場合の確認

未着時は、まず GET /mailboxes/{id}/status、次に GET /mailboxes/{id}/placement、最後に受信トレイと迷惑メールの実際のメッセージを確認します。status は Microsoft ではサインインを直接確認し、ほかのプロバイダーでは最新の同期に基づく状態を返します。

症状確認箇所次の操作
メールボックスに接続できないstatus と接続情報接続を復旧してから再実行します
アプリは送信したが見つからないplacement と迷惑メール実際の保存先を確認します
メールはあるが候補にならない送信元、件名、受信日時実際のメールと条件を照合します
コードを入力しても失敗するアプリ側の有効期限・再送仕様期限切れや旧コードの無効化を調べます
待機期限に達する接続状態と同期状態診断情報を残して失敗させます

Gmail でアカウントのパスワードを変更した場合は、Google が説明するアプリパスワードの失効も確認してください。

明示的な同期には POST /mailboxes/{id}/sync と sync 権限が必要です。通常の read キーに無条件で追加せず、接続準備の操作として分けます。未着のたびにコードを再送するループも作りません。

Mailera は即時到着や受信トレイへの振り分けを保証しません。「迷惑メールではない」で移動しても、次回の振り分けを決めるのはプロバイダーです。また、コードの有効性は対象アプリが判定します。API でコードを取得できただけでは、ログインテストは成功していません。

CI で API キーと認証コードを漏らさないための設定

キーは CI のシークレットからテストプロセスに渡し、ソースコードやブラウザー側に埋め込みません。GitHub Actions を使う場合は、公式のシークレット利用手順に沿って、secrets コンテキストから環境変数へ渡します。有効期限と失効担当を決めておきます。Mailera のキーを失効すると、そのキーを使う API 呼び出しは直ちに拒否されます。

メール OTP のテストでは、スクリーンショット、動画、トレースを無効にしてください。カスタムレポーターや HTTP デバッグログも別途確認し、認証ヘッダー、コード入力値、メール本文を出力しない設定にします。診断用の画像を残す場合は秘密情報を伏せ、保存先と閲覧権限を限定します。

メール本文は信頼できない入力として扱います。必要なコード以外を実行せず、本文に書かれた任意の指示やリンクにも従いません。運用上の確認には Mailera のセキュリティ情報も参照してください。

APIで同じ操作を実行

この記事の操作はすべてREST APIで実行できます。curlの例、API key、webhookも利用できます。

APIドキュメントを見る