Testing Email Delivery in Nightwatch.js End to End test

 日本語

Screenshot

I’ve published an example Nightwatch.js End to End (E2E) testing project that contains Email delivery testing using Mandrill and RequestBin.

ngs/nightwatch-mail-example on GitHub

This example project tests subscribing and unsubscribing Hacker News Letter user flow.

The test code is like this:

page
  .navigate()
  //
  // Create Mandrill Email Route
  .createEmailRoute(email)
  //
  // Fill in form
  .waitForElementVisible("@form")
  .clearValue("@email")
  .setValue("@email", email)
  .click("@submit")
  //
  // Navigate to completion screen
  .waitForElementNotPresent("@form")
  .assert.urlEquals(page.url + "almostfinished.html")
  //
  // Check Email with specified subject
  .assert.receivedEmailSubjectEquals(
    email,
    "Hacker Newsletter: Please Confirm Subscription"
  )
  //
  // Check Email contains specified string in HTML body
  .assert.receivedEmailHTMLBodyContains(
    email,
    '<a class="button" href="https://hackernewsletter.us1.list-manage.com/subscribe/confirm?u='
  );

See full code.

Motivation

We recently introduced Nightwatch.js for End to End testing our product.

I think that is pretty easy and simple to writing test code for our web application.

While writing test, we wanted to test user flow through receiving Emails from our application.

So I tried to implement custom actions and assertions for Nightwatch.js.

ref: Extending Nightwatch - Nightwatch Developer Guide

Getting Started

1. Set up Mandrill Inbound Domain

First you need to set up Inbound Email Domain by following Mandrill Documentation.

Just type your domain and hit the blue + Add button on Inbound Domains screen.

Inbound Domains

After adding test domain, you need add MX record in your DNS records indicated in MX Setup popup.

MX Setup

2. Grab your Mandrill API Key

Next you need to grab your Mandrill API Key from settings screen

API Key

I recommend turning on Test Key checkbox that prevents sending Emails someone mistakenly while testing.

3. (Optional) Launch your RequestBin

Delivered Emails are shared with public users. If you mind sharing your application Emails for them, you can launch your own RequestBin on hosting services like Heroku or IBM Bluexix by deploying Open Sourced RequestBin.

git clone git://github.com/Runscope/requestbin.git
cd requestbin

heroku create
heroku addons:add heroku-redis
heroku config:set REALM=prod

git push heroku master

4. Environment Variables

To launch the example project, export environment variables according to the above steps.

I use direnv to manage local environment variables.

echo "export MANDRILL_API_KEY=${YOUR_API_KEY_HERE}" >> .envrc
echo "export MAIL_DOMAIN=${YOUR_MAIL_DOMAIN_HERE}" >> .envrc

# If you set up your own RequestBin in Step 3
echo "export REQUEST_BIN_HOST=https://${YOUR_HEROKU_APP}.herokuapp.com" >> .envrc

# Allow new environment variables
direnv allow

5. NPM!

Finally, you can launch example test suite by running npm command.

npm install # for first time
npm test

The Custom Assertions and Commands

The extension file layout is like this.

lib
├── custom_assertions
│   ├── receivedEmailHTMLBodyContains.js
│   ├── receivedEmailSubjectContains.js
│   └── receivedEmailSubjectEquals.js
├── custom_commands
│   ├── checkEmails.js
│   ├── createEmailRoute.js
│   └── deleteEmailRoutes.js
├── globals.js
└── page_objects
    └── hackernewsletter.js # example specific file

You need to specify these paths in config file.

{
  "custom_commands_path": "./lib/custom_commands",
  "custom_assertions_path": "./lib/custom_assertions",
  "page_objects_path": "./lib/page_objects",
  "globals_path": "./lib/globals.js",
  // snip ...
}

More Assertions

I added receivedEmailHTMLBodyContains, receivedEmailSubjectContains, receivedEmailSubjectEquals in this example project.

If you want to add assertions for other field like from_email or attachments, you can implement by forking existing implementations. (See full list)

const util = require("nightwatch/lib/util/utils");

exports.assertion = function receivedEmailSubjectEquals(
  address,
  expected,
  msg
) {
  const DEFAULT_MSG = 'Testing if <%s> received with subject equals to "%s".';
  this.message = msg || util.format(DEFAULT_MSG, address, expected);

  this.expected = function () {
    return expected;
  };

  this.pass = function (value) {
    const expected = this.expected();
    return (
      value.filter(function (email) {
        return email.subject === expected;
        // use indexOf(expected) !== -1 for *contains* implementation.
      }).length > 0
    );
  };

  this.value = function (result) {
    return result || [];
  };

  this.command = function (callback) {
    return this.api.checkEmails(address, callback);
  };
};

If you write some cool assertions in your project, please fork the repository and send me a Pull Request!

Happy testing!

comments powered by Disqus