Introduction to Nightwatch, for ultra-fast acceptance testing

For a few months, developers have been wondering what to use for their acceptance tests in new projects. CasperJS having not released a new version for a long time and Protractor having never really convinced for Angular projects, the horizon seemed somewhat deserted in terms of a solid solution. But it was not without counting on the community that created Nightwatch (no, it's not the same as in Game Of Thrones …)

Museum Overview

nightwatch-logoNightwatch is an overlay to S for Node.js allowing to control the latter thanks to the protocol Selenium JsonWireProtocol through a simple and elegant API.
Here is an illustration representing fairly well how Nigtwatch works with Selenium and then the browser:
How Nightwatch works
Nightwatch stands out from the competition in several ways:

  • As said before, its syntax remains quite classy
  • It is easy to extend (we will see how later in this article)
  • Basically integrates the pattern of Object page
  • With selenium under the hood, you benefit from the ecosystem of this platform. I'm thinking in particular of Sass tools like BrowserStack ou Sauce Labs and all the sequel webdriver which allow you to connect to all browsers.
  • But all this is nothing compared to what is for me is a Killer Feature: Nightwatch is able to parallelize the execution of your test suites !

Show me the code!

Getting started

For the explanations which will follow we will start with the tree structure of the nightwatch-boilerplate project created for the article, described here:
├── nightwatch.json
├── nightwatch.globals.js
├── nw
│ ├── reports
│ ├── logs
│ └── tests
└── package.json
If you want the solution already complete, I advise you to clone this repository directly.
As usual, we start by installing packages (at the root of the project):

npm install nightwatch selenium-server-standalone-jar chromedriver --save-dev

Here, only the first dependency is really mandatory, but to save you time, we also install a package containing the selenium-server (which comes in the form of a simple jar file ), as well as another containing the ChromeDriver.

The next step is to create the Nightwatch configuration file named nightwatch.json . Here is the example file we will be working with (I will explain each field afterward):

{
  "src_folders" : ["nw/tests"],
  "output_folder" : "nw/reports",
  "globals_path" : "nightwatch.globals.js",
  "test_workers": {
    "enabled": true,
    "workers": "auto"
  },
  "selenium" : {
    "start_process" : true,
    "server_path" : "./node_modules/selenium-server-standalone-jar/jar/selenium-server-standalone-2.50.1.jar",
    "log_path" : "nw/logs",
    "host" : "127.0.0.1",
    "port" : 4444,
    "cli_args" : {
      "webdriver.chrome.driver" : "./node_modules/chromedriver/bin/chromedriver",
      "webdriver.ie.driver" : ""
    }
  },
  "test_settings" : {
    "default" : {
      "launch_url" : "http://google.com",
      "selenium_port"  : 4444,
      "selenium_host"  : "localhost",
      "silent": true,
      "screenshots" : {
        "enabled" : true,
        "path" : ""
      },
      "desiredCapabilities": {
        "browserName": "chrome",
        "javascriptEnabled": true,
        "acceptSslCerts": true
      }
    },
    "french" : {
      "launch_url" : "http://google.fr",
      "desiredCapabilities": {
        "browserName": "firefox",
        "javascriptEnabled": true,
        "acceptSslCerts": true
      }
    }
  }
}
  • src_folders : Folders where to find test suites.
  • output_folders : Folder where to create test reports.
  • test_workers : Is the part where we define if we want our test suites to be parallelized. Here we define that they will be “enabled:true”, and that the number of executions will be according to the number of CPUs “workers:true”.
  • globals_path : File of the global parameters of the test suites on which we will come back a little later.
  • selenium : This part defines the general configuration to connect to the Selenium server. We won't dwell on it, but note that we have specified the paths to access the Selenium server and the Chrome driver.
  • test_settings : This part is probably the most interesting since it will allow you to define your test environments. In this example we define the parameters of the default environment and that of the “french” environment. Note that the additional environments inherit the default configuration, which is why we only had to “overload” the target url of our environment (“launch_url”) and the browser with (“desiredCapabilities” )

The `desiredCapabilities` block might seem a bit confusing just for configuring the browser you want to launch. In reality, this format comes directly from the Selenium configuration and allows you to configure a multitude of things in the target browser. For more information, I recommend the official documentation.

First test

It's time to tackle our first test; we create the file nw/tests/reseachOnGoogle.test.js which contains the following:

module.exports = {
    'Search on google': (browser) => {
        browser
            .init()
            .waitForElementVisible('body', 1000)
            .setValue('input[type=text]', 'nightwatch')
            .waitForElementVisible('button[name=btnG]', 1000)
            .click('button[name=btnG]')
            .pause(1000)
            .assert.containsText('#main', 'Night Watch')
            .end()
    },
    after: (browser) => {
        browser.end()
    }
};

This file describes a test suite, including a 'Search on Google' test. First, this test begins with an ` init` statement that loads the page, then waits for an element to become visible using `waitForElement` , sets a value in the search field using `setValue` , clicks the search button using `click` (after verifying its presence), and pauses for a second. Finally, the `after` function, automatically called at the end of the test suite by Nightwatch, calls the ` end` method.

The `end` method call is very important, as it's responsible for closing the browser used for the test. Therefore, I advise you to call it at the end of each test sequence using `after`.

All you have to do is run it with the command node_modules/nightwatch/bin/nightwatch -c nightwatch.json
runTest3
We have a result similar to the window above as well as a Chrome opening in the background where we see the requested manipulations. Do you want to launch the 'french' environment now? Nothing's easier :
node_modules/nightwatch/bin/nightwatch -c nightwatch.json –env english
No difference to the console, on the other hand it is a Firefox which is launched now and which goes to “http://google.fr” instead of “http://google.com” of the default environment.

The “globals” to simplify everything

The globals file will be very useful to simplify the tests by defining global parameters. For example, currently, we need to systematically define the maximum waiting time to find a CSS element. It would be very practical to be able to define a global waiting time, it is precisely the kind of possibilities offered by this file, in addition to being able to define hooks at different times during the test and to define dynamic environment variables (unlike the static variables in the “declarative” json nightwatch configuration file).
Here is the file we are going to use, the comments will allow you to understand its structure:

module.exports = {
    default: { // Paramètres de l'environement 'default'
        searchTerm: 'nightwatch',
        movieName: 'Night Watch'
    },
    french: { // Paramètres de l'environement 'french'
        searchTerm: 'dikkenek',
        movieName: 'dikkenek'
    },
    // Arrête tout dès qu'un test échoue
    abortOnAssertionFailure: true,
    // Délais entre deux vérifications
    waitForConditionPollInterval: 300,
    // Délais à attendre par défault
    waitForConditionTimeout: 1000,
    // Echoue si une selection retourne plusieurs éléments alors qu'elle devait n'en retourner qu'un
    throwOnMultipleElementsReturned: false,
    // Avant et après l'éxecution de l'ensemble des tests
    before: (next) => next(),
    after: (next) => next(),
    //  Avant et après chaque éxecution de suite des tests
    beforeEach: (browser, next) => next(),
    afterEach: (browser, next) => next(),
    // Pour customiser le reporter de test
    reporter: (results, next) => next()
};

This allows us to refactor the test suite as follows:

module.exports = {
    'Search on google': (browser) => {
        browser
            .init()
            .waitForElementVisible('body')
            .setValue('input[type=text]', browser.globals.searchTerm)
            .waitForElementVisible('button[name=btnG]')
            .click('button[name=btnG]')
            .pause(1000)
            .assert.containsText('#main', browser.globals.movieName)
            .end()
    },
    after: (browser) => {
        browser.end()
    }
};

You will notice the use of dynamic 'globals' variables defined for each environment. They are very practical when you need, for example, to pass sensitive data accessible only as an environment variable.

And at the same time, what does it give?

Since our configuration already runs the test suites in parallel, we just need to add a second test suite to see them execute simultaneously. So let's create a suite called nw/tests/nightWatchIsMovie.test.js like this:

module.exports = {
    'Go To google': (browser) => {
        browser
            .init()
            .waitForElementVisible('body')
            .setValue('input[type=text]', browser.globals.searchTerm)
            .waitForElementVisible('button[name=btnG]')
            .click('button[name=btnG]')
            .pause(1000)
    },
    'Check movie name': (browser) => {
        browser
            .assert.containsText('.mod .kno-ecr-pt.kno-fb-ctx', browser.globals.movieName)
            .assert.containsText('.mod ._gdf', '2004')
    },
    after: (browser) => {
        browser.end();
    }
};

Small difference with this new file, we have several tests that make up our suite. We run the tests again and, TADA!
run test in parallel nightwatch
You have two test suites running in parallel in two different chromes (provided you have at least two cores/cpu, which I hope is the case).

Review & Follow-up

With a not so bad setup, Nightwatch is a very easy to pick up tool. Indeed, the minimal configuration is quick to implement, the test suites remain readable and maintainable and its suite parallelization feature saves monstrous time compared to the usual time to run this kind of test!
As a bonus, the test results are readable by the developer in the terminal and by your continuous integration system thanks to the xUnit outputs present in the folder nw/reports.
In a future article we will deal with Object Pages and how to develop complex commands and other Nightwatch tips.
Stay tuned ...
By Matthew Breton CTO at JS-Republic.