Let’s Build A Framework – Day 1


 

Building Out the Bones of Our UI Automation Framework with WebDriver.io

Introduction

Hello to the West Denver Test Engineers, or to anyone out there who’s following along with our grand framework creation experiment.  This post, while very belated, will cover exactly what we did on Day 1.   We will talk about how you can check out Day 1 and see the end result.  Or you can check out Day 0 and follow along with the video posted earlier on this blog or you can follow along with this very blog post.

For this post we are going to assume you already read through the Day 0 Post and are set up and ready to create.  We will start with discussing the goals for the day, and go into how we build it.  Then, finally, we will discuss the purpose of what we built and potential uses for it for Day 2 and beyond!

The What: What We Want to Build Today

This section will cover what coding we actually did on day one, with pictures and more.

Ultimately we want to build a “Page Object Framework” which is a very common design pattern for UI Automation.

A Quick Understanding A Page Object Framework

The basic concept is we can divide any website or application up into individual pages.  We can sometimes further divide up that pages into smaller pages (think headers and footers).

Each page then has its own object associated with it.  This object stores the locators for all the elements on that page, and actions that you can do on the page.  This abstract breakdown of the application helps tremendously in writing easy to read and easy to maintain tests.

Think of a situation where you have 100 tests that all require a user to login.  Then one day a developer changes the login page.  You might find yourself in a situation where you have to update 100 tests.  Whereas if you stored all the details of a login page in a single object that the tests share.  You only need to update that one object and all your tests will magically work.

There are numerous components to build a successful page object.  Today we are going to focus just on this:

  1. Build a basic script with no framework to understand the basics of mocha, node and webdriver.io
  2. Create a locator file that stores information about what elements we want to interact with on a page.  (Stuff like buttons and text fields)
  3. Create a custom command library that can take on of our locators from our file and pass it through to WebDriver.io.
  4. Finally create a page object, which will be a class to represent a page in the application.  This will have easy to call methods representing actions you can perform on a page.  Actions like “login”, “searchForSomething” etc.

The How: Building It

Getting Set Up

Before we can start coding our framework we have a little foundational work to do to install, and set up our basic folder structure.

Please refer back to the Day 0 post for more specifics on everything you need to install.

Clone and enter our repo from git
git clone https://github.com/mustarddemon/westdenvermeetupfw.git
cd westdenvermeetupfw

Install and build everything
npm install
Start the selenium server (In a separate terminal)

In a separate terminal run the command for windows or mac, and leave it running.  When running tests for the remainder of this project we always want to make sure the server is running.

Windows

start_server_win.bat
Mac
./start_server.sh
Create our folder structure

The final set up piece will be to create a series of folders that we will use to divide our code up logically.  The structure is a main folder to hold our framework “mynewframework” and inside that folder create sub folders “lib”, “locators”, “pages” and “tests”.

Code below is for mac/linux but it assumed windows users can create their own folders

mkdir mynewframework
cd mynewframework
mkdir lib
mkdir locators
mkdir pages
mkdir tests

And that’s it, we’re ready to start building our framework!

Creating A Basic Test Without A Framework

The first thing we’ll want to do is just create a straightforward WebDriver.io test with no framework.  This is important to understand the basic structure, and also to serve as a reference to the advantages of our framework as we get further along.

Understanding Mocha

For our tests we are going to be using the mocha test runner.  As such were going to need to understand the basics of Mocha.  And how to set up a test file.

Mocha (for our purposes) is a behavior driven test framework.  It uses concepts such as “describe” to group a series of related tests together, and “it” to define a specific test.  This is so you can get test output like this:

Ebay Home Page – Should be able to search for an item

The way this looks in code form is like this:

screen-shot-2016-11-16-at-2-39-07-pm
Basic Mocha Test Structure
Understanding Basic Mocha

Looking at the above example you can see a pattern, where the first argument to a “describe” or “it” block is the description.  Each describe block can have multiple “it” blocks under it.  It can also have additional “describe” blocks to further group tests together.

The 2nd argument is whats called an anonymous function, this is a feature of the JavaScript language which Node is built on top of.   You can think of this function as the actual work that will be done.  Note the bottom two lines of the code above, the first closes out the “it” statement, and the 2nd closes out the “describe”.  The ‘}’ ends the anonymous function, and the ‘)’ ends the call to “it” or “describe”.

