Fetching values from email in protractor test case

This is something I've solved recently. Hope the solution would also apply for your use-case.

Prerequisites:

  • mail-listener2 package
  • understanding of the concept of promises

Step by step instructions:

  1. Install mail-listener2:

    npm install mail-listener2 --save-dev
    
  2. In your protractor config initialize Mail Listener and make it available globally:

    onPrepare: function () {
        var MailListener = require("mail-listener2");
    
        // here goes your email connection configuration
        var mailListener = new MailListener({
            username: "imap-username",
            password: "imap-password",
            host: "imap-host",
            port: 993, // imap port 
            tls: true,
            tlsOptions: { rejectUnauthorized: false },
            mailbox: "INBOX", // mailbox to monitor 
            searchFilter: ["UNSEEN", "FLAGGED"], // the search filter being used after an IDLE notification has been retrieved 
            markSeen: true, // all fetched email willbe marked as seen and not fetched next time 
            fetchUnreadOnStart: true, // use it only if you want to get all unread email on lib start. Default is `false`, 
            mailParserOptions: {streamAttachments: true}, // options to be passed to mailParser lib. 
            attachments: true, // download attachments as they are encountered to the project directory 
            attachmentOptions: { directory: "attachments/" } // specify a download directory for attachments 
        });
    
        mailListener.start();
    
        mailListener.on("server:connected", function(){
            console.log("Mail listener initialized");
        });
    
        global.mailListener = mailListener;
    }),
    
    onCleanUp: function () {
        mailListener.stop();
    }, 
    
  3. Create a helper getLastEmail() function which would wait for an email to be retrieved:

    function getLastEmail() {
        var deferred = protractor.promise.defer();
        console.log("Waiting for an email...");
    
        mailListener.on("mail", function(mail){
            deferred.fulfill(mail);
        });
        return deferred.promise;
    };
    
  4. Example test case:

    describe("Sample test case", function () {
    
        beforeEach(function () {
            browser.get("/#login");
            browser.waitForAngular();
        });
    
        it("should login with a registration code sent to an email", function () {
            element(by.id("username")).sendKeys("MyUserName");
            element(by.id("password")).sendKeys("MyPassword");
            element(by.id("loginButton")).click();
    
            browser.controlFlow().await(getLastEmail()).then(function (email) {
                expect(email.subject).toEqual("New Registration Code");
                expect(email.headers.to).toEqual("[email protected]");
    
                // extract registration code from the email message
                var pattern = /Registration code is: (\w+)/g;
                var regCode = pattern.exec(email.text)[1];
    
                console.log(regCode);
    
             });
        });
    });
    

The solution I implemented was using mailcatcher API, if you scroll down a bit you'll find the following about the API:

A fairly RESTful URL schema means you can download a list of messages in JSON from /messages, each message's metadata with /messages/:id.json, and then the pertinent parts with /messages/:id.html and /messages/:id.plain for the default HTML and plain text version, /messages/:id/:cid for individual attachments by CID, or the whole message with /messages/:id.source.

So we first fetched the whole json response, parse it and fetch the latest email id:

// Returns the last email id
function(emails, user) {
    var email, recipient;
    for(var i = emails.length - 1; i >= 0; i--) {
        email = emails[i];
        for(var j = 0; j < email.recipients.length ; j++) {
            recipient = email.recipients[j];
            if(recipient == "<"+user+">") {
                return email.id;
            }
        }
    }
};

using that email id we can get the body of the email by hitting /messages/:id.plain(of course there are more variants like fetching the email source code or email rendered html, we only needed the message) then we can just parse the body to fetch what we want, following is the code:

browser.driver.get(mailcatcherUrl+"/messages");
browser.driver.findElement(by.tagName('body')).getText().then(function(response) {
    var emails, lastEmailId, partialTokens ;
    emails = JSON.parse(response);
    lastEmailId = getLastEmailId(emails, user);
    browser.driver.get(mailcatcherUrl+"/messages/"+lastEmailId+".plain");
    browser.driver.findElement(by.tagName('body')).getText().then(function(lastEmail) {
        // use latestEmail to get what you want.
    });
});

And Cheers!