UI End-to-End Testing with Nightwatch.js

Sunset over La Jolla

Good morning and Happy Friday! I’m back after a little prompting from an old friend.

Song of the day:

Quick life update: I’m living near New York City now, and I’m working as a Senior Software Engineer for a team that writes the software that supports such excellent blogs as Engadget, Huffington Post Australia (more international editions to follow!), and Autoblog, among many others.

I’ve also gotten way into 3D printing, Internet of Things development, and am still doing minor development on my open-source fitness project, PPL.fitness.

Today we’re going to talk about testing our front-end code. I know, I know, I’ve used the excuses myself: “I don’t have time! My deadlines are too tight.” “I need to test how something LOOKS, not unit test a function in my code.” “Writing tests is weird and unnatural.” “Deuteronomy says UI testing is an abomination.” Yeah.

Enter stage right: Nightwatch.js. Super simple to set up. I get to use Node.js to simulate clicks, typing, and key presses, and check to see if elements are visible. I also get to check properties of the elements. Plus, it runs against the industry-standard Selenium server.

Though the initial setup took about a day to get my code decently covered by tests, I can now rest a little easier knowing that when I release new feature updates to my software, everything will work.

Setting Up

Install guide
Also download: https://sites.google.com/a/chromium.org/chromedriver/

I set up my folder structure like this:

project folder
…source files…
nightwatch.json
– bin
— chromedriver
— selenium-server-standalone-2.53.0.jar
– tests
— pages
—- pageCreateNew.js
— login.js
— createNew.js

I’m going to put my files up, with my comments inline for explanation.

nightwatch.js

{
  "src_folders" : ["tests"],   
  "output_folder" : "reports",
  "custom_commands_path" : "",
  "custom_assertions_path" : "",
  "page_objects_path" : "tests/pages",
  "globals_path" : "",

  "selenium" : {
    "start_process" : true,
    "server_path" : "bin/selenium-server-standalone-2.53.0.jar",
    "log_path" : "",
    "host" : "127.0.0.1",
    "port" : 4444,
    "cli_args" : {
      "webdriver.chrome.driver" : "bin/chromedriver", //THIS IS A BIG DEAL SO WE CAN TEST IN CHROME
      "webdriver.ie.driver" : ""
    }
  },

  "test_settings" : {
    "default" : { //runs when we don't pass in any options
      "launch_url" : "http://localhost:3000/management/splash/", //point this to whatever URL you want to test
      "selenium_port"  : 4444,
      "selenium_host"  : "localhost",
      "silent": true,
      "screenshots" : {
        "enabled" : false,
        "path" : ""
      },
      "desiredCapabilities": {
        "browserName": "firefox",
        "javascriptEnabled": true,
        "acceptSslCerts": true
      }
    },

    "chrome" : { //runs when user runs `nightwatch --env chrome`
      "desiredCapabilities": {
        "browserName": "chrome",
        "javascriptEnabled": true,
        "acceptSslCerts": true
      }
    },

    "production" : { //runs when user runs `nightwatch --env production`
      "launch_url" : "http://production.com/management/splash/",//point this to whatever URL you want to test
      "selenium_port"  : 4444,
      "selenium_host"  : "localhost",
      "silent": true,
      "screenshots" : {
        "enabled" : false,
        "path" : ""
      },
      "desiredCapabilities": {
        "browserName": "firefox",
        "javascriptEnabled": true,
        "acceptSslCerts": true
      }
    }
  }
}

Next, I wrote a tiny login function that I can call to get past authentication screens:

login.js

module.exports = function(client){
    return client
        .url('https://cms.aol.com')
        .waitForElementVisible('body', 5000)
        .waitForElementVisible('#signinemail', 3000) //change the selector to whatever the username input is on the site you're testing
        .setValue('#signinemail', email_goes_here) //change the selector accordingly
        .click('#continue_button')
        .waitForElementVisible('#signinpassword', 3000) //change the selector to whatever the password input is on the site you're testing
        .setValue('#signinpassword', password_goes_here) //change the selector accordingly
        .click('#signin_button')  //again, change this to the ID of the login button
};

One thing that’s really cool about Nightwatch is the ability to define pages. You get to define elements here and re-use them later in your testing code:

pageCreateNew.js

module.exports = {
  elements: {
    splashesListContainer: { 
      selector: '.splashes-list-container' 
    },
    startFreshButton: { 
      selector: '.new-button'
    }
  }
};

 

Quick break.

Alright, and now we get to the meat of our testing code!

var login = require('./login.js'); //this is our login function from before

module.exports = {
  '@tags': ['create', 'splash'], //tags are used to run certain groups of tests; I'll talk more about this in a minute

  'can edit the first headline': function (client) { //name your functions like this so that the person running the test knows what's broken or working
  	var createNew = client.page.pageCreateNew(); 

  	login(client); //this is how easy it is to call our login.js script!

  	createNew.navigate(client.launchUrl)
      .waitForElementVisible('@startFreshButton', 15000) //this is how we use the element selectors we defined in pageCreateNew.js
      .click('@startFreshButton')
      .waitForElementVisible('#template-container .headline-1 .splash__header', 1000) 
      .click('#template-container .headline-1 .splash__header')
      .setValue('#template-container textarea', 'Automated Testing Is the Best!') 
      //custom color tests
      .waitForElementVisible('.showCustomColors', 2000)
      .click('.showCustomColors')
      .click('.customColorOption:nth-of-type(2)')
      .click('.showCustomColors') //hide it
      //font size
      .setValue('#font-size-number-input', '80')
      //hyperlink
      .click('.link-button')
      .clearValue('.anchorLink')
      .setValue('.anchorLink', 'http://test.com')
      .click('.accept-item-button')
      .waitForElementVisible('#template-container .headline-1 .splash__header', 1000)
  
   //this is where we make our actual comparisons to see if everything is working!
    client.expect.element('#template-container .headline-1 .splash__header').text.to.equal('Automated Testing Is the Best!');
    client.expect.element('#template-container .headline-1 .splash__header').to.have.css('font-size', '80px');
    client.expect.element('#template-container .headline-1 .splash__header').to.have.css('color', '#2D7061');
    
    client.end();
  }
};

As you can see, the syntax is really simple!


expect(elementSelector).to.have.css(style, value)

expect(elementSelector).text.to.equal(value)

 

I am even able to test the type of my remote data store’s JSON schema with it by running AngularJS commands using client.api.execute(command)!


'is the schema set up correctly?':function (client) {
 var pageData= client.page.pageData();
 var code;

login(client);
 pageSplashLive.navigate(DATA_STORE_URL)
 .waitForElementVisible('body', 10000)
 .api.execute("return angular.element($('.data-editor-form')).scope()['ctrl']['data']['schema']['properties'];", [], function(response) {
 code = response.value;
 var dataType = typeof code;

client.assert.equal(dataType, 'object');
 client.assert.equal(code['Data']['type'], 'array');
 });

client.end();

},

 

I’ve just barely scratched the surface of this fantastic automation framework. I envision being able to automate a ton of online stuff with this tool – it doesn’t just have to be used to test my code.

 

Discussion of the day: what would you automate to make your life easier? I’m currently working on an automatic window blinds project (I’ll write that up soon!)

 

Cheers!

Sam

Author: Sam

Tinkerer. I like making things.

2 thoughts on “UI End-to-End Testing with Nightwatch.js”

Leave a Reply

Your email address will not be published. Required fields are marked *