In this chapter, we will be focusing on writing automated tests with Cucumber. We'll be creating tests for the automationpractice.com application.
It's your typical eCommerce site with products, a shopping cart, and the login capabilities.
I have already prepared some Gherkin statements that will allow us to dive right into creating automation.
In this first section, we'll be focusing on these Scenario
and Background
keywords.
Let's get into it.
So, here we are presented with two scenarios
Let's go through them and see how they should work.
Feature: Dress search — As a user, I want to be able to search for dresses by type, so I can purchase one.
Scenario: Search for a type that does not exist
Scenario: Search for a type that exists
I'll be using the same architecture from the previous chapter, so you will see my WebDriverIO file wdio.conf.js has the same configurations from that chapter, and I already have empty folders to store all my files.
Let's start by creating the Feature File for this scenario.
I'll put this in a sub folder called “search”.
I see that we have a duplicate given step, so this is a good time to use the Background
keyword. If you remember, Backgrounds are what we can use to repeat a series of steps for scenarios,
So, let's move the Given
step to our Background
.
Feature: Dress Search
As a user, I want to be able to search for dresses by type so I can purchase one
Background:
Given the browser is at the "Home" page
Scenario: Search for a type that does not exist
When the user searches for "floral"
Then a no results error message is shown
And no products are listed
Scenario: Search for a type that exists
When the user searches for "printed"
Then products are listed
And search results show products related to "printed"
Okay, now that we have that setup, I'm going to move on to the Page Objects.
These tests only work with 2 pages — I'll call one the “Home” Page, and the other the “Catalog” Page, which is the page that displays products.
I already have the selectors we'll be using, so I'll just copy those in — the search input field, the search button, the no results error message, and the products on the catalog page.
class Catalog {
get noResultsErrorMessage() {
return $(".alert.alert-warning");
}
get products() {
return $$("#search .product_list .product-container .product-name");
}
}
module.exports = new Catalog();
Don't forget our URL for the homepage.
We won’t have a URL for the catalog page since this URL is based on the search
file.
As for the methods, both of these scenarios only require 1 function, which is our search on the home page, so I'll create that now.
class Home {
get url() {
return "/";
}
get searchField() {
return $("#search_query_top");
}
get searchButton() {
return $(".btn.btn-default.button-search");
}
/**
* Searches for a keyword
* @param {String} keyword Search term
*/
search(keyword) {
this.searchField.waitForDisplayed(2000);
this.searchField.setValue(keyword);
this.searchButton.click();
}
}
module.exports = new Home();
The steps are pretty similar to the previous chapter — we'll wait for the input field to be displayed, the value using the keyword, and click the search button.
Okay, let's take a look at our first step.
Given the browser is that the home page — what I want to do with this is create a modular function to go to any page based on whatever name is used in the step.
So, I'll create this function in our “actions” folder and call it “goToPage.js”.
import home from "../../pages/Home";
export default page => {
switch (page) {
case "Home":
browser.url(home.url);
break;
default:
console.log(`Invalid Page ${page}`);
}
};
What I have here is a switch
case that checks the page parameter.
If it's equal to “Home”, I would go to the home page URL
Otherwise, it will print out “Invalid Page”
This is good enough for us to use in the test we need to create.
Next up is to call this function in the Step Definition for our Given
step.
I'll create a “home” and “catalog” folder and create the “given”, “when”, and “then” files in them.
Now, I define our Given step
— be sure to put this code in the “given” file that's in the “home” folder.
import { Given } from "cucumber";
import goToPage from "../../support/actions/goToPage";
Given(/^the browser is at the "(Home)" page$/, page => {
goToPage(page);
});
If you notice my regular expression, I have the literal “Home”.
The reason is I imagine that in some other tests, we'll want to start on a different page. This allows me to limit what pages I expect to go to.
Let's head to RegExr and see how this expression will work.
So, you see, it matches a statement when the page name is “Home”.
Let's try adding another expected page in the expression. And if we were to have a step that doesn't match any of these?
Great.
Let's move on to defining our When
step.
The only when action we need to create is a search. This will be an identical implementation from Chapter 4, so I'll just copy and paste this in.
I'll create a search action that calls the homepage search function
import home from "../../pages/Home";
export default keyword => {
home.search(keyword);
};
And define the When
step that calls that action.
import { When } from "cucumber";
import search from "../../support/actions/search";
When(/^the user searches for "(.*)"$/, keyword => {
search(keyword);
});
Okay, on to the Then
steps.
Based on our scenarios, you may want to define four Then
steps. We’ll take a deeper look at them.
The last step for scenario 1 says, "And no products are listed".
The second to last step in scenario 2 says, "Then products are listed."
These are identical except for the “no” part of the statement. We can use regular expressions to create a 1-step definition for these.
Let's head to RegExr and see what we can come up with.
I'll put both steps in the text area and create that regular expression to match the first one.
What we can do with regular expression is make some value optional with a question mark.
So, with that we can match either of our steps.
Let's add this to our Then
step.
Remember, we need to capture our variable, which will either be:
Defined as “no”, if this step says “no products are listed”
It would it be undefined if the step reads “products are listed”
So, now we need to decide what to do in either case.
For this, let's create an assertion named “checkProducts”.
This will take 1 variable — toBeFound
— and either a thread that there are products or that there are no products, so we can use the 1 function in our Step Definition.
import catalog from "../../pages/Catalog";
import assert from "assert";
/**
* @param {boolean} toBeFound If true, products should be present, else they should not be present
*/
export default toBeFound => {
const products = catalog.products;
if (toBeFound) {
assert(products.length > 0, "No products were found");
} else {
assert(products.length === 0, "Products were found");
}
};
We can use a products
element from the catalog
page and say, "If to be found is ‘true’, verify that list of products is greater than 0. Else, if to be found as ‘false’, verify that the list of products is 0."
Now that we have that, we can implement it in our Then
step.
Then(/^(no )?products are listed$/, notListed => {
if (notListed) {
checkProducts(false);
} else {
checkProducts(true);
}
});
So, just to recap:
If the step reads, "No products are listed," we will call our checkProducts
action without “false” parameter, which will cause it to assert that there are no products on the page.
If the step reads, "Products are listed," we will call our checkProducts
action with a “true” parameter, which causes it to assert that products are on the page.
We still have two more Then
steps to define, but let's try to run these to see if we have any unexpected errors.
Looking good so far. The errors we're seeing here are because of our undefined steps.
The next Then
step I'll define is for “no results error messages is shown”.
For this, I'll start by creating assertion method called “checkNoResultsError”.
I'll just have it get the “no result” error message selector from the catalog page and assert
that its text contains “no results were found for your search”, as we saw on the catalog page a no product match to our search.
import catalog from "../../pages/Catalog";
import assert from "assert";
export default () => {
const noResultErrorMessage = catalog.noResultsErrorMessage;
assert(
noResultErrorMessage
.getText()
.includes("No results were found for your search"),
"No results error message was not found"
);
};
For this Step Definition, I'll use a String instead of a regular expression just because there isn't anything to parameterize here.
And then call our assertion.
Then("a no results error message is shown", () => {
checkNoResultsError();
});
Okay, on to the last Then
step — “search results show products related to ‘printed’”.
This one will also be similar to our verify links assertion in the previous chapter.
I'll create an assertion called “checkProductsContain”.
Get the products
element from the Catalog
Page Project, and iterate over them to verify it contains our keyword
.
import catalog from "../../pages/Catalog";
import assert from "assert";
/**
* @param {boolean} keyword search term to be present
*/
export default keyword => {
const products = catalog.products;
products.forEach(product => {
const productText = product
.getText()
.trim()
.toLowerCase();
if (productText) {
console.log(productText);
assert(
productText.includes(keyword),
`Product ${product.getText()} does not contain ${keyword}`
);
}
});
};
What I'll do here as well is log out those product names, just so I can do some manual verification of the product names myself.
And now, I'll implement this function in the Then
step definition file.
import { Then } from "cucumber";
import checkNoResultsError from "../../support/assertions/checkNoResultsError";
import checkProducts from "../../support/assertions/checkProducts";
import checkProductsContain from "../../support/assertions/checkProductsContain";
Then(/^(no )?products are listed$/, notListed => {
if (notListed) {
checkProducts(false);
} else {
checkProducts(true);
}
});
Then("a no results error message is shown", () => {
checkNoResultsError();
});
Then(/^search results show products related to "(.*)"$/, keyword => {
checkProductsContain(keyword);
});
Now that we have all of our steps, let's run our tests.
Great. All of our tests are passing, and here is a log of our product names.
All of them do, in fact contained “printed”.
Let's next move onto the next section where we can implement some more tests.
Quiz
The quiz for this chapter can be found in section 5.3