Getting up and running with Nightwatch.js

Getting Up And Running With Nightwatch.js

24th July 2022 - 14 minutes read time

Nightwatch.js is an end to end testing framework, written in JavaScript. It can be used to test websites and applications and uses the W3C WebDriver API to drive modern browsers to perform the tests.

In this article I will look at setting up Nightwatch.js in a project and getting started with writing tests.

Installing Nightwatch.js

To install Nightwatch.js you should have a npm project. This can be an existing project, but Nightwatch.js can be easily installed as a standalone application; which is useful if you just want to get familiar with the system.

Creating a new, empty, npm project can be done with the following command.

npm init -y

You can now include Nightwatch.js as a development dependency into your project.

npm install nightwatch --save-dev

Whilst you won't always set out with a new project, you should get used to installing Nightwatch.js as a development dependency as you probably won't need it in your production environment.

Your package-lock.json file should look something like this.

{
  "name": "nightwatch-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "nightwatch"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "nightwatch": "^2.2.3"
  }
}

As a side note, don't call the directory you are using the same as the package you want to install as there will be a naming clash. In other words, don't name your directory 'nightwatch'.

Running Nightwatch.js

With the package in place you can run Nightwatch.js. It won't do a lot at this point, but it's worth doing to make sure that you have the package installed. Also, running Nightwatch.js right out of the gate will allow it to self generate some of the configuration files that you would otherwise need to do manually.

To run Nightwatch.js you can use the npx command.

npx nightwatch

The 'npx' command will automatically look in the node_modules/.bin/ directory for the relevant executable file; which in this case is the 'nightwatch' binary. So this is functionally equivalent to running ./node_modules/.bin/nightwatch.

Since we configured the package.json file with Nightwatch.js being the test command we can also run the following command to allow npm to run the nightwatch command for us.

npm test

Once we have the system running we can now configure it to do something more useful.

Configuring Nightwatch.js

When you run Nightwatch.js out of the box it will create a nightwatch.conf.js file and complain about there being no tests, which there aren't.

To configure Nightwatch.js you need to open this file and add your testing directory to the 'src_folders' option. Something like "tests" should be fine, but you also need to make sure that the directory exists.

  src_folders: ['tests'],

Next, we need some way for Nightwatch.js to interact with a browser. This can be done by using the Chrome driver package, which will automatically be used when we run a test.

To install the Chrome driver package run the following command.

npm install chromedriver --save-dev

Again, we are installing this package as a development dependency. It's not a good idea to leave this package in your production environment.

