Starting an Automation Framework: Part 2

A Step-by-Step Guide to Using Javascript

Welcome to part two of this series on starting an automation framework. In part one, we went over requirements and we learned how to design Feature files. Read on as we dive deeper into modeling the framework, Steps definitions and Page Objects pattern.

  1. Steps definition

Yay, we have the scenario! We’re done, right? Not quite. We may have the scenario, but those scenarios aren’t doing anything right now. So, in order to give life to this scenario we have to map the steps to something. In order to do this, let’s have a folder called step_definitions on the same level with the feature file we’ve just created. In this folder, we’re going to create a file with the .js extension, for example, googleSearch.js. In this file we’re going to add the following lines:

var { Given, When, Then } = require("cucumber");

 

Given("I am on the google landing page", function() {

});

 

When("I am performing a search by word {string}", function(text) {

});

 

Then("Google page displays some results for my search", function() {

});

 

When("I click on the first result", function() {

});

 

Then("Selenium page should be opened", function() {

});

Wait a second, that function thing again. What’s this all about? If you’re having a Java or C# background, this might look a little bit odd to you. But yes, Javascript has something called functions. But it also has methods as well. 

A function, as the definition states, is a block of code designed to perform a particular task. But isn’t the method doing the same thing? Actually, it does, with a difference.  In order to use a method, you’ll need an object (object.method()) whereas the function doesn’t need one. So, each step of our scenario is mapped to an empty function. There is also a function which takes a parameter, which will be passed from the feature file so that the function is more flexible, we don’t want to hardcode things, right? Right. Also, the first line from this file says: “Please give me the power to use Given, When, Then keywords from the Cucumber library”. It’s that easy. 

Moving along! At this point, you can already run your tests, but surprise, surprise, it won’t do anything. Because we have empty functions. That makes sense, but how should we run this? Remember the IDE from requirements? That’s right, we’re going to use it. Open a command line terminal and go the folder where the project resides (features folder and package.json level) and issue the following command “code .”. Don’t worry, this is just a shortcut for opening Visual Studio Code in this particular folder. Now, that Visual Studio Code is opened, you can probably navigate and see your files (if everything is right). Go to the package.json file and open it. In the file, locate the “scripts” field and next to “test” field write down “./node_modules/.bin/cucumber-js” (delete what’s there by default). Save the file.

We’re ready to run our empty test scenario. Go to the Terminal tab from Visual Studio Code and select New Terminal. You’ll notice that a new terminal is opened on the bottom of the IDE (surprise, surprise!). Here, you can issue a npm test command and VSC should work its magic.

The result should look like this:

Everything is green, we should go home and celebrate! Not so fast, it’s green because it doesn’t test anything, it just runs a bunch of empty functions. Again, take a look at how the file with the functions looks like in Visual Studio Code.

Yep, empty. Now, let’s fill up those methods in order to have something for real.

  1. Page Objects Pattern

This may sound a little frightening if this is your first encounter with the Page Objects design pattern. But it’s not, really (hopefully). It’s a smart way to separate the tests from the actual internal implementation of each page you’re interacting with. This is useful in order to avoid modifying the whole test when something minor from the page is changed. That way, your tests remain the same, you only have to tinker with the implementation of the page. Simple as that. So, let’s do this with our framework. Let’s think for a moment (not that we’re not doing it already), the scenario should look like this: Go to Google page → Write down the word to search after → Press Search → Click on the first link, Selenium link → Make some checks on the Selenium page. 

Now that we have the scenario in mind, we need to identify the following elements: the search bar, the search button (we’ll use a shortcut), first Selenium result and some elements from the Selenium page. But because we’re talking about Page Objects, we’ll treat each page separately. We’ll start with the Google Search page first.

