Part 5. Creating your First Proper Test

Creating your First Proper Test

Search for “Reddit” on DuckDuckGo and Select a Search Result

Creating the Cucumber Scenario

When creating our BDD scenarios, we want to keep them simple and write them in a declarative style, sticking simply to the business logic, rather than writing them in an imperative style.  A great article on this can be found at http://itsadeliverything.com/declarative-vs-imperative-gherkin-scenarios-for-cucumber.

  1. In your code editor (e.g. VS Code), right-click on the src/resources/features directory, select ‘New File’ and create a file called ‘search-scenarios.feature’
  2. Open up the ‘search-scenarios.feature’ file and add the following test scenario….
    Feature: Search Scenarios
     As a user of DuckDuckGo, I want to be able to search for stuff
     
     Scenario: 01. Search and select a result
        Given I am on the search page
        When I search for "Reddit homepage"
        And I view the first result
        Then I see the Reddit homepage

    If you’ve been following all the blog posts in this series, you will notice that we have reused some existing steps in this scenario, such as;
    – “Given I am on the search page”

    The other undefined step definitions will be highlighted as being undefined still.

Creating the Undefined Step Definitions

  1. Open up the Terminal (or Command prompt) window and change directory (cd) in to the root of your WebDriverIO project again…
    cd your/Project/Root
  2. Run your test suite with the following command…
    npm test

      The test will obviously fail, but you will get output in the console like below…

    *************************************************************************
    ** For Feature: Search Scenarios
    ** Step Definitions are not specified
    ** Copy and Paste the following snippet(s) into your step definitions
    
    Given(/^I search for "([^"]*)"$/, (arg1) => {
        // Write code here that turns the phrase above into concrete actions
        return 'pending';
    });
    
    Given(/^I view the first result$/, () => {
        // Write code here that turns the phrase above into concrete actions
        return 'pending';
    });
    
    Given(/^I see the Reddit homepage$/, () => {
        // Write code here that turns the phrase above into concrete actions
        return 'pending';
    });
  3. Copy the ‘Given I search for…’ pending step definition and paste it in to the ‘send-keys-steps.js’ file, changing the ‘Given’ to a ‘When’ and renaming the argument to something appropriate, like below. This step definition will contain an action of sending keys (a search term) to an input field (the search box). We will put all our steps that send keys in this step definition file…
    import { Given, When, Then } from 'cucumber';
    
    When(/^I search for "([^"]*)"$/, (searchTerm) => {
      // Write code here that turns the phrase above into concrete actions
      return 'pending';
    });
  4. Copy the ‘Given I view the first result…’ pending step definition and paste it in to the ‘click-steps.js’ file, again changing the ‘Given’ to a ‘When’, like below.  This step definition will contain an action of clicking on an element.  We will contain all of our step definitions that perform click actions in this click step definition file…
    import { Given, When, Then } from 'cucumber';
    
    When(/^I view the first result$/, () => {
      // Write code here that turns the phrase above into concrete actions
      return 'pending';
    });
  5. Copy the ‘Given I see the Reddit homepage…’ pending step definition and paste it in to the ‘validation-steps.js’ file, changing the ‘Given’ to a ‘Then’, like below…
    import { Given, When, Then } from 'cucumber';
    
    Then(/^I see the Reddit homepage$/, () => {
      // Write code here that turns the phrase above into concrete actions
      return 'pending';
    });

Defining the Step Definitions

‘When I search for…’ step definition

Although we enter a search term on the base page, it is not really a generic function that can be done across multiple pages, so we will create a new search page object for this. Although called page objects, we ideally will make new page objects for each component under test, it has become the norm to call them page objects though.

  1. In your code editor (e.g. VS Code), right-click on the ‘page-objects’ folder and select ‘New File’
  2. Add a new page object file called ‘search.page.js’
  3. Open up the ‘search.page.js’ file in the ‘/page-objects’ folder and add the following class that extends from the base page object, with a super constructor, like so (if you don’t include the super constructor you will get an error when you run your tests like “Must call super constructor in derived class before accessing ‘this’ or returning from derived constructor”)…
    import Page from './base.page';
    
    export default class SearchPage extends Page {
      constructor() {
        super();
      }
    }
  4. Add the following selector to be stored as a property inside our SearchPage class (if we go to https://start.duckduckgo.com in a browser like Chrome and right click on the search field and select ‘Inspect Element’, we can see that the element has a ‘name’ value of ‘q’, which we can use to interact with/find the element)…
    get searchField() { return $('[name="q"]'); }
  5. Next, let’s add a property for the search button selector (if we now inspect the search button element in the browser, we can see that it has an ID of ‘search_button_homepage’, which we can use to interact with/find the element. In our CSS selector, we can use ‘#’ for ID’s and ‘.’ for classnames)…
    get searchButton() { return $('#search_button_homepage'); }
  6. Finally, let’s add a function that uses the two properties we just added to enter in a search term and submit the search…
    searchFor(searchTerm) {
      this.searchField.setValue(searchTerm);
      this.searchButton.click();
    }

    The whole thing should look like below…

    import Page from './base.page';
    
    export default class SearchPage extends Page {
      constructor() {
        super();
      }
    
      get searchField() { return $('[name="q"]'); }
      get searchButton() { return $('#search_button_homepage'); }
    
      searchFor(searchTerm) {
        this.searchField.setValue(searchTerm);
        this.searchButton.click();
      }
    }
  7. Next, open up the ‘send-keys-steps.js’ step definition file and import the search page object…
    import searchPage from '../page-objects/search.page';
  8. Now let’s edit the undefined ‘When’ step to send keys (setValue) to call the searchFor() function we just added in our search page object…
    When(/^I search for "([^"]*)"$/, (searchTerm) => {
      searchPage.searchFor(searchTerm);
    });

    The whole thing should look like below…

    import { Given, When, Then } from 'cucumber';
    import searchPage from '../page-objects/search.page';
    
    When(/^I search for "([^"]*)"$/, (searchTerm) => {
      searchPage.searchFor(searchTerm);
    });

