E2E Testing with Nightwatch: Part Two

Published Aug 29, 2017Last updated Mar 05, 2018
E2E Testing with Nightwatch: Part Two

Welcome to the second part of the E2E Testing with Nightwatch tutorial. In this part, we'll be writing actual tests for the React TodoMVC App. Read the first part here.

This part borrows from the tests at g00glen00b. You can find the source code here.

Writing Our First Test

At the root of the project's directory, create a new directory called tests. This folder name corresponds with the src_folders property value in the nightwatch.json file. In Nightwatch, all tests are Node.js modules.

For our first test, we'll check that the main and footer section of the app don't exist without any tasks. This is the default behaviour of the TodoMVC App.

module.exports = {
  'Does not show the task list if there are no tasks'(client) {
    client
      .url('http://todomvc.com/examples/react/#/')
      .waitForElementVisible('.header h1')
      .expect.element('.main').to.not.be.present;
    client.end();
  },
  'Does not show the footer if there are no tasks'(client) {
    client
      .url('http://todomvc.com/examples/react/#/')
      .waitForElementVisible('.header h1')
      .expect.element('.footer').to.not.be.present;
    client.end();
  },
}

First, let's go to the application page and wait until view is visible by waiting for the header on that page, .header h1. When it's loading, let's test to see if the element with the class .main exists (which should not exist if there are no tasks). Let's call the end() method and tell it to close the current browser.

If you forget to call the end() method, your test will still work. However, if you write multiple tests in the same file, the state will be preserved, which could lead to your tests failing because the data from earlier tests won't be removed.

In Nightwatch you can decide to choose the chai assert or expect style of assertions. We're not using the assert style here, so we don't have to chain the end() method as the expect assertion doesn't currently support method chaining. It is entirely up to you which assertion style you choose, the most important thing is that you're consistent.

The second test is similar to the first. This time it's testing if the element with the class .footer exists.

The next test will check if the input field is focused when the page is loaded. To do that we'll use the :focus CSS selector, which returns only focused elements. Apply that to the .new-todo input field - your test will only succeed if that element is indeed focused:

'On page load, it sets focus on the input field'(client) {
    client
      .url('http://todomvc.com/examples/react/#/')
      .waitForElementVisible('.header h1')
      .assert.elementPresent('.header .new-todo:focus')
      .end();
  },

With elementPresent() we can check whether or not an element exists. It does not have to be visible.

For the next test, we'll test that a new task is added when we enter a task in the input field and press the Enter key.

'When I add a task, it shows todo items'(client) {
    client
      .url('http://todomvc.com/examples/react/#/')
      .waitForElementVisible('.header h1')
      .setValue('.new-todo', 'New task')
      .keys(client.Keys.ENTER)
      .assert.containsText('.todo-list li:first-child label', 'New task')
      .end();
  },

The first few lines remain the same, but with setValue(), we set a value to the input field with class new-todo. After that, we submit it by pressing the Enter key using keys(client.Keys.ENTER) and verifying if the .todo-list has an item with a label with the same text as the value we entered earlier.

The next test goes even further, by not only adding a new task, but also by completing the task itself and verifying if it has been stricken through:

'Marks a todo item as completed by striking through it'(client) {
    client
      .url('http://todomvc.com/examples/react/#/')
      .waitForElementVisible('.header h1')
      .setValue('.new-todo', 'New task')
      .keys(client.Keys.ENTER)
      .click('.todo-list li:first-child .toggle')
      .assert.cssClassPresent('.todo-list li:first-child', 'completed')
      .end();
  },

We didn’t really verify if there’s a line through the text here, but we verified if the completed class is present. The CSS for this class provides the line through the text. To check an element, we used the click() API to click on the checkbox itself.

We've already verified that you can add and complete tasks. With the next test we’re going to see if the counter at the bottom of the application shows the correct number of tasks left.

To do that, we simply add tasks and complete them, and then verify if the number changes appropriately:

'Shows how many items there are left'(client) {
    client
      .url('http://todomvc.com/examples/react/#/')
      .waitForElementVisible('.header h1')
      .setValue('.new-todo', 'New task')
      .keys(client.Keys.ENTER)
      .setValue('.new-todo', 'Another task')
      .keys(client.Keys.ENTER)
      .assert.containsText('.todo-count', '2 items left')
      .click('.todo-list li:first-child .toggle')
      .assert.containsText('.todo-count', '1 item left')
      .end();
  },

