Google Workspace(旧G Suite)の組織に属するGmailのWeb APIを使用することでEmailのテストを作成できます。無料版のGmailは期限のないトークンを取得することが難しいため、Google Workspaceの契約を検討するか、MailSlurpというWebサービスをご利用ください。
目次
1. Emailテストの作成方法
1-1. 「クライアントID」「クライアントシークレット」を取得する
まず最初に、Web APIの認証に必要な「クライアントID」「クライアント シークレット」を取得します。
Google Workspaceに属するテスト用のGoogleアカウント(= Gmailアカウント)を1つ用意してください。Web APIでメール内容を取得可能になってしまうため、普段使っているアカウントとは別のものを用意してください。
続いて、作成したGoogleアカウントでGoogleのDeveloper Consoleにサインインし、画面左上のドロップダウンリストから、好きなプロジェクト名で「プロジェクト」を作成します。
続いて、画面左上メニューの「APIとサービス」>「ダッシュボード」から、「APIとサービスを有効にする」を選択し、GmailのAPIを選んで有効化します。
APIの認証を行うには、先にOAuthを有効にする必要があります。まず、画面左上メニューの「APIとサービス」>「OAuth同意画面」を選択します。Google Workspaceのメールアドレスであれば、「User Type」に「内部」を選択できるので、これを選択します。「外部」でもテスト送信は可能ですが、トークンが数日で切れてしまいます。
Google Workspaceのメールアドレスでない場合、「内部」が選択できません。「外部」を選択してGoogleによる確認を受ければ、Google Workspace以外のメールアドレスでもおそらく期限のないトークンが取得できますが、承認を受けるのは非常に困難だと思います。
次の画面では、適当な「アプリケーション名」「ユーザーサポートメール」「デベロッパーの連絡先情報」を指定し、「保存して次へ」を押してください。
さらに次の画面で「スコープを追加または削除」を押し、「メール メッセージと設定の表示」を選択して「更新」を押してください。これでOAuthが有効になりました。
続いて「認証情報」を作成します。画面左上メニューの「APIとサービス」>「認証情報」から、「認証情報を作成」>「OAuth クライアントID」を選び、「アプリケーションの種類」を「デスクトップアプリ」にして「作成」を押します。
すると、「クライアントID」と「クライアント シークレット」、JSONファイルのダウンロードボタンが画面に表示されるので、JSONファイルを適当なディレクトリに「credentials.json」という名前で保存します。
1-2. 「リフレッシュトークン」を取得する
続いて、こちらもWeb APIの認証に必要な「リフレッシュトークン」を取得します。
Pythonを使用した手順で説明します。(そのほかのAPIもこちらから参照できます)
まず、Pythonがマシンにインストールされていない場合は、こちらの手順に従いインストールします。
次に、Windowsの場合はコマンドプロンプト、Macの場合はターミナルを開き、以下のコマンドを実行します。
pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib
これでGoogle client libraryがダウンロードできました。
次に、マシン上の適当なディレクトリにエディタを起動して空のファイルを作成します。こちらのページの「コードサンプルをコピー」ボタンを押して内容をコピーし、貼り付けます。
作成したファイルを「quickstart.py」というファイル名で保存します。
「quickstart.py」と同じディレクトリに先ほど保存した「credentials.json」を移動し
コマンドラインから以下を実行します。
cd <quickstart.pyのあるディレクトリ>
python quickstart.py
すると下図のようなページが表示されるので「許可」を押しウィンドウを閉じます。
同じディレクトリに「token.json」というファイルが作成され、そこから「クライアントID」「クライアントシークレット」「リフレッシュトークン」を確認することができるようになります。
これでGmail APIを使用するための準備は完了です。「クライアントID」「クライアントシークレット」「リフレッシュトークン」の3つが揃ったので、Magic PodのテストスクリプトからGmailのAPIを呼び出すことができます。
1-3. 「Web APIコール」コマンドで「アクセストークン」を取得する
まず、シークレット共有変数にCLIENT_ID、CLIENT_SECRET、REFRESH_TOKENの3つを定義し、それぞれに先ほど取得した値をセットします。
次に、このjson型のテキストをコピーし、テストケース編集画面に「ctrl + V」(Mac OSの場合は「command + V」)でペーストしてください。するとリフレッシュトークンを元に最新の「アクセストークン」を取得するWeb APIコールのステップが追加されます。
{"command_calls": [{"command": "web_api","val": [{"url": "https://accounts.google.com/o/oauth2/token","method": "post","header": [{"key": "Content-Type","value": "application/x-www-form-urlencoded"}],"body": {"format": "raw","form_data": [],"raw": "client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&refresh_token=${REFRESH_TOKEN}&grant_type=refresh_token"},"result": {"params": [{"key": "ACCESS_TOKEN","value": "jsonResponse[\"access_token\"]"}]}}]}],"project_id": 0,"version": "0"}
これを実行すると、変数ACCESS_TOKENにアクセストークンの値がセットされます (取得したアクセストークンは、30分以内なら何度も使い回すことができます。) 。
もしコピー&ペーストでのステップ追加に失敗する場合は、Web APIコールのステップを追加し、以下項目をセットしてください。
メソッド: POST
URL: https://accounts.google.com/o/oauth2/token
ヘッダー: キーに「Content-Type」、値に「application/x-www-form-urlencoded」
ボディ:「rawデータ」を選択し、内容に「client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&refresh_token=${REFRESH_TOKEN}&grant_type=refresh_token」
結果: 変数に「ACCESS_TOKEN」、Javascriptに「jsonResponse["access_token"]」
1-4. 「Web APIコール」コマンドで「メールID」を取得する
続いて手順1-3で取得したアクセストークンを使い、メールのIDを1件取得します。このjson型のテキストをコピーし、テストケース編集画面に「ctrl + V」(Mac OSの場合は「command + V」)でペーストしてください。すると受信ボックスにある最新のメールを取得するWeb APIコールのステップが追加されます。
{"command_calls": [{"command": "web_api","val": [{"url": "https://www.googleapis.com/gmail/v1/users/me/messages/","method": "get","header": [{"key": "Authorization","value": "Bearer ${ACCESS_TOKEN}"}],"result": {"params": [{"key": "MAIL_ID","value": "jsonResponse[\"messages\"][0][\"id\"]"}]}}]}],"project_id": 0,"version": "0"}
これを実行すると、変数「MAIL_ID」に、<検索条件>にマッチする最新のメールのIDが格納されます。
もしコピー&ペーストでのステップ追加に失敗する場合は、Web APIコールのステップを追加し、以下項目をセットしてください。
メソッド: GET
URL: https://www.googleapis.com/gmail/v1/users/me/messages/
ヘッダー: キーに「Authorization」、値に「Bearer ${ACCESS_TOKEN}」
結果: 変数に「MAIL_ID」、Javascriptに「jsonResponse["messages"][0]["id"]」
Tips: 取得するメールの条件を指定する
URLの末尾に?q=<検索条件>をつけ、「https://www.googleapis.com/gmail/v1/users/me/messages/?q=<検索条件>」とすることでTo・Fromのメールアドレスや件名など検索条件を指定し、特定のメールを取得することができます。
<検索条件>の部分は、Gmailの検索条件の記法がそのまま使えます。例えば、以下のような書き方が可能です。 詳しい記法はこちらを参考にしてください。
- ?q="subject:New user registration" (タイトルに「New user registration」を含むメールを取得)
- ?q="from:no-reply@magic-pod.com" (「no-reply@magic-pod.com」から送信されたメールを取得)
- ?q="to:new_user%2B1890537491%40magic-pod.com" (「new_user+1890537491@magic-pod.com」に送信されたメールを取得する。エイリアスのメールアドレスを使用する場合は+を「%2B」、@を「%40」とURLエンコードで指定する必要があります。)
- ?q=new_user12345678 (本文などに「new_user12345678」を含むメールを取得)
Tips: 最新ではないメールを取得する
Web APIコールの設定画面の「結果」タブのJavascript「jsonResponse["messages"][0]["id"]」の数字部分を変更することで最新ではないメールのIDを取得することが可能です。
- 最新の1件前のメールIDを取得したい:「jsonResponse["messages"][1]["id"]」
- 最新の2件前のメールIDを取得したい:「jsonResponse["messages"][2]["id"]」
1-5. 「Web APIコール」コマンドでメールの本文を取得する
最後に手順1-4で取得したメールIDを元にメール本文を取得します。このjson型のテキストをコピーし、テストケース編集画面に「ctrl + V」(Mac OSの場合は「command + V」)でペーストしてください。すると手順1-4で取得したメールIDを元にメール本文を取得するWeb APIコールのステップが追加されます。
{"command_calls": [{"command": "web_api","val": [{"url": "https://www.googleapis.com/gmail/v1/users/me/messages/${MAIL_ID}","method": "get","header": [{"key": "Authorization","value": "Bearer ${ACCESS_TOKEN}"}],"result": {"params": [{"key": "MAIL_CONTENTS","value": "const inlineMessagePart = function(messagePart) { let messagePartBodies = [{\"mimeType\": messagePart.mimeType, \"body\": messagePart.body}]; if (\"parts\" in messagePart) { for (const subMsgPart of messagePart[\"parts\"]) { messagePartBodies = messagePartBodies.concat(inlineMessagePart(subMsgPart)); } } return messagePartBodies; }; atob([jsonResponse['payload']].map(inlineMessagePart).flat().filter(function(item) { return item.mimeType === \"text/plain\"; })[0]['body']['data']);"}]}}]}],"project_id": 0,"version": "0"}
これで、変数MAIL_CONTENTSにメール本文の内容が保存されます。
メール本文から必要な情報のみを変数に保存する方法については2. テストケースステップの具体例をご確認ください。
もしコピー&ペーストでのステップ追加に失敗する場合は、Web APIコールのステップを追加し、以下項目をセットしてください。
- メソッドに「GET」
- URLに「https://www.googleapis.com/gmail/v1/users/me/messages/${MAIL_ID}」
- ヘッダーに「Authorization」「Bearer ${ACCESS_TOKEN}」
- 結果の変数に「MAIL_CONTENTS」と記述し、結果のJavascriptに以下の内容をコピー&ペーストする (改行文字がなくなりますが、動作上問題はありません)
const inlineMessagePart = function(messagePart) {
let messagePartBodies = [{"mimeType": messagePart.mimeType, "body": messagePart.body}];
if ("parts" in messagePart) {
for (const subMsgPart of messagePart["parts"]) {
messagePartBodies = messagePartBodies.concat(inlineMessagePart(subMsgPart));
}
}
return messagePartBodies;
};
atob([jsonResponse['payload']].map(inlineMessagePart).flat().filter(function(item) { return item.mimeType === "text/plain"; })[0]['body']['data']);
2. テストケースステップの具体例
2-1. メール本文に含まれたURLに遷移する
メールに記載されたURLに遷移することで新規会員登録を行うテストを作成してみましょう。
- テスト対象アプリにて「登録する」ボタンを押す
- 新規登録したメールアドレスにメールが送られる
- メールに記載されたURLをクリックし、会員登録を完了させる
XX様
ご登録ありがとうございます。受信から1時間以内に下記リンクからログインしてください。
https://example.com/qwertyuiop123456789
この場合、以下のテストケースステップでテストできます。
上記テストではステップ11の「正規表現に一致した部分を保存」コマンドで以下の正規表現を指定することで、メール本文からURLのみを取得しています。
https:\/\/example.com\/.+
For mobile apps, the Open deep link command should be used instead of the Navigate to URL command in Step 14 since the Navigate to URL command is not available for mobile.
2-2. メール本文に含まれた認証コードをアプリ内で入力する
メールに記載された認証コードをテスト対象アプリに入力することで新規会員登録を行うテストを作成してみましょう。
- テスト対象アプリにて「登録する」ボタンを押す
- 新規登録したメールアドレスにメールが送られる
- メールに記載された認証コードをアプリに入力し、会員登録を完了させる
XX様
ご登録ありがとうございます。受信から1時間以内にログインし、下記認証コードを入力してください。
認証コード: 123456
この場合、以下のテストケースステップでテストできます。
上記テストではステップ11の「正規表現に一致した部分を保存」コマンドで以下の正規表現を指定することで、メール本文から数字6桁のみを取得しています。
[0-9]{6}
より厳密に「認証コード: 」の後に記載された6桁の数字を取得したい場合は、以下の正規表現を使用すると良いでしょう。
(?<=認証コード: )([0-9]{6})
2-3. ランダムなメールアドレスを使用してテストを行う
Gmailのメール エイリアスを使用することで毎回異なるメールアドレスを使用してテストを行うことができます。「new_user+20240621175024@magic-pod.com」のように「+」の後に値を追記することで元のアドレスに紐付いた新たなメールアドレスを作成できます。
以下の例では「現在時刻を元に生成したユニークな値を保存」コマンドでユニーク値を作成し、「固定値を保存」コマンドで新規会員登録に使うメールアドレスを生成しています。
3. トラブルシューティング
3-1. アクセストークンの取得に失敗する
アクセストークンを取得するWeb APIコールを行った際に、次のような401エラーで失敗することがあります。
Client error. Status code 401 Unauthorized
Response data: {"error":"invalid_client","error_description":"The OAuth client was not found."}
その場合は共有変数CLIENT_ID、CLIENT_SECRET、REFRESH_TOKENのいずれかが誤っている可能性が高いです。次の内容を確認してください。
- 3つの共有変数が正しく定義されているか
- テスト実行時に3つの共有変数が定義されているテスト実行設定を使用しているか
3-2. メールIDの取得に失敗する
Gmail Web APIではメールIDを取得するWeb API実行時に検索条件に合致したメールがない場合、APIレスポンスとして{“resultSizeEstimate”:0}が返ります。この場合、メールIDを取得するWeb APIの実行時にまだメールを受信していないことが原因である可能性が高いです。
以下のようにメール送信のトリガーとなるステップ(「登録する」等)とメールIDを取得する「Web APIコール」ステップの間に「秒間待つ」コマンドを挿入してください。待機秒数については適宜調整ください。
3-3. メール本文の取得に失敗する
メールをプログラムで扱う際、そのデータ構造を意識する必要があります。HTMLメールの場合、そのデータは階層構造になることがあります(単純なテキストメールの場合、階層構造にはならない)。そのため、変数MAIL_CONTENTSの代入に使用するJavascriptは次のような処理を行なっています。
- メールのデータ構造をフラットにする
- MIMEタイプ が text/plain であるメッセージパートを探す。これは、単純なテキストメールからメール本文を取得すること、あるいは、HTMLメールからHTMLメールが見えない環境向けの代替テキストを取得することを意図しています。
- 2のメッセージパートからデータ (メール本文あるいはHTMLメールの代替テキスト) を取得する。そのデータはBase64文字列なので、UTF8に変換する。
そのため、このJavascriptは以下の2つの場合にメール本文の取得に失敗する可能性があります。
テスト対象のメールがHTMLメールかつ代替テキストがない場合
MIMEタイプ が text/plain であるメッセージパートがない場合です。この場合は、上記のJavascriptの「item.mimeType === "text/plain"」を「item.mimeType === "text/html"」に書き換ればメール本文が取得できるようになるはずです。
MIMEタイプ が text/plain であるメッセージパートが複数箇所ある場合
こちらに該当する場合、意図しないメール本文を取得してしまう可能性があります。この場合、お手数をおかけしますがMagicPodサポートにご連絡ください。個別の回避策をご案内します。
参考
メールのデータ構造をざっくり理解するにはこちらが参考になります。また、Gmail Web APIでメールが具体的にどのようなデータ構造で表現されているかはこちらが参考になります。