LoginSignup
2
2

More than 1 year has passed since last update.

受信メールの検証、MailTrap

Posted at

概要

以前、投稿した記事「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
mailslurp011.png
MailTrap
mailTrap007.png
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を取得しておきます。
mailTrap003.png
また、IDおよびパスワードも画面に表示されたものを使いますので控えておきます。
mailTrap004.png

inboxid(受信箱のIDみたいなもの)は、APIのドキュメントページにてApi-Tokenを使って取得します。
mailTrap005.png

このAPIドキュメントですが、実際にhttpsでリクエストを出して、返信を確認することが出来ますので、テストコードを実装する際、挙動を確認しながらの作業ができます。
受信するinboxのメールアドレスは、
mailTrap010.png
InboxのEmail Addressタブに表示されたものを利用します。
codecept.conf.jsですが、helperにcodeceptjs-chai、plugINにAllureレポートを追加しただけで、MailTrapに関するものは、何も追加しません。
メール送受信に関するコードは、

eMailMailTrapCommon.js
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で使うテストコードは、以下のようになります。

eMailTest_test.js
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 を実装して使っています。

テスト結果の確認

メールボックスには、テスト用のメールが着信していることが確認できます。
mailTrap006.png

テストレポートはAllureで表示できます。
mailTrap009.png

余談ですが、今回helperとしてplayWrightを用いて、テスト中の動画撮影機能を有効化してみました。
mailTrap008.png
撮影された動画は、エラーを混入したテストデータを使った場合のみ、個別にクリップとして記録されているようです。Allureのレポートに表示されるスクリーンショットと併せて、動画も記録として残るようなので(platWrightの場合だけです。また、webm形式で記録されます。)報告資料として添付する等の用途があるかと思います。

総括

前回のMailSlurpに続いてMailTrapについてもメール受信のテストで使えますので、メール受信が関係するWebUIの自動テストの事例紹介とします。
紹介した事例にあるように、メールのSubjectと本文が、間違っていないかのテストが主旨となります。仕様との差異を間違い探しするようなことです。( 一郎 と 一朗 みたいな)
単に間違い探しであれば、あまり予算や時間をかけずにできる点で、MailSlurpやMailTrapを利用して、CodeceptJSと併用するのは、良い選択だと考えます。
ぜひ、やってみてください。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2