If you are coming to this having worked in a compiled language like Java or C# this can look a little strange.  But right now we can treat this as necessary “code  noise” over time it will make more sense.

Before and After Blocks

Mocha like most test runners also supports using Before and After blocks.  This allows to have some code that runs before the entire suite “before” or before each individual test “beforeEach”.  The way this looks in code is:

screen-shot-2016-11-16-at-2-46-48-pm
Mocha with a beforeEach method
The “done” function

The final aspect of mocha we need to be familiar with is the “done” function.  Node by its nature and mocha as an extension are asynchronous.  That means that whenever I/O is encountered (like reading a file, interacting with a browser, or calling an API) it is placed on a stack to be executed while control of the program continues on.

This asynchronous nature while making for very fast program execution, can be difficult in testing.  And since for UI testing almost everything done in the test is I/O the test can sometimes complete erroneously before the browser has even opened.

Mocha offers a couple solutions to this, one is that if a test is written using promises, it wont finish until the promise has resolved (this will be discussed in more detail below).  The other is a “done” function which serves as a “callback”.  What this means conceptually, is that a test is not considered completed until the “done” function is called.

The “done” function is passed in as an argument to any “it” or “before” or “after”
statement (which means any test).  Then inside the test the writer of the test can call “done” at a point when they know the test has completed.

In code this looks like:

screen-shot-2016-11-16-at-2-53-08-pm
Using done to ensure test completion

Note that done is passed in to our anonymous function, and called when we are sure the test or before actions are done.

You may be asking, who is passing a a “done” function in, and what is it doing inside that function.  The answer is that mocha is passing a function in.  That function determines test results and what to do next.  So when you call “done()” in your test, you are telling mocha to execute its end of test logic and move on.

Writing Our Basic Test File

Now that we have a basic understanding of mocha, we can write our first basic test file.  Our initial structure looks like this.  If you are following along at home, create a new file called “basic_scripting.js” under the “tests” folder.  And type this in.

Why Type?  You’ll notice that most code here is in an image format.  This is intentional to prevent copy/pasting.  By typing out the examples you will build muscle memory and it will immerse you in the learning experience.  Studies conducted by me, have shown people learn much faster when typing out the code initially.

Screen Shot 2016-11-16 at 2.57.53 PM.png
Initial structure for basic WebDriver.io test

Just like we discussed above, but we added one new thing a “require” statement.

Require Statements in Node

This is how we import libraries or classes in node.  Whether they are our libraries, or from a third party or build in libraries.  The behavior to pull one in varies slightly

Libraries Built By Us

//We need to pass the relative path from where we are currently
var myLibrary = require('./lib/myLibrary.js');

For our libraries we have to pass the path to the library.  This path is from the file we are currently in.  So in the above example if we were requiring this library from a file in the tests directory we’d have to change it to “../lib/myLibrary.js”  the “..” indicating we need to go up a directory.

Native or Third Party Libraries (Not Built By Us)

//We don't need to pass the path to the library just the name
var libraryName = require('libraryName');
Initializing WebDriver.io

We are now at a point where we can actually start a browser.   Below is the basic scripting test we will write.  We will break down whats happening below the image.

screen-shot-2016-11-16-at-3-18-32-pm
A basic vanilla WebDriver.io test