‘When I view the first result’ step definition

We will now define our step for clicking the first search result link that is returned.  This is done on the search results page, so we can add it in our ‘search-results.page.js’ page object file. We will also need to make sure the search results have loaded in, before trying to click on them.

  1. In your code editor (e.g. VS Code), right-click on the ‘page-objects’ folder and select ‘New File’
  2. Add a new page object file called ‘search.page.js’
  3. Open up the ‘search-results.page.js’ file in the ‘/page-objects’ folder and add the following class that extends from the base page object, again with a super constructor, like below…
    import Page from './base.page'; 
    
    export default class SearchPage extends Page { 
      constructor() {
        super();
      }
    }
  4. Next, let’s add a function that will check that the search results are visible and then click on one of the resultsLinks (within this class) based on an index that we pass to it, like below…
    selectResultAtIndex(index) {
      this.resultsLinks[index].click();
    }

    The whole thing should look like below…

    import Page from './base.page';
    
    export default class SearchResultsPage extends Page {
      constructor() {
        super();
      }
    
      get results() { return $('#links'); }
      get resultsLinks() { return $$('.result__a'); }
    
      selectResultAtIndex(index) {
        this.results.isVisible();
        this.resultsLinks[index].click();
      }
    }
  5. Next, let’s open up our ‘click-steps.js’ step definition file and add an import for our ‘search results’ page object at the top…
    import searchResultsPage from '../page-objects/search-results.page';
  6. Finally, let’s define our step definition to click on the result link at index 0 for the resultsLinks property we just added in our page object…
    When(/^I view the first result$/, () => {
      searchResultsPage.selectResultAtIndex(0);
    });

    The whole thing should look like below…

    import { Given, When, Then } from 'cucumber';
    import searchResultsPage from '../page-objects/search-results.page';
    
    When(/^I view the first result$/, () => {
      searchResultsPage.selectResultAtIndex(0);
    });

‘Then I see the Reddit home page…’ step definition

We will now define our step for asserting that we are on the Reddit page, by checking that an element on the Reddit page is visible and also calling our base validation functions to check that things like the page URL, title and source are correct.

  1. First, let’s create a new page object for the Reddit home page. In your code editor (e.g. VS Code), right-click on the ‘page-objects’ folder and select ‘New File’
  2. Name the file ‘reddit.page.js’
  3. Open up the ‘reddit.page.js’ file and add the following class, again extending from the base page object and with a super constructor, like below…
    import Page from './base.page';
    
    export default class RedditPage extends Page {
      constructor() {
        super();
      }
    }
  4. Next, let’s add a property for a selector on the reddit page.  If we look at the Reddit page in a browser, we can inspect and see that most elements have dynamic IDs which change every visit, so for ease, let’s play it safe and just grab the classname of the promoted link (I tried a few other static looking element IDs and classnames but they didn’t always work)
    get promotedLink() { return $('.promotedlink'); }

    The whole thing should look like below…

    import Page from './base.page';
    
    export default class RedditPage extends Page {
      constructor() {
        super();
      }
    
      get promotedLink() { return $('.promotedlink'); }
    }

    Now we can finally define our pending step 🙂

  5. Go back to the ‘validation-steps.js’ file and add another import at the top for the new reddit page object we previously created…
    import redditPage from '../page-objects/reddit.page';
  6. Next, edit the pending ‘When’ step to assert that the ‘promoted link’ on the Reddit page is visible, as well as assert that the page url is correct, the page title is correct, and the page source contains an appropriate string, like below…
    Then(/^I see the Reddit homepage$/, () => {
      expect(redditPage.promotedLink.isVisible());
      expect(redditPage.title).to.contain('reddit:');
      expect(redditPage.url).to.contain('https://www.reddit.com');
      expect(redditPage.source).to.contain('Reddit');
    });

    The whole thing should look like below…

    import { Given, When, Then } from 'cucumber';
    import redditPage from '../page-objects/reddit.page';
    
    Then(/^I see the Reddit homepage$/, () => {
      expect(redditPage.promotedLink.isVisible());
      expect(redditPage.title).to.contain('reddit:');
      expect(redditPage.url).to.contain('https://www.reddit.com');
      expect(redditPage.source).to.contain('Reddit');
    });

It is important that we keep any validation and assertions outside of our page objects, which is why we put them in our step definition directly.  A great article on this can be found at https://essenceoftesting.blogspot.com/2012/01/assertions-and-validations-in-page.html

If we now run our tests via 

npm test

 , they should all pass 😀