Transcripted Summary

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

    • Given the browser is at the “Home” page
    • 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

    • Given the browser is at the homepage
    • When the user searches for “printed”
    • Then search results show products related to “printed”

# Okay, now that we know what these scenarios do, let's automate.

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.


# features/Search/DressSearch.feature

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.


# pages/Catalog.js

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.


# pages/Home.js

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”.


# support/actions/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.


# steps/Home/given.js

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


# support/actions/search.js

import home from "../../pages/Home";

export default keyword => {
  home.search(keyword);
};

And define the When step that calls that action.


# steps/Home/when.js

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.


# support/assertions/checkProducts.js

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.


# support/assertions/checkNoResultsError.js

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.


# support/assertions/checkProductsContain.js

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.


# steps/Catalog/then.js

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.



Resources



Quiz

The quiz for this chapter can be found in section 5.3

© 2024 Applitools. All rights reserved. Terms and Conditions Privacy Policy GDPR