So what’s happening here?

  • First we define a variable object called “options”.  This object has a property called “desiredCapabilities” which itself is an object.  The “desiredCapabilities” object has a property called “browserName” which we define as firefox.  This is how we set the browser for our tests.  This options object is what WebDriver.io needs to get started
    • One of the advantages of Node/Javascript is the ease in which we can create new objects.  We can quickly create objects within objects, and can even give these objects functions!
  • Next we start by returning a command to webdriver.io.  If you’ll remember we pulled webdriverio in from our require statement at the top of the test.
    • We return as a way to stop the test from completing.  The test won’t complete until the entire return statement is finished.  In this case you can see we are “chaining” together multiple commands which finished at “end()”
  • The next command in our chain is “init()” this is where the browser is actually started.
  • The first command in our “chain” is “remote” this is telling webdriver to connect to the selenium server that we have running and use that to start our browser.  We pass in the “options” object, which tells it which browser we’d like to start.
  • The next command is “url(‘http://www.google.com’)” this is a WebDriver.io method that tells our browser to open that webpage.
  • Our final command is “end()” this tells the WebDriver.io and the selenium server that we are done and the browser can be closed.  This is also the end of our command chain so we terminate it with a “;”

That’s it we’ve created our first test.  You can explore all the different methods you can use with WebDriver.io here.  If you were so inclined you could start writing tests right away.  You know how to set up a test file and how to interact with webdriver.  You can call methods like “click” and “setValue” to type and click things on your web page.

Note: In truth you can get very far with just basic scripting.  The reasons for a framework are not immediately apparent.   Issues with basic scripting tend to happen months down the line after you’ve build your 100 tests and now you need to change something.  I encourage anyone to write a few fully scripted tests using this basic method.  It will not only help your understanding, it will help explain the features we are trying to give in our framework.

Wait?  How Do I Run the Test?

Ah, a key oversight!  To run the test use the following commands (after making sure the selenium server is running and you have run npm install).

Make sure you are in the mynewframework folder where we created our folder structure

mocha tests/basic_scripting.js

Creating a Locator File

It’s difficult to pick a starting point when building a new framework as many of the components tie in together.   That being said, a key feature of our framework is going to be storing a lot of knowledge about our pages in external language agnostic files.

This has numerous advantages which we’ll explore as we continue to build out the framework.

We are going to be storing our locators in JSON files.  This is because its easier to write than XML and its got native support in JavaScript/Node which will make parsing it in our code much easier.  But, in truth you could use any form of external storage for your locators.

Our basic layout is going to be an object, and in that object each property represents an element on the page.  For our example we’re going to use ebay.com.

First, we’ll create a new file called “EbayHome.json” and put it in our locators folder, then we’ll identify a few elements on the eBay home page we know we’ll want to interact with in our tests. In this case we’re going to use the search bar at the top of the page, and the corresponding search button.   Here’s what our file will look like initially.

Screen Shot 2016-11-17 at 9.39.47 AM.png
Basic locator file layout

You can see we give each element an easy to read name, and inside the object for the element we are going to store data on how to locate the element.  We are also going to include a “description” field.  This gives even more information about the element, and will allow us to make some very elegant test output.

In this experiment we are going to gloss over how to locate an element.  We are going to assume all elements are located through css.  But in later sessions we’ll discuss how to easily expand this to support xpath or any other locator method.

Here’s what our file looks like after we add information for our elements:

Screen Shot 2016-11-17 at 9.42.34 AM.png
Complete locator file

And that’s it.  While it may seem simple this locator file will allow us to add incredible features which we’ll discuss in future sessions.

Creating Our Own Custom Commands

Since we will be using our own locators pulled from our locator files.  We don’t want to use the straight WebDriver.io methods like click and setValue.  We instead want to make a wrapper method that takes our version of a locator and passes the correct locator information to the native WebDriver.io method.

We’re going to store all of our custom commands in a file called “commands.js” which we will put under the “lib” directory.  This file is going to be a library, that means its just going to be a collection of methods we can pull into any of our code with a “require” statement and call those methods.

To help make our code readable and understandable we’re going to use a NodeJs concept called “Promises”.  We’ll discuss further below what promises are and why we use them.  But our initial commands library will look like this:

Screen Shot 2016-11-17 at 9.49.26 AM.png
Basic custom commands library layout

There’s a lot happening here so lets break it down.

  • First we require “bluebird” this is a third party promise class that we will use.  Note that we store the required class in a variable called “Promise”.  The capitalization there is intentional, in Node the standard is when requiring a library that we use camel case, but when we require a class we use Uppercase.
  • Next we define a variable called “driver” this is going to hold the webdriver.  You can consider the driver to be essentially the browser, and it is the Native WebDriver.io code.
  • Next we create module.exports, this is how we tell Node which of our methods are public.  Anything inside module.exports you can reference when you require the library.
  • Inside module.exports we define a setDriver method.  This allows us to pass the driver in once and reuse it in all of our methods.  This will make our code more readable.  Notice that we take a passedInDriver, and assign it to our driver variable.  The nature of Node is that once something is “required” all subsequent “require” statements load the same library.  Meaning once we set the driver it will stay set even across different tests.
  • Finally we define some “stub” functions for click and setValue.  These both take a “locatorObject” in, this is going to be one of the elements from our locator file.  Now we can implement our commands.

How To Call A WebDriver.io Function

Before we can implement our custom function let’s look at how we would call it normally.

driver.click('locatorString');
driver.setValue('locatorString', 'text to type');

This is the base way to call a WebDriver.io method.   So we know our locatorObject has a locatorString property so we can modify this to be:

driver.click(locatorObject.locator);
//Our setValue function has a 2nd argument which is the text we want to input
driver.setValue(locatorObject.locator, textToType);

So far so good.  But lets take advantage of that description field we put in our locator object.   We can make it so every time our custom function is called it outputs a nice description so anyone running our tests can see in English what is happening.

click: function(locatorObject) {
 console.log('I am clicking ' + locatorObject.description);
 driver.click(locatorObject.locator);
 });

