Introduction
If you develop AngularJS applications, then you have probably heard of Protractor. It is an end-to-end testing framework built specifically for AngularJS. It allows you to create tests that interact with a browser like a real user would. One of the greatest features of Protractor is its ability to โbe smartโ about waiting for a page to load, limiting the amount of waits and sleeps you use in your suite. Protractor is also incredibly flexible in that it allows you to incorporate different behavior-driven development (BDD) frameworks like Cucumber into your workflow. In this tutorial, youโll learn how to implement the Cucumber.js framework with Protractor.
Protractor and BDD
Out of the box, Protractor supports Jasmine. Jasmine allows you to write your specs based on the behavior of the application. This is great for unit tests, but may not be the preferred format for business-facing users. What if your business team wants the ability to see a higher-level view of what your suite is testing against? This is where Cucumber comes in.
Cucumber is another BDD framework that focuses more on features or stories. It mimics the format of user stories and utilizes Gherkin. Cucumber provides your team with living documentation, built right into your tests, so it is a great option for incorporating with your Protractor tests. It also allows you to better organize suites of tests together with tags and hooks. Cucumber.js is the well-documented Javascript implementation of the framework and can be easily incorporated in your Protractor tests.
Prerequisites
There are a few things needed before you can work with Protractor. Make sure you have the latest versions of the following installed:
Protractor requires Node and the development kit is needed for the Selenium Server.
Starting with Protractor
Install Protractor by running npm install -g protractor
. You can double- check your installation by running protractor --version
.
Letโs take a look at the structure of a Protractor test. There are two core files needed for a suite to run โ a spec file and a configuration file. The spec file contains the code needed to interact with the browser. The config file sets up the environment, framework, capabilities, and where to find your specs.
Below are examples of basic config and spec files:
//protractor.conf.js
exports.config = {
seleniumAddress: 'http://localhost:4444/wd/hub',
specs: ['*.spec.js'],
baseURL: 'http://localhost:8080/',
framework: 'jasmine',
};
//test.spec.js
describe('Protractor Test', function() {
var addField = element(by.css('[placeholder="add new todo here"]'));
var checkedBox = element(by.model('todo.done'));
var addButton = element(by.css('[value="add"]'));
it('should navigate to the AngularJS homepage', function() {
browser.get('https://angularjs.org/'); //overrides baseURL
});
});
The describe
and it
blocks within test.spec.js are specific to Jasmine, the default BDD framework for Protractor.
In order to run Protractor, you will need to first start the Selenium Server. Protractor includes a webdriver-manager tool that starts up your server. In a separate terminal tab, run the webdriver-manager update
command. This downloads the necessary selenium server and chromedriver components. Then run webdriver-manager start
to start up the server.
Then you can use protractor protractor.conf.js
in the terminal to run the test spec. Now, letโs incorporate Cucumber features into your Protractor suite.
Cucumber Setup
Note: With the latest versions of Protractor (3.x), Cucumber is no longer included by default so you will use the custom framework option.
First, you need to install Cucumber with npm install -g cucumber
. Make sure it is installed in the same place as Protractor. Next, youโll have to install the protractor-cucumber-framework with npm install --save-dev protractor-cucumber-framework
. This iteration is designed with Protractor 3.x in mind. When the installation completes, you can write out a feature. In the project, create a features folder with a test.feature feature file. All of your features will be housed in this folder.
Below is an example of a feature file:
#features/test.feature
Feature: Running Cucumber with Protractor
As a user of Protractor
I should be able to use Cucumber
In order to run my E2E tests
Scenario: Protractor and Cucumber Test
Given I go to "https://angularjs.org/"
When I add "Be Awesome" in the task field
And I click the add button
Then I should see my new task in the list
Save the file and run cucumber.js
to see how Cucumber processes the feature. Below is a snippet:
Feature: Running Cucumber with Protractor
As a user of Protractor
I should be able to use Cucumber
In order to run my E2E tests
Scenario: Protractor and Cucumber Test
Given I go to "https://angularjs.org/"
When I add "Be Awesome" in the task field
And I click the add button
Then I should see my new task in the list
1) Scenario: Protractor and Cucumber Test - features/test.feature:6
Step: Given I go to "https://angularjs.org/" - features/test.feature:7
Message:
Undefined. Implement with the following snippet:
this.Given(/^I go to "([^"]*)"$/, function (arg1, callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
1 scenario (1 undefined)
3 steps (3 undefined)
0m00.000s
Cucumber creates the necessary steps that your spec, called step definition, needs automatically based on the feature file.
this.Given(/^I go to "([^"]*)"$/, function (arg1, callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
You can copy and paste those code snippets into a new step definition file. Save that file in a new step_definitions folder within the features folder. From there, you can write out your steps using Protractor functions and locators. Your step definition will look similar to this:
//features/step_definitions/my_step_definitions.js
module.exports = function() {
this.Given(/^I go to "([^"]*)"$/, function(site, callback) {
browser.get(site)
.then(callback);
});
What is that .then(callback);
for? It letโs Cucumber know itโs time to move on to the next step. However, asynchronous behavior sometimes isnโt needed, so omitting the callback
parameters is acceptable. Read more on how Cucumber.js handles promises and asynchronous behavior in the projectโs repository.
Now, in order to run these, you will need to make a few adjustments to the protractor.conf.js file. As mentioned before, Cucumber is no longer included by default for Protractor 3.x so you will pass in the custom option for your framework plus a few extras for the Cucumber framework itself.
//protractor.conf.js
exports.config = {
seleniumAddress: 'http://127.0.0.1:4444/wd/hub',
getPageTimeout: 60000,
allScriptsTimeout: 500000,
framework: 'custom',
// path relative to the current config file
frameworkPath: require.resolve('protractor-cucumber-framework'),
capabilities: {
'browserName': 'chrome'
},
// Spec patterns are relative to this directory.
specs: [
'features/*.feature'
],
baseURL: 'http://localhost:8080/',
cucumberOpts: {
require: 'features/step_definitions/stepDefinitions.js',
tags: false,
format: 'pretty',
profile: false,
'no-source': true
}
};
The setup is still very similar to your original config file. To use Cucumber.js, you should update the framework and add a framework path to include the module downloaded earlier. Next, add a few cucumberOpts that specify where to find the step definition files, any necessary tags, the desired output format, and if a profile is needed.
You can now run the protractor cucumber.conf.js
command and you should see a browser pop up and navigate to the desired URL. You have successfully coupled Cucumber with your Protractor tests.
Assertions: Chai and Chais-As-Promised
Since youโre using the custom framework option with Protractor youโll need to add an assertion library like Chai โ a popular choice. Chai allows us to write assertions in a simple, readable style โ such as the familiar expect syntax in expect(element.getText()).to.eventually.equal('Name');
.
To install, you should run npm install chai chai-as-promised
. Within the step definition file, add the following lines to the top of the file:
var chai = require('chai');
var chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
var expect = chai.expect;
You will now be able to include assertions in your tests. Letโs build out your previous test more to see Chai in action.
//features/step_definitions/my_step_definitions.js
var chai = require('chai');
var chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
var expect = chai.expect;
module.exports = function() {
this.Given(/^I go to "([^"]*)"$/, function(site) {
browser.get(site);
});
this.When(/^I add "([^"]*)" in the task field$/, function(task) {
element(by.model('todoList.todoText')).sendKeys(task);
});
this.When(/^I click the add button$/, function() {
var el = element(by.css('[value="add"]'));
el.click();
});
this.Then(/^I should see my new task in the list$/, function(callback) {
var todoList = element.all(by.repeater('todo in todoList.todos'));
expect(todoList.count()).to.eventually.equal(3);
expect(todoList.get(2).getText()).to.eventually.equal('Do not Be Awesome')
.and.notify(callback);
});
};
With this test, you should see a failed step in your output. You added โBe Awesomeโ to the task list, but are expecting to see โDo not Be Awesomeโ in the list. Your test fails as expected, with the following assertion error:
Failures:
1) Scenario: Protractor and Cucumber Test - features/test.feature:6
Step: Then I should see my new task in the list - features/test.feature:10
Step Definition: features/step_definitions/stepDefinitions.js:23
Message:
AssertionError: expected 'Be Awesome' to equal 'Do not Be Awesome'
1 scenario (1 failed)
4 steps (1 failed, 3 passed)
Now that Cucumber is incorporated, you can see which step failed along with the assertion error. This makes things a little easier when troubleshooting failures as it gives the context of the failure within the output. Changing โDo not Be Awesomeโ to โBe Awesomeโ should pass your test.
Tips and Tricks
Even though you have the core test structure set up now, there are a few enhancements worth considering.
- Move the Chai and Chai-as-promised plugins to Cucumberโs world.js. This will keep you from having to add those to each step definition file. Check out the Cucumber.js repo for more information on support files.
- Use Cucumber tags and profiles with
protractor cucumber.conf.js --cucumberOpts.tags @tagName
orprotractor cucumber.conf.js --cucumberOpts.profile Name
. Donโt forget to add your tags and profiles under CucumberOpts in your configuration file. - Use Page Objects in your tests. That will allow you to store each of your locators in one location, making them easier to manage. The Protractor wiki has a great resource for creating simple, efficient Page Objects.
Conclusion
To recap, you learned about the basic structures of both Protractor and Cucumber.js frameworks and how to build features that run seamlessly with the Angular testing tool. Incorporating Cucumber with Protractor is fairly simple and it gives you the opportunity to create tests that are readable for your team as well as build simple documentation for your application at the same time. Have you used Cucumber with your Protractor tests? Let us know!