Behavior Driven Development with Nightwatch.js and Cucumber.js
Cucumber is a testing tool that executes plain text functional descriptions as automated tests. The test cases are written in simple, plain English. To illustrate the tester-friendly nature of Cucumber, while explaining its functionality along the way, we will work through the implementation of a feature encountered often by many – searching a term on Google. We start by creating a folder called ‘features’ in the base directory of your project (usually in the ‘app’ folder). Inside this features folder we need to create files for each feature, with a title of the feature as the prefix and ‘.feature’ as the suffix. For example, if we have a searching Google feature we want to test we create a file called ‘google_search.feature’. Each feature file contains a title, a description, the feature name, and a scenario. A scenario is made up of the steps you need to take to implement the feature. The title, description, and feature sections are written in plain English, but the steps of a scenario are written using the Gherkin language. Gherkin scripts are made up of three keywords: Given, When, Then. The following is an example of what the google_search.feature should look like:
Nightwatch.js is an automated testing framework written in Node.js that utilizes the Selenium Webdriver Api, specifically the WebDriver Wire Protocol, to carry out browser related tasks. Essentially, this ‘automation’ tool allows a computer to mimic a user’s interaction with a web browser through simple, intuitive commands (such as .url() and .click() which navigate to a site and click a button/link, respectively). Like most other automation tools, Nightwatch.js uses CSS and Xpath selectors to manipulate web elements. Besides being a powerful test automation framework, Nightwatch.js stands out from most other Node.js based frameworks due to its third-party integration with Cucumber. This means that you can use a Cucumber.js plugin for Nightwatch.js that enables the tester to use a Behavior Driven Development (BDD)-style approach for browser testing. This means that you can describe user scenarios/test-cases using the Gherkin syntax (plain English) with Cucumber and map them to browser commands/operations and assertions provided by Nightwatch.js.
There is a neat, time-saving trick Cucumber provides so that we can get started with implementing our step definitions. After installing Nightwatch.js and configuring it to use the Cucumber.js plugin – details on how to install and configure both can be found in the last section of this article – use your terminal to navigate to the directory that contains your ‘features’ folder and run/execute the test/feature using the following command: node_modules/.bin/nightwatch . This is what will be outputted in your terminal as a result:
After having completed these steps the tester is in the position to actually use the Nightwatch.js commands and assertions to turn the phrases into concrete actions the computer can perform on the browser to carry out the scenario. If you are unfamiliar with Nightwatch.js commands and assertions I recommend that you read the official user-guide found here http://nightwatchjs.org/guide#usage as a tutorial on its use is beyond the scope of this article. Nevertheless, the caption below illustrates the “defined” steps of the scenario using the Nightwatch.js commands and assertions:
Now that we have defined the step definitions for our scenario, we can run our test. Navigate to the directory that contains your features file and run the same command as before which executes the test: node_modules/.bin/nightwatch
As you can see, the test successfully passed (denoted by all green lines) which means that the computer was able to successfully navigate to Google, enter a term into the search bar, and receive relevant results! This means that our feature was executed successfully so the tester should feel confident that this feature would not break in the wild. Now that we have the basics down, we will focus on advanced features of Cucumber.js that will make your tests more scalable and maintainable: multiple scenarios and parameterization.
One of the powerful aspects of Cucumber.js is the ability to reuse the functions implemented in the step_definitions files for multiple scenarios of the same feature. For example, assume that we want to test multiple scenarios for the Google Search feature. Namely, we would like to search two terms using two distinct scenarios. The first scenario, from our original example, will search ‘QualityWorks’ while our second scenario will search ‘NodeQa’. Cucumber.js allows us to define two or more scenarios for the same feature while allowing us to reuse functions defined in the corresponding step_definitions file is any of the Given, When, Then statements happen to belong to both scenarios. This comes in handy as it allows the tester to adhere to the ‘DRY’ principle (Don’t Repeat Yourself) by not having to create duplicate functions with the same behavior. This is beneficial in the long term as code-changes will only need to be implemented in one function should the functionality of the feature change in the future, thus making the tests more scalable and maintainable. Below you can see an example of our Google Search features file that now contains two scenarios and its corresponding step_definitions file. Note that even though we added an extra scenario, we did not need to add any code to the step_definitions file as the functions within it are reused by the scenarios – since the Given, When, Then steps in the two scenarios are identical except for the data (double-quoted) which is passed in to the function:
When we run these tests (by typing node_modules/.bin/nightwatch) we can see that they were executed successfully:
So, we have now witnessed one of the more powerful features of Cucumber.js which allows us to avoid code duplication which means less work, more maintainability, and scalability in the long term. A more advanced feature of Cucumber.js that allows the tester to remove duplication, or ‘DRY up’ their tests, even more is the use of Scenario Outlines with Example Tables for parameterization. This functionality allows the tester to avoid having to duplicate code not only on their step_definitions files but also in their feature files. Essentially, scenario outlines make the scenarios in each feature “dynamic” so that instead of having to describe two (or more) scenarios with the exact same Given, When, Then steps except for the data value passed in, the tester can just have one scenario outline that is passed in multiple data values stored in an examples table. This is similar to the concept of using loops in programming where we iterate through the same code multiple times while passing in different values for the same variable. To best illustrate how scenario outlines are used we will restructure our feature file which contained two scenarios composed of the same steps but that each contain a different data value into a feature file which contains only one scenario outline and an accompanying examples table which stores the data values that will be passed in. Note that the double quotes used to represent a data value are now replaced by less than ‘<’ and greater than ‘>’ symbols around a variable which now represents the data values that will be passed in by the examples table. The data values for each variable are stored in the subsequent rows of the same column as the data variable:
When we execute this test we can see that we obtain the same results as if we had defined two scenarios like we did in our previous example
However, by using a scenario outline we have removed duplication from our feature file which makes our tests more scalable in the long run and saves time and effort. If the tester needs to search for an additional term on google in the future, they can accomplish so by just adding an additional row to the examples table with the search term and search results instead of having to define an additional feature.
As promised, this last section will hopefully provide the reader with guidance on how to install and configure Nightwatch.js and its Cucumber plugin into their app. There is an official nightwatch-cucumber repository that contains instructions on how to set up the two frameworks in your local environment. Personally, I found these instructions to be very straightforward but incomplete as I was not able to setup the frameworks in my app successfully by just following these steps while trying to fill in the gaps by reading the official Nightwatch.js documentation. Luckily, I was able to find another repository that provides a step-by-step guide on how to successfully install and configure Nightwatchjs. Since the latter did not contain instructions on how to configure the Cucumber.js plugin, I combined the instructions from the two repos and was able to get my tests to successfully run in my local environment. Note, only follow these steps if you cannot successfully install the two frameworks using the instructions provided here.
- Clone the learn-nightwatch repo by copying and pasting the following command into your terminal: git clone https://github.com/dwyl/learn-nightwatch.git && cd learn-nightwatch && cp sample.env .env
- After executing the previous command, you will now be inside the learn-nightwatch parent folder in your terminal. In this directory (learn-nightwatch), Install the required dependencies using the following command: npm install
- In the same directory, you need the nightwatch-cucumber library to be installed locally using the following command: npm install --save-dev nightwatch-cucumber
- in the same directory, you need to install the Cucumber.js framework locally: npm install –-save-dev cucumber
- In your current directory, delete the nightwatch.conf.Basic.js file and replace all of the contents in the nightwatch.conf.js file with the contents in this file. The major change in the configuration file is that the src_folders property is now set to nightwatch-cucumber which represents the required ‘nightwatch-cucumber' library
- Lastly, in the same directory (learn-nightwatch) delete the test folder. Now, create a features folder in the current directory and start writing your features and step-definitions. Remember that for the tests to run successfully you need to run the node_modules/.bin/nightwatch from the directory that contains the features folder.