At this point our function will work, but using it will require tests to chain all methods together due to the nature of WebDriver.io.  We want to use promises to make our test code easy to write for anyone who might not have a lot of programming skills.

When using promises, we can wrap any non promise method in a promise and have it return that promise.  To do this we we immediately return a new Promise object.

return new Promise(function(resolve, reject) {
   //our method here
});

So what does this mean?  It means we immediately return the new Promise object, and we pass that object a new anonymous function.  That function takes itself 2 arguments resolve, and reject.  Both of these are also functions.  When we are sure our method is complete successfully we would call resolve, if an error occurred somewhere we would instead call reject.

This is how promises can enforce sequential behavior when dealing with asynchronous actions.

So we know we want to call “resolve” when our webdriver method is complete.  Luckily for us its as simple as:

return resolve(driver.click(locatorObject.locator));

That’s it.  Code using our promise wont move on to the next step until resolve is called, and resolve won’t be finished until the WebDriver.io click method is finished.

Putting it all together we get our finished commands file:

Screen Shot 2016-11-17 at 10.11.23 AM.png
Finished custom commands library

Now whenever we want we can expand our commands library to cover any WebDriver.io method.  Meaning any normal method you will call will now be able to have a nice output and use our locator objects from our locator file.

The advantages of this aside from the output are, that whenever we want to add a new feature to our locator object, we only need to modify these functions to support the new feature we don’t have to update anything else.

Some great features we’ll be adding later are:

  • Automatic handling of frames
  • Switching locator types between css and xpath
  • Grouping elements for automatic page load validation

Other features you could add

  • Alternate locators (great for mobile applications or applications where the locator changes between versions or users)
  • Automatic retry on failed actions
  • Checking for pop ups on failed actions (great for sites with ads)

Creating Our First Page Object With Locators

We’re so close now!  The final component of our framework is going to be a Page Object.  We are going to have a 1:1 ratio for page objects and locator file.  Meaning we need a page object called EbayHome.js to match our locator file EbayHome.json.  We’ll put this file in our pages folder.

This will allow us to automatically be able to reference all elements on a page.

The next thing we want our page objects to handle is what actions we are going to perform.  These actions are at a larger level then just click.  Think of actions like “logIn” or “searchForItem” or “waitForPageToLoad”.

Let’s start with a basic definition of our page.

Screen Shot 2016-11-17 at 10.19.53 AM.png
Basic page object class layout

So, again, a lot happening here let’s break it down.

  • First we require our handy Promise library like before
  • We also pull in our commands.js file we just made
  • And we define a “locators” variable.  This will store all the locators from our file.
  • Then we use the kind of funky way JavaScript defines classes.  It does so by defining a function with the class name.  This is the same as a “constructor” you would see in other languages.
    • Our constructor takes a driver in and assigns it to our driver variable.
    • It also loads the locator file and stores it in our locator object.
  • Then we define some “public” methods for our class to have.  to do this we use “prototype” syntax followed by the method name.  Here is where we will implement our actions.
  • Finally we set module.exports to our class.  This is what will allow us to create new pages.

Note: unlike libraries, classes in javascript need to be created with a “new” command, and doing so will create a fresh copy each time.  Meaning that each time we will have to pass a driver in and anything changed in the class will not be preserved across instances of that class.

Now for the fun part actually stringing together some actions to do something on the browser.

First we want an “open” method, this will load the url for the home page.  Unlike click and setValue we havent implemented a custom command for open yet. So we’ll just wrap it in a promise like before:

EbayHome.prototype.open = function() {
   var self = this;
   //open the ebay home page
   return new Promise(function(resolve, reject) {
      return resolve(self.driver.url('http://www.ebay.com'));
   });
};

So what’s the deal with the “self” variable?  JavaScript does some interesting thing with scoping, so its just a good practice to create a variable called self and assign it to “this”.  I won’t go into more detail you can just think of it as some necessary code noise.

