UI Testing with Nightwatch.js – Page Objects

I have already written a little about UI testing with Nightwatch.js. This was a little while ago and nightwatch.js has changed a little since then. In v0.7.0 they changed their implementation of page objects and added enhanced support. Nightwatch is a great framework for writing UI tests, and it easy to pick up an write some basic tests. But, if you are going to be writing lots of tests which you are more than likely going to, to ensure your UI is working as planned then you should really be making use of page object within the Nightwatch framework. Page objects are going to save you a lot of time, and prevent duplicated test code. By using page objects you able to abstract away a lot of the HTML actions needed when you are accessing and manipulating your pages. Also by using page objects you can increase your test coverage as you reuse the objects in multiple tests.

There are two parts to the page objects in the Nightwatch framework. Elements and Sections. For me, you need to be using both combined to get the most from the page objects. This is where you will get the most of them. To highlight this I’ve put together a couple of examples to explain how elements and sections work. Following on from my first post: UI testing with Nightwatch.js. I will be using the test from that post, and making two more test files, one that uses elements, and another that uses elements and sections together.

Getting started with page objects

Before you can get using page objects you need to update your config file to tell Nightwatch where to look for your page object files. In your Nightwatch config file you need to update the property:  page_objects_path. Mine looks like: 

"page_objects_path": "page-objects",

I am putting all my page objects inside of one folder so I use a single value in my config. Nightwatch allows you to set the value of page_objects_path as an array, with each element in the array a folder path. As your UI tests grow it might be worth looking at splitting them up into multiple folders for better maintainability. Each page object should be within it’s own file. Nightwatch will read the contents of each folder and these will become your page objects you can use within your tests.

Using Page Objects in your tests

Once you have updated your config to pull in the page objects you need to reference them inside of your tests to be able to call the commands or using the elements. You can name your files how ever you like, I name mine all one word using camel case if needed. You can use hyphens, underscores or even spaces in the file names if you so wish. Depending on how you name your files will matter, as you will need to reference the page object differently inside of your test files. All your page objects are available to all your test on the function argument .page context. I stick with the argument being named browser as per the Nightwatch documentation, but you can use whatever you wish.

The reason I keep the filename to camelCase is because the browser.page is a JavaScript object of all your page object files and folders. I have a page object file named simpleLogin.js which I reference in my tests as:

var login = browser.page.simpleLogin();

If you where to use hyphens in your file name you would need to use the following syntax.

var login = browser.page['simple-login']();

You can make your page object folder have nested folders. This would result in the browser.page object have a key for the folder name which is an object containing the page object files within that folder, you could then call that object like:

var login = browser.page.folder['simple-login']();

Page Elements and Sections

Page elements, or elements as they are referred to within the code are a way to reduce to keep the selectors you use in your tests DRY (Don’t repeat yourself). Rather than having to write the same selector over and over again in you test file you can add it as an element. An element is a property of a page object that have a selector attached to them. You can then use the element name in your test to reference to the selector of the HTML element you want to test for. This allows us to remove the duplicated selector references from our test files and move them into a single file. By doing this we only have one location to change if at a later point in time you are refactoring you application and change the class or ID of the element you are using in your test, you just need to update the selector value in your object file and all the test’s using that element will get the new value.

From the tests in my previous post, switching the selector I was using into page elements gives me the following elements

elements: {
  username: {
    selector: '#username'
  },
  password: {
    selector: '#password'
  },
  submit: {
    selector: 'input[type=submit]'
  },
  error: {
    selector: '.error'
  }
}

If you look at the Nightwatch documentation for page elements you will see there are a few different ways to set up you elements object. I go with the name of the element then an object containing selector and value. As you can see from my example we have four elements, username, password, submit, and error. Each have a selector value that corresponded to an HTML element within our page. You can split your elements down into sections if your object is dealing with a lot elements, you could split your elements object into sections to allow for better maintainability. See the Nightwatch documentation for more on using element sections.

To show you how you could use page elements in a test, here’s our initial test written without using page objects:

'Login Page Initial Render': function(browser) {
  browser
    .init()
    .waitForElementVisible( 'body', 1000 )
    .verify.visible('#username')
    .verify.visible('#password')
    .verify.value( 'input[type=submit]', 'Log In' )
    .verify.elementNotPresent('.error')
    .end()
}

Here’s the same test written using a page object that contains just page elements:

'Login Page Initial Render': function(browser) {
  var login = browser.page.simpleLogin();

  login.navigate()
    .waitForElementVisible( 'body', 1000 )
    .verify.visible('@username')
    .verify.visible('@password')
    .verify.value( '@submit', 'Log In' )
    .verify.elementNotPresent('@error')

  browser.end();
}

As you can see it’s not any shorter in length, possibly longer if anything. First off you need to setup the page object you wish to use in your test, you do this by setting a variable with the value being the page object. Then you start your test chain using this new variable, you can then make use of the elements from the page object by using the name of the element prefixed with an @, as you can see I’ve replaced #username with @username.

Page Commands

Page Commands, or commands called by the Nightwatch documentation is what makes using page objects all the worth while. By using commands you can make your test files really DRY, and move the bulk of your test logic into the page objects allowing future developers or tests who need to contribute to your test to reuse blocks of test’s without having to reinvent the wheel.

Commands are functions that contain logic for reuse in your tests, if you find yourself writing the same assertions or verify statements over and over, you should look at using a command. A really common example would be having to click submit on a form, rather then each test having the same code to click and verify something to do with the form submission you can wrap this up in a command and call the command inside of your test. Commands will still output to the terminal or your report for assertions and verify statements, there is no drastic difference between using a command verses having the test code within the test file. Using commands inside of your page objects make things more maintainable, and helps others who may need to write tests that touch parts of the page you have worked on.

If we take our first test, that’s not using pages objects and show you how we could write it using a page object that makes use of elements and commands

'Login Page Initial Render': function(browser) {
  browser
    .init()
    .waitForElementVisible( 'body', 1000 )
    .verify.visible('#username')
    .verify.visible('#password')
    .verify.value( 'input[type=submit]', 'Log In' )
    .verify.elementNotPresent('.error')
    .end()
}

Here’s the same test written to use page objects with elements and commands:

'Login Page Initial Render': function(browser) {
  var login = browser.page.commandsLogin();

  login.navigate()
    .validateForm()

  browser.end();
}

Sure makes our test a lot smaller, that’s because we have moved the logic into a page object command named “validateForm()”, which now contains the test logic for validating the form is present, inside of the page object we have the following

var loginCommands = {
  validateForm: function() {
    return this.waitForElementVisible('body', 1000)
      .verify.visible('@username')
      .verify.visible('@password')
      .verify.value('@submit', 'Log In')
      .verify.elementNotPresent('@error')
  }
};

module.exports = {
  commands: [loginCommands],
    url: function() { 
      return this.api.launchUrl; 
    },
  elements: {
    username: {
      selector: '#username'
    },
    password: {
      selector: '#password'
    },
    submit: {
      selector: 'input[type=submit]'
    },
    error: {
      selector: '.error'
    }
  }
};

As you can see, we have page elements and commands combined in our page object file for our login page. Overall a bit more code than our very first test, but this is a simple example, if you look at our other tests which is all on my GitHub, you will be able to see how I have used page objects that combine both elements and commands.

Take a look around the updated nightwatch-demo repository to see the other test’s and how I’ve switched them over to use page objects. You can even clone it or download the repository, I’ve updated it to contain all it’s required dependencies, and all runs from an npm install (tested on a Mac and Travis-CI only)