How to use Cucumber and Selenium WebDriver effectively for BDD test automation (part 2)
This is in continuation of PART 1 where a basic project setup was discussed and the cucumber feature file for this tutorial was created. Our focus here will be the implementation of step-definitions.
So, the file structure should be modified to:
e2e
├── config.js
│ │
├── features
│ │
│ ├── app.feature
│ │
│ ├── step-definitions
│ │ ├── app.js
│ │
│ ├── common
│ │ ├── action.js
│ │ ├── browser.js
│ │ ├── selectors.js
│ │
│ ├── support
│ │ ├── world.js
│ │ ├── hooks.js
Update the files accordingly:
e2e/features/common/browser.js
const {By, until} = require('selenium-webdriver');
const {timeout} = require('../../config');
function browser(driver){
function waitAndLocateByCSS(selector){
return driver.wait(until.elementLocated(By.css(selector)), timeout);
}
function waitAndLocateByXpath(selector){
return driver.wait(until.elementLocated(By.xpath(selector)), timeout);
}
return {
waitAndLocateByCSS,
waitAndLocateByXpath
};
}
module.exports = browser;
e2e/features/common/selectors.js
module.exports = {
'email input': 'input[name="identifier"]'
'password input': 'input[name="password"]'
'Sign in', '//a[contains(text(), "Sign in")]',
'Create account', '//a[contains(text(), "Create")]',
'Next': '//span[contains(text(), "Next")]'
};
e2e/features/common/action.js
const {baseURL, email, password} = require('../config');
const helper = require('./common/browser');
const selector = require('./common/selector');
const action = {
navigateToPage: function() {
return this.driver.get(baseURL);
},
enterInput: function(text, inputField) {
const itemSelector = selector[inputField],
const mapText = {
'<email>': email,
'<password>': password
};
const value = mapText[text] || text;
return helper(this.driver).waitAndLocateByCSS(itemSelector).sendKeys(value);
},
click: async function(identifier) {
const itemSelector = selector[identifier],
await helper(this.driver).waitAndLocateByXpath(itemSelector).click();
},
confirmUserId: async function() {
await helper(this.driver).waitAndLocateByXpath(`//a[contains(@aria-label, "${email}")]`);
},
confirmTextVisibility: async function(text) {
await helper(this.driver).waitAndLocateByXpath(`//*[contains(text(), "${text}")]`);
},
confirmMultipleTextVisibility: async function(dataTable) {
// convert cucumber dataTables to array of objects
const arrayOfObjects = dataTable.hashes();
const locateText = [];
arrayOfObjects.forEach((value) => {
const textArray = Object.values(value);
const [text] = textArray;
locateText.push(helper(this.driver).waitAndLocateByXpath(`//*[text()="${text}"]`));
});
await Promise.all(locateText);
}
};
module.exports = action;
e2e/features/step-definitions/app.js
const {Given, When, Then} = require('cucumber');
const {
navigateToPage,
enterInput,
click,
confirmTextVisibility,
confirmMultipleTextVisibility,
confirmUserId
} = require('../common/action');
Given('A user visits mail.google.com', navigateToPage);
Then(/^the user should see the following .*$/, confirmMultipleTextVisibility);
When(/^the user clicks on '(.*)' .*$/, click);
When ('the user enters {string} into the {string} field', enterInput);
Then(/^the user should see '(.*)' .*$/, confirmTextVisibility);
Then('the user should see the Google Account used to sign in to the email', confirmUserId);
Part 3 (coming soon) will focus on how to write resilient BDD tests
I love cucumber, was a bit lost thought about how to deal with selector in a clean and maintenable way… this solution could be nice. Thanks a lot !
You’re welcome
Hey Marcus,
Nice guide. However, several lines of code in the examples are incorrect. I.e. extra ‘,’ or missing semicolons etc.
I adapted this using the latest @cucumber/cucumber repo and all worked fine with a few tweaks. Look forward to part 3…
Can you send me your project file? (adapt in @cucumber/cucumber repo) I need to learn for my educaion. Please tarosliced@gmail.com
I’m glad the article was helpful 😊