Jul 13, 2020 · by Armand Stan

Starting an Automation Framework: Part 3

A Step-by-Step Guide to Using Javascript

Welcome to the third and final installment of this series on starting an automation framework. Now that we’ve defined our test scenario and made some more progress on our framework and covered Steps definitions and Page Objects pattern, let’s add the last bits and pieces to our framework in order to make it usable. We will go over the “World” concept in Cucumber.js and share some final thoughts.

  1. Bringing everything together

We’re ready now to fill up the empty functions from googleSearch.js. Open the file and let’s start by importing the already created modules. In order to do this, we should add the following lines in the file, right after the Given, When, Then import (first line):

var {

    performSearch,

    checkSearchResult,

    clickOnSeleniumLink

  } = require("../page_objects/searchPage");

 

var {

    checkElementsSeleniumPage

  } = require("../page_objects/seleniumPage");

As you can see, we’re just pointing to where the page’s logic is located. The (“..”) it’s used for  going one level up (remember page_objects and step_definitions are at the same level and we’re in the step_definitions folder). What now? Finally, we’re able to map the functions we’ve created to the plain text definitions

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

    this.driver.get("https://www.google.com");

});

 

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

    performSearch.searchAfterWord(this.driver, text);

});

 

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

    checkSearchResult.checkResultAfterSearch(this.driver);

});

 

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

    clickOnSeleniumLink.openSeleniumResult(this.driver);

});

 

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

    checkElementsSeleniumPage.checkSeleniumPage(this.driver);

});

As you can see, we’re just using the methods we’ve previously created. What do we have here? The keyword this in front of driver, that’s right! Why is that? Well, if you look closely, we have the function that’s opening the browser to Google Page but what browser are we using? There is currently no setting to do that in our framework. We can add it here, but if we’re having multiple files, with different scenarios, we’ll repeat ourselves. But where should we add it, in order for all the steps to have access to this setting? 

  1. World concept

Cucumber.js has a concept called “World” which gives you the opportunity to share context between the steps. It is very useful when writing BDD code as you can share context even between steps that are not placed in the same file. Imagine your framework getting bigger and bigger, you’ll eventually come across a situation where a step can be used in more than one place but you want to have access to the context each time that step is reused. This is usually done using the “World” concept. Basically, you can have access to the world context using this keyword. Hence the usage of  this keyword from the above. But we have to define it first in this thing “World” (whatever that means!), in order to use it. So, on the same level with step_definitions and page_objects folders, create a folder called support. In this folder, create a file with .js extension and call it world.js. Let’s instantiate a browser session that can easily be used by other steps and in other places:

var { setWorldConstructor } = require('cucumber')

var seleniumWebdriver = require('selenium-webdriver')

function OurWorld() {

    this.driver = new seleniumWebdriver.Builder()

        .withCapabilities()

        .forBrowser('firefox')

        .build()

}

setWorldConstructor(OurWorld)

As you can see, we have a custom function here (we can have classes as well) in which we’re setting the browser configuration (capabilities can be added, but we’ll keep it simple) and we’re making this configuration available in the World by using setWorldConstructor. Now, every time we’re using this.driver this particular configuration will be used. It’s massively configurable, so the sky’s the limit, really. For this purpose, we’re going to use Firefox browser. 

  1. Adding the last bits and pieces to our frameworks

Now, let’s get back to our step definitions and let’s see what’s missing. Remember the asynchronous nature of Javascript? Take a look at the functions for each step. That’s right, they are simple functions. What will happen if we’re running the scenarios in the current state? Exactly, Javascript won’t wait for each step to finish and it will just go on running all of them and we’ll end up with a bunch of failed tests because the browser is slow and pages take time to load. Let’s fix this. Turn each function into an async one and add the await keyword before each instruction from each function. It should look like this:

Finally! We’re ready to run our scenarios. Let’s give it a try using npm test in Visual Studio Code terminal. It works beautifully! Wait a minute, the browser didn’t close when the test finished and a test that’s showing us the leftovers is a bad thing. And there is also the fact that the browser didn’t go full screen when started. We can make something up and add them in the step definitions but that wouldn’t be nice. We need to have the browser maximized and closed after finishing for every scenario. For this purpose, we’re going to use some hooks. The hooks are used for setting up and tearing down the test environment. So, let’s do this. In the support folder, create a file with .js extension called exactly like that hooks.js. Here, we need a function that’s maximizing the browser when the tests are starting and another one that’s closing the browser when everything is done. In other words, one Before function and one After function. Well, that’s handy! In Cucumber we have access to these keywords (and many others) that are used exactly for that.

Before(function() {

  this.driver.manage().window().maximize();

});

 

After(function() {

    this.driver.close();

});

Remember, the hooks.js has access to the World context as well by using this. So, using this word, we’re making sure that we’re using the correct driver. Don’t forget to “acquire” the powers to use them, by adding:

var {Before, After} = require("cucumber");

Let’s see how the hooks.js file looks like:

And with that, we’re ready to go! Go ahead and issue a npm test command in the terminal. 

That wasn’t that hard, right? We now have a fully functional framework. It’s not the biggest one and can easily be improved here and there (move the strings you’re using in a separate place, design specific clicking methods and use them everywhere, design generic waits, use other browsers as well, parallelize tests, etc.), but it was designed from scratch and nobody can take this from you.

Final thoughts

Hopefully, this guide wasn’t that painful, because, as a person who has used mainly Java/C# for automation, I can agree that Javascript looks a bit odd at first glance. But, if you’re giving it a chance, you’ll notice it might look quite cool. 

It’s really powerful and after getting used to its syntax, there’s nothing holding you back. There is tons of information on the internet regarding Selenium, the main one being https://www.selenium.dev/documentation/en. Read (don’t scan), apply, practice and you’ll be on your way. Happy automating!

Armand Stan

Armand Stan

QA Automation Engineer
Armand Stan
Share This Article

Post A Comment