On the same level with the step_definitions folder and the feature file, create a new folder called page_objects. Here, we’re going to add the logic for each page we’re interacting with. Let’s start with the first one. Create a file with the .js extension called searchPage.js and the first thing you’re going to tackle here is the identification of your elements, therefore:

  • Google search bar, let’s call it searchInput – seleniumWebdriver.By.name(“q”)
  • Search button – we’re going to use a shortcut by pressing the Enter key, so we’ll skip this one
  • Search results; we’re just interested if something is found, so we’re going to pick the first result from the list – namely searchResultLink – seleniumWebdriver.By.css(“div.g”)
  • Selenium link – seleniumLink – seleniumWebdriver.By.xpath(“//h3[contains(text(), ‘Selenium’)]”

As you’ve probably noticed, different locating strategies were used: By name, by css and by xpath but you can use whatever you’re comfortable with. As a matter of fact, it would be beneficial to experiment with using other locating strategies. After identifying the elements, let’s create an object out of it: 

const searchPageOjects = {

              searchInput: seleniumWebdriver.By.name("q"),

              searchResultLink: seleniumWebdriver.By.css("div.g"),

              seleniumLink: seleniumWebdriver.By.xpath("//h3[contains(text(), 'Selenium')]")

         };

That’s right, that’s an object in Javascript. This is just for holding the information in a structured form. The next thing we need there is the suite of functions that will work with those elements. If you feel the urge to add some other methods, which are not related with this page or these elements, please don’t, that’s why it’s called “Page Objects.” Now, let’s start with the related functions:

  1. The function that writes down the word to search after and presses Enter – searchAfterWord

const performSearch = {

  searchAfterWord: async function(driver, stringToSearch) {

       await driver.wait(until.elementLocated(searchPageOjects.searchInput), 100000);

    await driver.findElement(searchPageOjects.searchInput).sendKeys(stringToSearch, seleniumWebdriver.Key.ENTER);

  }

};

     2. The function that checks that some results are returned after the search is performed – checkSearchResult

const checkSearchResult = {

  checkResultAfterSearch: async function(driver) {

    await driver.wait(until.elementLocated(searchPageOjects.searchResultLink), 100000);

    let result = await driver.findElement(searchPageOjects.searchResultLink).getText();

    expect(result.length).to.not.equal(0);

    expect(result).to.have.string("Selenium automates browsers");

  }

};

  1. The function that clicks on the Selenium link after the search is performed – clickOnSeleniumLink

const clickOnSeleniumLink = {

  openSeleniumResult: async function(driver) {

    await driver.wait(until.elementLocated(searchPageOjects.seleniumLink), 100000);

    await driver.findElement(searchPageOjects.seleniumLink).click();

  }

};

Now, some explanations. You can see that each function has a name. But the name is written before the colon? Why is that? Well, go back two steps and take a look at this structure again. There is a constant over there (it can easily be a var) and after that, there is the name of the function, then the function, looking exactly like a name:value pair. Go back to the identification of the elements and check that structure again? Sound familiar?  That’s right, again, we’re using objects. Of course, there are more ways to use functions in Javascript, but this looks elegant, so we’re going to use it.What about that odd await? What about the async, placed just before the function?

Don’t worry. Those keywords are here to help us. You see, because of the asynchronous nature of Javascript (Javascript is synchronous and single-threaded at its base), after a statement is executed, Javascript does not wait around for it to finish, it goes ahead with executing the next one. This is bad for us, because that wait for the element to be located is crucial for our tests. We need to be sure that the element is located before advancing. That’s what await keyword is doing for us, it blocks the thread until the element is located. In order to use the await keyword in a function you should mark that function as async. There are numerous blogs and books, if you’re really interested in the history of the Promises (yes, this is a big topic in Javascript) but this is neither the time nor the place for this. Let’s get back to our code. We’re having the functions, but we need to use them to fill the empty functions from googleSearch.js file (step_definitions folder). In order to do that, we need to export them:

exports.searchPageOjects = searchPageOjects;

exports.performSearch = performSearch;

exports.checkSearchResult = checkSearchResult;

exports.clickOnSeleniumLink = clickOnSeleniumLink;

If everything was done correctly, searchPage.js file should look like this:

Don’t worry about the first three lines, they just give you the possibility to use certain features like waits (until, for synchronize the flows), assertions (expect, from the Chai package which is an assertion library, remember?). That should be everything related to the searchPage.js. 

Let’s take care of the other page which is where you’ll be redirected after the search is performed. We’re going to look after two tabs that should be present on that page: Downloads and Projects, then we’re going to make sure that the header from the middle of the page has the text “Getting Started.” We’ll call this file seleniumPage.js. In the exact same manner, we’re going to identify the elements we’re going to interact with, then we’re going to write the functions that will interact with the defined elements (in our case, just one), then, we’re going to export our modules in order to be able to use them in other files. If everything is done right, seleniumPage.js should look like this:

In the next and final part of this series, we will add the last bits and pieces to our framework, cover “World” concept, and share some final thoughts.

Background Image