Edit the nightwatch.conf.js file and change some of the values at the top of the "test_settings" section. You essentially want to ensure that the "browserName" setting is "chrome" and that the "webdriver" setting has the "server_path" property set to the path of the chromedriver binary.

  test_settings: {
    default: {
      disable_error_log: false,
      launch_url: 'https://nightwatchjs.org',

      screenshots: {
        enabled: false,
        path: 'screens',
        on_failure: true
      },

      desiredCapabilities: {
        browserName : 'chrome'
      },

      webdriver: {
        start_process: true,
        server_path: 'node_modules/.bin/chromedriver'
      }
    },

Doing this will allow Chrome to load as the browser when running Nightwatch.js tests.

We haven't set the mode to headless yet so the browser will be shown on screen, but that's a good thing whilst you get used to what it's doing.

If you run the nightwatch command now it will still complain that there are no tests, so let's write one.

Adding Tests

Now it's time to write a test. What you test depends on your requirements, but the simplest thing you can do is just get Nightwatch.js to open a web page.

In the testing directory you created earlier, create a JavaScript file and add the following code to it. All this does is to tell Nightwatch.js to open the URL you designate.

module.exports = {
  'User can visit website': function (browser) {
    browser
      .url('https://www.hashbangcode.com/')
  }
}

When we run this Nightwatch.js will open up the browser and visit the web page. As we haven't actually asserted anything Nightwatch.js will complain about that, but this is a good way for you to verify that everything is working correctly.

The next thing to do is add some assertions to the test.

The following code will open the homepage of the website and will ensure that the "home" link contains the text of the website.

module.exports = {
  'User can visit website': function (browser) {
    const homepageLink = '[rel~="home"]';
    const homepageLinkText = '#! code';

    browser
      .url('https://www.hashbangcode.com/')
      .waitForElementVisible(homepageLink)
      .assert.textContains(homepageLink, homepageLinkText)
  }
}

Notice the constants at the top of the test definition. This is a good technique to get into as it allows you to change random selector strings into useful and readable variables. By coding the selectors and text items into your test as variables it will make your tests much more readable.

You can make the test much more complicated by using selectors, click events and assertions. The following example opens up the tally tool on this website and will verify that the tool outputs the correct number when different buttons are clicked.

module.exports = {
    'Tally tool test': function (browser) {
        const homepageLink = '[rel~="home"]';
        const tallyNumberField = '#edit-tally-number';
        const tallyNumberIncrement = '#edit-tally-record';
        const tallyNumberReset = '#edit-tally-reset';

        browser.url('https://www.hashbangcode.com/tools/tally')
        .waitForElementVisible(homepageLink)
        .assert.valueEquals(tallyNumberField, '0')
        .click(tallyNumberIncrement)
        .assert.valueEquals(tallyNumberField, '1')
        .click(tallyNumberIncrement)
        .assert.valueEquals(tallyNumberField, '2')
        .click(tallyNumberReset)
        .assert.valueEquals(tallyNumberField, '0')
    }
}

Writing tests can get complicated, but there is plenty of documentation to help you understand how to get ahead with this. Here are a couple of resources that are useful when writing your tests.

  • Nightwatch.js API - This is a useful resource for finding out how to interact with elements or perform different assertions.
  • W3 WebDriver Specification Locator Strategies -  Nightwatch.js is able to find different elements on the page using a variety of locator strategies. This includes tag names, CSS selectors, xpath queries, link selectors and more.

This is the simplest mechanism to test with, but let's look at improving things with Page Objects.

Using Page Objects

What we have done so far is fine for getting into Nightwatch.js, but you will soon realise that you have a lot of repeating code in your tests.

Thankfully, there is a better way to do this by using page objects. These are objects that represent pages or sections of your application that you can pull in and use as and when you need them. Think of them as a way to encapsulate information about your website outside of the tests.

Open up the nightwatch.conf.js file again and edit the page_objects_path setting. Add in the name of a directory where you will keep your page objects, in this case we called it "pages".

  page_objects_path: ['pages',],

Make sure this "pages" directory exists.

In this directory, each of your pages will be created as a "js" file. As an example, here is a page object for interacting with the text length tool on this site.

module.exports = {
    url: 'https://www.hashbangcode.com/tools/textlength',
    elements: {
      title: {
        selector: '#block-hashbangcode-theme2-page-title h1'
      },
      input: {
        selector: '#edit-text'
      },
      results: {
        selector: '#edit-output',
      }
    }
  };

As you might be able to see, the main purpose of this object is to encapsulate away some of the complexity of the page from the test. Instead of embedding the selectors into the test we add them to the page object and can use them by name.

To use a page object in a test we just need to import it via the browser property that is passed to the test. The object has the same name as the file it is defined in, so because I called the file textLengthToolPage.js the page object can be found using browser.page.textLengthToolPage().

With the object imported we can use it like the browser object, the only difference is that we use the "@" symbol to name properties within the page object. In the example below "@title" maps to the title selector property.

module.exports = {
    'Text length tool test': function (browser) {
        var textlength = browser.page.textLengthToolPage();

        textlength.navigate()
        .assert.textContains('@title', 'Text Length Tool')
        .setValue('@input', 'test')
        .assert.valueEquals('@results', '4');

        browser.end();
    }
}

This test will run the test against the tool and verify that it works correctly.

Page Commands

Another useful thing about page objects is being able to encapsulate commands within the object itself. This allows us to save time by allowing pages to perform common actions or even assertions on elements without having to write that complexity into the test.

Let's change the definition of the textLengthToolPage.js file to add a command. Here, we are adding a command that will add text to the value of the form field.

const textLengthCommands = {
  enterText: function(text) {
    return this.setValue('@input', text);
  }
};

module.exports = {
    url: 'https://www.hashbangcode.com/tools/textlength',
    commands : [textLengthCommands],
    elements: {
      title: {
        selector: '#block-hashbangcode-theme2-page-title h1'
      },
      input: {
        selector: '#edit-text'
      },
      results: {
        selector: '#edit-output',
      }
    }
  };

We can now alter the test to use this function as it if it was part of the page object.

Instead of using the .setValue() function with the selector of where the text should go, we just call enterText() and the function will handle where to place that text for us.

module.exports = {
    'Text length tool test': function (browser) {
        var textlength = browser.page.textLengthToolPage();

        textlength.navigate()
        .assert.textContains('@title', 'Text Length Tool')
        .enterText('test')
        .assert.valueEquals('@results', '4');

        browser.end();
    }
}

Using page objects with commands means we can abstract away lots of complexity from the tests themselves, which makes them more readable.

Read more about Page Objects in the Nightwatch.js documentation.

I have just scratched the surface with Nightwatch.js here. There are lots of commands you can use to interact with web pages and different selectors and assertions you can use to test the output. The Nightwatch.js documentation is a great way of getting stuck in with more advanced parts of Nightwatch.js.

Add new comment

The content of this field is kept private and will not be shown publicly.