Getting Started With Cucumber.js on Node.js
Cucumber.js is an open-source library that allows you to automate tasks in your web app. Learn how to use it, and make your app as cool as a cucumber!
Join the DZone community and get the full member experience.
Join For FreeCucumber.js
Cucumber.js is a Node.js library used for automation. Cucumber.js helps to test our site's features using pure JavaScript and the Selenium WebDriver. So there is no longer the burden of installing jars and formatting the response and all sorts of time-consuming tasks like we used to do when writing automation scripts in Java using the Eclipse IDE. I am not comparing the power of Java with Node.js, but every language has its advantages and different learning curves. So, for getting a quick automation script working and impressing your colleagues, or for adding onto your resume for impressing your employer, let's start coding and explore it.
Pre-Requisites:
Before getting started, you should be well aware of Node.js async antipatterns and promises. We will be using Node.js 6.9.4 and a module named selenium-cucumber-js which is a clean package that comes with the selenium driver for Node.js, as well as a reporting mechanism by default. Wow! That's awesome, right?
Components:
There are basic 2 basic components:
- Features (eg: Google Search feature)
- Scenarios (eg: If I enter 1+1, I should get 2 on the results page)
Features:
Features are nothing but a file with a .feature extension which has multiple scenarios, which are basically plain English statements which completely state what the feature is, what it is expected to do, and so on.
Scenarios:
Scenarios are the actions taken by the user and the outcome of these actions in the form of a response from the website.
Let's look at a short example for testing the Google Search Feature.
Typical Directory Structure of a Cucumber Project:
As you can see in the basic directory structure for a cucumber project above:
- features - we place all the feature files here (eg: google-search.feature)
- page-objects - page objects are global objects accessible in your feature files for using common functions or anything else. It is accessible inside your feature file as a
page
object. Page objects with a separate file are stored as a key in the page object, for example,google-search.js
will be available as apage.googleSearch.url
, and so on. Remember to use camelCase for key names. - step-definitions: step definitions are normal JS files with all the feature testing logic inside. We can use any assertion library like Chai, Assert, etc. We will be using Chai for our example.
- reports: the reports folder is only used by the library for storing the reports we are not concerned with. If you wish to modify the path you will have to specify the path in your npm test command with the report path. It's up to you; I like it to be at the root level.
- node-modules: We will use just two packages for our project
selenium-cucumber-js
andchai
- page-objects - page objects are global objects accessible in your feature files for using common functions or anything else. It is accessible inside your feature file as a
Wow, just two modules, awesome right?!
Demo Overview (Google Search Testing):
We will be testing the Google search feature. We'll test it by performing some searches in its input and just checking that we get the results that we expected. Let's begin:
google-search.feature
We will create our feature file with the name, google-search.feature, inside our features folder and write the below code in it.
#Search.feature
Feature: Google Search Testing
I should be able to go to a website
and check its search functionality
Scenario: Google search for voter cards app
When I search Google for "itunes vote cards app"
Then I should see "Vote Cards" in the result
Scenario: I evaluate search for sum of 1+1
When I enter "1+1" in input
Then I get "2" in the result
Cucumber understands the Gherkin language for its test cases and parses it to execute the specific definitions file for each scenario. The first line is just comments, so we'll just ignore that part. Line 3 begins the description of the feature we have defined as "Google Search Testing" - you can name it whatever you want. Then from line 7, we start writing scenarios in plain English to use as a test case. We define our scenario with the 'Scenario' keyword, in this case, we defined it as 'Google search for voter cards app.' It is similar to the way you describe test cases in the Mocha framework.
Here we use
When I search Google for "itunes voter cards app"
and on next line
Then I should see "Voter cards" in the result
Simple English statements that can be easily understood by anyone who reads it. Similarly, our second scenario is to check that, if I search for a mathematical equation, that Google will show me a calculator with the result of the expression.
Let's begin with the step-definition file, we will name it google-search-steps.js
const expect = require('chai').expect;
module.exports = function () {
this.When(/^I search Google for "([^"]*)"$/, (text) =>{
return helpers.loadPage('https://google.com')
.then( () => {
return page.googleSearch.performSearch(text)
})
})
this.Then(/^I should see "([^"]*)" in the result$/, function (keywords) {
return driver.wait(until.elementsLocated(by.partialLinkText(keywords)), 10000);
});
this.When(/^I enter "([^"]*)" in input$/, function(expression){
console.log("expression is ",expression);
return helpers.loadPage('https://google.com').then(e => {
return page.googleSearch.performSearch(expression)
})
})
this.Then(/^I get "([^"])*" in the result$/, function(res){
return driver.wait(until.elementsLocated(by.id('cwos')), 10000)
.then(() => {
driver.findElement(by.id('cwos')).getText()
.then(t => {
try {
expect(t).to.be.eql("2")
}catch(e){return Promise.reject(false)}
})
})
})
};
There's a lot of things happening in this code right now. Woof, don't worry, it's fairly simple to understand.
We will walk through module.exports= function(){}
step-by-step.
On line 7, we are defining a call to a When function that will run against the Scenario you entered in your feature, Gherkin-based, file. Note that the statement should match, except to replace the regex "([^"]*)
which will extract the value you entered as an argument to your callback function. We are using the helpers object for loading the page, which is similar to using driver.get(url)
for opening the page. After the window is opened with the Google search page, we will use the page.googleSearch objects to perform a Search function (just adding the text we have provided to the input and submit the form). We will see the code later. After the form is submitted, our this.Then
is called with arguments. As a result, we are expecting to, in our case, get the Voter Cards text, and a hyperlink.
Page-Object:
Create a file called google-search.js in your page-objects directory and place the below code to make the page object available in your step definition file.
module.exports = {
url: 'https://www.google.com',
elements: {
searchInput: by.name('q'),
searchResultLink: by.css('div.g > h3 > a')
},
/**
* enters a search term into Google's search box and presses enter
* @param {string} searchQuery
* @returns {Promise} a promise to enter the search values
*/
performSearch: function (searchQuery) {
var selector = page.googleSearch.elements.searchInput;
// return a promise so the calling function knows the task has completed
return driver.findElement(selector).sendKeys(searchQuery, selenium.Key.ENTER);
}
};
Reports :
After all the scenarios are tested, a dynamic report is generated based on the return value of your this.Then
function. So, in the above case, where we are checking the voter card app in a Google search, we are finding the hyperlinks with the Voter Cards App as a partial text in its link. If it is found on our search page, it was a successful test. Below is the screenshot for the report file which is usually called report.html and automatically opened in your browser once all the tests are finished.
You can see that all the tests have passed, and see the details about the time and scenarios of a particular feature's test. Wow, that's so cool! No more additional configuration, no annotation, no report library injection like we do in Java using extent report or some other library.
For running the test, I have to just run an commandnpm test
. Below is my package.json that has the npm test command under the block scripts
.
{
"name": "cucumber-test",
"version": "1.0.0",
"description": "To test using cucumber js and selenium webdriver",
"main": "index.js",
"scripts": {
"test": "node ./node_modules/selenium-cucumber-js/index.js -s ./step-definitions -x 50000"
},
"keywords": [
"cucumber"
],
"author": "Kailash K Yogeshwar",
"license": "ISC",
"devDependencies": {
"chai": "^4.1.1",
"selenium-cucumber-js": "^1.4.9"
}
}
You can see my in scripts block test
key has ./node_modules/selenium-cucumber-js/index.js -s ./step-definitions -x 50000
Here, -s
is optional. By default, it will check for a step-definitions folder in your project directory. -x timeout is the timeout for each scenario to be tested - quite handy I would say.
Now, what if I am checking the result of a scenario manually, like if the page title should be "Google" but it is "Googler." Additionally, we are using,expect
so it will throw an assertion error and the test suite may not give the proper result. In such cases, we should wrap it in the following:
try{
expect(condition).to.be.eql(value)
} catch(e){
return Promise.reject('Message')
}
So it will treat it as a failed test scenario and show up under the failed tab in the report.
If you are getting irritated by the continuous opening of the browser for each scenario, just pass the parameter-b
with a "phantomjs" value, so it will run the test against the headless browser.
So that's a short project for quickly getting started with selenium-cucumber-js.
Hope you found it interesting and try it. Let me know of any suggestions in the comments!
Opinions expressed by DZone contributors are their own.
Comments