Nothing new here. We added two elements, verified if the text matches '2 items left', then completed a task and verified if there is only '1 item left'.

For the final test of the overview, we’re going to verify if we cannot add a task if we don’t enter a value in the textbox or if we only enter spaces:

'Does not add empty or blank tasks'(client) {
    client
      .url('http://todomvc.com/examples/react/#/')
      .waitForElementVisible('.header h1')
      .setValue('.new-todo', 'New task')
      .keys(client.Keys.ENTER)
      .keys(client.Keys.ENTER)
      .setValue('.new-todo', '  ')
      .keys(client.Keys.ENTER)
      .assert.containsText('.todo-count', '1 item left')
      .end();
  }

What we did here is nothing new. We added a proper task, tried to submit the form without entering a new value, entered some spaces, and tried to submit again. In neither of these cases should a task be added. There should only be one item left — the first, and valid, task we added.

Global Configuration

Nightwatch commands like waitForElementVisible() or the assertions require the timeout parameter be passed to the element so that the test throws an error when that timeout limit is reached. Normally the waitForElementVisible() method looks like this:

waitForElementVisible('@anElement', 3000)

3000 is the number of milliseconds after which the test throws an element not visible exception. Fortunately, we can move that value outside the test so that the code is cleaner. In order to do that, create a globals.js file at the root of the project's directory and paste this code:

export default {  
  waitForConditionTimeout: 10000
};

Afterwards, edit the nightwatch.json config file changing globals_path: "" to globals_path : "globals".
Now all Nightwatch methods that require a timeout will have this global ten second timeout specified as the default. You can still define a special timeout for single calls if needed.

Running Tests

That's it! All that's left to do is run the test. In the terminal, navigate to your projects' root directory and type this command:

npm test or yarn test

With everything set up correctly, you should see a console output similar to this one:

Starting selenium server... started - PID:  30647

[Overview Spec] Test Suite
==============================

Running:  Does not show the task list if there are no tasks
 ✔ Element <.header h1> was visible after 84 milliseconds.
 ✔ Expected element <.main> to not be present - element was not found in 40ms.

OK. 2 assertions passed. (8.655s)

Running:  Does not show the footer if there are no tasks
 ✔ Element <.header h1> was visible after 577 milliseconds.
 ✔ Expected element <.footer> to not be present - element was not found in 25ms.

OK. 2 assertions passed. (3.731s)

Running:  On page load, it sets focus on the input field
 ✔ Element <.header h1> was visible after 573 milliseconds.
 ✔ Testing if element <.header .new-todo:focus> is present.

OK. 2 assertions passed. (4.36s)

Running:  When I add a task, it shows todo items
 ✔ Element <.header h1> was visible after 563 milliseconds.
 ✔ Testing if element <.todo-list li:first-child label> contains text: "New task".

OK. 2 assertions passed. (4.812s)

Running:  Marks a todo item as completed by striking through it
 ✔ Element <.header h1> was visible after 561 milliseconds.
 ✔ Testing if element <.todo-list li:first-child> has css class: "completed".

OK. 2 assertions passed. (4.808s)

Running:  Shows how many items there are left
 ✔ Element <.header h1> was visible after 1599 milliseconds.
 ✔ Testing if element <.todo-count> contains text: "2 items left".
 ✔ Testing if element <.todo-count> contains text: "1 item left".

OK. 3 assertions passed. (6s)

Running:  Does not add empty or blank tasks
 ✔ Element <.header h1> was visible after 562 milliseconds.
 ✔ Testing if element <.todo-count> contains text: "1 item left".

OK. 2 assertions passed. (4.858s)

OK. 15  total assertions passed. (37.525s)

Summary

In this tutorial you've learned how to create an end-to-end test and use the globals.js file.
Keep an eye out for the next part, where I would introduce Nightwatch pages!

Update:

The part three on Nightwatch.js Pages is here.

Kindly hit the like button if you find this article useful.
Follow me on Twitter @codejockie

Discover and read more posts from John Kennedy
get started