概要
以前、投稿した記事「CodeceptJSで受信メールの検証、MailSlurp vs gmail お薦めはMailSlurp」に利用できるメールサーバを1つ追加という話。
まず簡単に、前回紹介したものも含めて比較してみます。
メール受信 | 必要なインストール | 受信手段 | 利用するメールサーバ | 特記事項 |
---|---|---|---|---|
MailTrap | 特になし | APIを参考に自作したLastMail受信メソッド | MailTrap | 1つのinboxに50通まで。その後は古いメールから削除される。無料だと500通/月まで |
MailSlurp | MailSlurp-client | メールがこちらに送信された後のLastMailメソッド | MailSlurp | 利用頻度によっては有料プランが必要。無料だと100通/月まで |
gmail | inboxおよびmailParserおよびiconv | メール待受け中の着信に反応 | gmail | IMAPが必須。gmailの外からのアプリケーションアクセスの許可が必須 |
参考にした資料: |
専用のメール・クライアントが用意されている「MailSlurp」
APIを参考に自前でゴリゴリ受信メソッドを書く「MailTrap」
一言で言うと、そんな感じです。
MailSlurpとMailTrapの有料プランを比較してみました。参考まで。
MailSlurp
MailTrap
MailTrapは9ドルからあるみたいです。あと、inboxの数に違いがありますが、メールアドレスにエイリアスを使わなければ、inboxの数 = テストに使えるメールアドレスの数、と考えると、同じくらいの料金ではMailSlurpの方が多いように見えます。
また、MailSlurp-clientでは、開発言語についてjavaScript のみのようですが、MailTrapでは、cURLといったCUI、Ruby / Python / PHP / Java / Perl / C# についてもスニペットがありますので、自作に当たっての参考になればと思います。
メール受信して何を自動テストするか
メールを受信して、本文に含まれるキーワードを使ってWebUIの検証をするという点は前回の投稿と同等ですが、メールのSubjectについて検証対象に追加しました。
Selenium学習サイトを使って、以下のメール本文
[会員ランク]
[会員氏名]様。
この度は当ホテルに会員登録いただき、誠にありがとうございました。
登録手続きが完了いたしましたので、ご連絡申し上げます。
ユーザーID:[メールアドレス]
パスワードは登録時に設定頂いたものをお使い頂けます。
ご登録頂いた内容につきましては、以下のアドレスにてご確認頂けます。
[URL]
それでは、ご利用を心からお待ち申し上げております。
[トップページのURL]
からメールアドレスと控えておいたパスワードを使って、会員情報へのログインした後、IDと会員ランクと氏名とを評価します。
今回も、テストデータとして以下を準備して臨みました。
- 登録済みの4名を使ったテスト(会員登録完了のご連絡001 / 会員登録完了のご連絡006 / 会員登録完了のご連絡007/ 会員登録完了のご連絡008)
- リンク先URLが間違っているケース(会員登録完了のご連絡002)
- 会員種別が間違っているケース(会員登録完了のご連絡003)
- メール文言の一部が間違っているケース(会員登録完了のご連絡004)
- 会員氏名が間違っているケース(会員登録完了のご連絡005)
メール本文さえ取得できれば、後はURLっぽいのを抽出したり、メールアドレスっぽいのを抽出したりは、JavaScriptのmatch関数で正規表現を用いて行ないます。
.waitForLatestEmail だけでメール本文を取得できる点でMailSlurpは、とても簡単です。
今回紹介するMailTrapの場合、API仕様に従ったhttpsリクエストを投げて返信をもらうといった実装は、自前で行なう必要があります。
メールに記載のURLに移動したり、ログインをしたりは、CodeceptJSを利用します。
MailTrapを利用した場合の自動テスト環境設定について
事前にMailTrapのサイトにアカウントを作成しログイン後、Api-Tokenを取得しておきます。
また、IDおよびパスワードも画面に表示されたものを使いますので控えておきます。
inboxid(受信箱のIDみたいなもの)は、APIのドキュメントページにてApi-Tokenを使って取得します。
このAPIドキュメントですが、実際にhttpsでリクエストを出して、返信を確認することが出来ますので、テストコードを実装する際、挙動を確認しながらの作業ができます。
受信するinboxのメールアドレスは、
InboxのEmail Addressタブに表示されたものを利用します。
codecept.conf.jsですが、helperにcodeceptjs-chai、plugINにAllureレポートを追加しただけで、MailTrapに関するものは、何も追加しません。
メール送受信に関するコードは、
var nodemailer = require('nodemailer');
module.exports.SendMail = async function(rank, subject, simei, id, url, callback_func){
var address = 'ここにMailTrapの受信ボックスアドレス';
var nodemailer = require('nodemailer');
var smtpConfig = {
port: 2525,
host: 'smtp.mailtrap.io',
auth: {
user: 'ここにMailTrapのID',
pass: 'ここにMailTrapのパスワード'
}
};
const transporter = nodemailer.createTransport(smtpConfig);
var body01 = '様。\r\n';
var body02 = 'この度は当ホテルに会員登録いただき、誠にありがとうございました。\r\n';
var body03 = '登録手続きが完了いたしましたので、ご連絡申し上げます。\r\n';
var body04 = 'ユーザーID:';
var body05 = 'パスワードは登録時に設定頂いたものをお使い頂けます。\r\n';
var body06 = 'ご登録頂いた内容につきましては、以下のアドレスにてご確認頂けます。\r\n';
var body07 = 'それでは、ご利用を心からお待ち申し上げております。\r\nhttps://hotel.testplanisphere.dev/ja/index.html';
var body = rank + '\r\n' + simei + body01 + body02 + body03 + body04 + id + '\r\n' + body05 + body06 + url + '\r\n' + body07;
let info = await transporter.sendMail({
from: 'ここは送信元のアドレス',
to: address,
subject: subject,
text: body
}, function(e, res){
console.log(e ? e.message : res.message);
smtp.close();
});
return await info;
};
module.exports.lastMailId = async function(){
var result;
var https = require('https');
var url = 'https://mailtrap.io/api/v1/inboxes/"ここにinboxid"/messages';
let options = {
headers: {
'Api-Token': 'ここにApi-Token'
}
}
var data = [];
let promise = await new Promise(async(resolve, reject) => {
await https.get(url, options, async function (res) {
await res.on('data', function(chunk) {
data.push(chunk);
}).on('end', function() {
var events = Buffer.concat(data);
var r = JSON.parse(events);
var matchData = r.filter(function(item, index){
if(item.id != '0') return true;
});
resolve(r[0].id);
// console.log(r[0].id);
});
});
}).then((lastId) => {
result = lastId;
});
return await result;
}
module.exports.receiveSubject = async function(){
var result;
var https = require('https');
var url = 'https://mailtrap.io/api/v1/inboxes/"ここにinboxid"/messages';
let options = {
headers: {
'Api-Token': 'ここにApi-Token'
}
}
var data = [];
let promise = await new Promise(async(resolve, reject) => {
await https.get(url, options, async function (res) {
await res.on('data', function(chunk) {
data.push(chunk);
}).on('end', function() {
var events = Buffer.concat(data);
var r = JSON.parse(events);
var matchData = r.filter(function(item, index){
if(item.id != '0') return true;
});
resolve(r[0].subject);
// console.log(r[0].id);
});
});
}).then((lastId) => {
result = lastId;
});
return await result;
}
module.exports.receiveMail = async function(mailid){
var result;
var https = require('https');
var url = 'https://mailtrap.io/api/v1/inboxes/"ここにinboxid"/messages/' + mailid + '/body.txt';
let options = {
headers: {
'Api-Token': 'ここにApi-Token'
}
}
var data = [];
let promise = await new Promise((resolve, reject) => {
let client = https.get(url, options, function (res) {
res.setEncoding('utf-8');
res.on('data', function(chunk) {
resolve(chunk);
});
res.on('close', function(){
// console.log('Connection closed');
});
res.on('end', function() {
// console.log('Response data end');
});
res.on('aborted', function() {
console.log('Connection aborted');
});
});
}).then((body) => {
result = body;
});
return await result;
};
Node.jsのhttpsモジュールでApi-Token含めてGETし、返信がJSON形式で返ってくるので、最後に着信したメールのIDを取得し、取得したメールIDの本文をUTF-8でエンコードして取り出しています。
MailSlurp-clientの .waitLastEmailは未読の最終着信を見ているようなので、ここは、もう少し(例えばメールが到着しない場合とか想定して)改良の余地があるかもしれません。
CodeceptJSで使うテストコードは、以下のようになります。
let emailTestTable = new DataTable(['Subject', 'url', 'verifyTitle', 'verifyWord', '会員ランク', '会員氏名', '会員ID', '会員Pass']);
emailTestTable.add(['会員登録完了のご連絡001','https://hotel.testplanisphere.dev/ja/login.html','ログイン | HOTEL PLANISPHERE - テスト自動化練習サイト','ご利用を心からお待ち申し上げております。', 'プレミアム会員', '山田一郎', 'ichiro@example.com', 'password']);
emailTestTable.add(['会員登録完了のご連絡002','https://hotel.testplanisphere.dev/ja/index.html','ログイン | HOTEL PLANISPHERE - テスト自動化練習サイト','ご利用を心からお待ち申し上げております。', 'プレミアム会員', '山田一郎', 'ichiro@example.com', 'password']);
emailTestTable.add(['会員登録完了のご連絡003','https://hotel.testplanisphere.dev/ja/login.html','ログイン | HOTEL PLANISPHERE - テスト自動化練習サイト','ご利用を心からお待ち申し上げております。', '一般会員', '山田一郎', 'ichiro@example.com', 'password']);
emailTestTable.add(['会員登録完了のご連絡004','https://hotel.testplanisphere.dev/ja/login.html','ログイン | HOTEL PLANISPHERE - テスト自動化練習サイト','ご利用を心からお待ち申し上げます。', 'プレミアム会員', '山田一郎', 'ichiro@example.com', 'password']);
emailTestTable.add(['会員登録完了のご連絡005','https://hotel.testplanisphere.dev/ja/login.html','ログイン | HOTEL PLANISPHERE - テスト自動化練習サイト','ご利用を心からお待ち申し上げております。', 'プレミアム会員', '山田一朗', 'ichiro@example.com', 'password']);
emailTestTable.add(['会員登録完了のご連絡006','https://hotel.testplanisphere.dev/ja/login.html','ログイン | HOTEL PLANISPHERE - テスト自動化練習サイト','ご利用を心からお待ち申し上げております。', '一般会員', '松本さくら', 'sakura@example.com', 'pass1234']);
emailTestTable.add(['会員登録完了のご連絡007','https://hotel.testplanisphere.dev/ja/login.html','ログイン | HOTEL PLANISPHERE - テスト自動化練習サイト','ご利用を心からお待ち申し上げております。', 'プレミアム会員', '林潤', 'jun@example.com', 'pa55w0rd!']);
emailTestTable.add(['会員登録完了のご連絡008','https://hotel.testplanisphere.dev/ja/login.html','ログイン | HOTEL PLANISPHERE - テスト自動化練習サイト','ご利用を心からお待ち申し上げております。', '一般会員', '木村良樹', 'yoshiki@example.com', 'pass-pass']);
Feature('emailTest');
Data(emailTestTable).Scenario('eMail_Test', async ({I , current}) => {
var mailBox = require('../emailMailTrapCommon');
//テスト用メール送信
var sendMail = await mailBox.SendMail(current.会員ランク,current.Subject,current.会員氏名,current.会員ID,current.url,null);
await sleep(10000);
// I.wait(30);
//ここまで
var lastid = await mailBox.lastMailId();
var sentSubject = await mailBox.receiveSubject(lastid);
var sentBody = await mailBox.receiveMail(lastid);
console.log(sentSubject);
console.log(sentBody);
//Subjectの検証
I.assertContain(sentSubject, current.Subject);
//文面の文言検証
I.assertContain(sentBody, current.verifyWord);
const memberid = await sentBody.match(/[A-Za-z0-9_.-]*@example.com/)[0];
const url = await sentBody.match(/(https?|ftp)(:\/\/[\w\/:%#\$&\?\(\)~\.=\+\-]+)/g)[0];
//文面から取得したURLに移動
await I.amOnPage(url);
//移動先の検証、ここではタイトル
I.seeTitleEquals(current.verifyTitle);
//文面から取得したアカウントでログイン
I.fillField('email', memberid);
I.fillField('password', current.会員Pass);
I.waitForClickable('#login-button');
I.click('#login-button');
//文面から移動した会員ページの検証
I.see(current.会員ID);
I.see(current.会員氏名);
I.see(current.会員ランク);
I.click('#logout-form > button');
});
function sleep(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, time);
});
}
前述した意図的にエラーを混入させたテストデータを含めて、8件のテストが実行されます。
Subjectの検証ですが、テストデータに含まれるSubjectを、そのままメール送信しているので、本件ではメール受信した後の検証でエラーにはならず、会員ページに記載の氏名と異なっていると検出されています。実際のメール送信を含むシステムテストでは、期待されるSubject(例えば予約日付が入ったり)を含むテストデータを準備してテストに臨むことになります。
メールテスト中は、I.wait()が効いていなさそうなので、別途 sleep を実装して使っています。
テスト結果の確認
メールボックスには、テスト用のメールが着信していることが確認できます。
余談ですが、今回helperとしてplayWrightを用いて、テスト中の動画撮影機能を有効化してみました。
撮影された動画は、エラーを混入したテストデータを使った場合のみ、個別にクリップとして記録されているようです。Allureのレポートに表示されるスクリーンショットと併せて、動画も記録として残るようなので(platWrightの場合だけです。また、webm形式で記録されます。)報告資料として添付する等の用途があるかと思います。
総括
前回のMailSlurpに続いてMailTrapについてもメール受信のテストで使えますので、メール受信が関係するWebUIの自動テストの事例紹介とします。
紹介した事例にあるように、メールのSubjectと本文が、間違っていないかのテストが主旨となります。仕様との差異を間違い探しするようなことです。( 一郎 と 一朗 みたいな)
単に間違い探しであれば、あまり予算や時間をかけずにできる点で、MailSlurpやMailTrapを利用して、CodeceptJSと併用するのは、良い選択だと考えます。
ぜひ、やってみてください。