The rest of the method is just like our custom commands, we just wrap the built in WebDriver.io method in a promise.

The next method “searchFor” we get to use our custom commands though:

EbayHome.prototype.searchFor = function(textToSearchFor) {
   // type something and search for it
   var self = this;
   return commands.setValue(self.locators.topSearchBar, textToSearchFor)
   .then(function() {
     return commands.click(self.locators.topSearchButton);
   });
};

So you can see we can call any of our custom commands just by using “commands.<method name>”.

Another nice feature here, is because we know our commands methods are already returning promises, we don’t need to wrap them in one, we can just return the method.

So what about this “then” business?  This is the key part of why we are using promises.  Anytime you are working with a function that returns a promise, you can call call “then” and pass a function to it.  That function passed will only be executed after the previous promise is resolved.  This means we can guarantee our actions happen sequentially.

This is called a “promise chain” meaning we can string together however many actions we want and have them execute in sequence.

//Typical Promise Chain Structure
return somePromiseFunction()
.then(function(resultFromFirstFunction) {
    return someOtherPromiseFunction();
})
.then(function(resultFromPreviousFunction) {
    //and so on and so forth
});

If you are new to Node this may seem strange, but you might still see the value of syntactical structure of the code.   “Do something THEN do something else THEN etc …”

Note:  The return statements are absolutely critical with promises.  Because of the nature of Node, if it sees a return statements it stops execution in that method until all promises are resolved.  If you left out a “return” Node would asynchronously run the promises and continue execution to the next command.  Meaning it would get to the end of the function and finish before the promises had finished.

Let’s look at our finished product:

Screen Shot 2016-11-17 at 10.38.00 AM.png
Finished page object class

Now we can create as many Pages as we want to map out the entire eBay site, and we can make as many actions on each page.  So that when it comes time to write tests all the hard work has been done for us!

Tying It All Together With A New Test

We’ve reached the promise land, time to write our first test using our framework.

Note: Be prepared to be underwhelmed, our new test will not be that much cleaner, but remember this is just day one.  In the coming days we will add more features to make our tests cleaner and easier to run.

We already have our “basic_scripting.js” file in our “tests” directory.  Let’s create a new file for our new framework style test called “better_scripting.js”.  We’ll give it a basic mocha structure:

Screen Shot 2016-11-17 at 10.41.32 AM.png
New test file layout

A couple things to point out before we go further.

We added a beforeEach and afterEach method.  We are going to use these to automatically start our browser and shut it down when the test is done.

We also added a “this.timeout(30000)” this is a feature in Mocha.  By default it times out a test after 2 seconds, so we are expanding it so it will kill our tests after 30 seconds instead.

Finally we added some new “require” statements we are pulling in our EbayHome class.  This is going to allow us to create a new version of the page and call our handy methods.  And we pulled in our custom commands, this is so we can set our driver in it at the start of our test once we’ve initialized it.

First though, lets move our initialize webdriver code into our beforeEach method.

 beforeEach(function(done) {
   var options = {
     desiredCapabilities: {
       browserName: 'firefox'
     }
   };

   driver = webdriverio.remote(options)
   .init();
   commands.setDriver(driver);
   return done();
 });

So this is essentially the same code as in our “basic_scripting.js” but we call the “done()” method when we are done to ensure we don’t start the test before the browser loads.

Our “afterEach” method is even simpler.

afterEach(function() {
   driver.end();
});

That’s it, it will close the browser after each test.

Now to the big moment.  We can write our new test using page objects:

First we want to create a new EbayHome page so we can use it.

var ebayHome = new EbayHome(driver);

Then we can call and chain any methods on the page together:

 return ebayHome.open()
 .then(function() {
    return ebayHome.searchFor('orphans');
 });

Now we only have 2 methods, but you can imagine if you had multiple page objects, and methods you could write a pretty impressive test.

Lets see the finished product:

Screen Shot 2016-11-17 at 10.56.47 AM.png
Finished test file

Wait?  How Do I Run the Test?

Ah, a key oversight!  To run the test use the following commands (after making sure the selenium server is running and you have run npm install).

Make sure you are in the mynewframework folder where we created our folder structure

mocha tests/better_scripting.js

 

It’s still not super pretty and easy to use.  But by the end of day 2 all of that will change!

So stay tuned, and watch this space!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s