BUILD THAT WEB TEST AUTOMATION FRAMEWORK PART 1

Navigating Tight Timelines

In the age of rapid software development, the agile age, Scrum, and DevOps, getting software properly tested within tight windows is paramount. QA is already an afterthought in most shops; also, the prevailing thought is that QA is a bottleneck in the software delivery pipeline: this forces us to interject Testing at Speed into the mix.

Test automation cannot be done without tools. This blog series will not review or rate the test automation tools out there. Instead, we are building one from scratch. You’ll soon find out that budget constraints are real: recall, QA is already an afterthought, minimize the expenditure.

In that light, we’re going to build one from scratch and minimize expenditure. Tools of the trade:

·         IDE: Eclipse (Yes, Java)

·         Base Framework: Selenium/WebDriver (we’ll address mobile testing in another series)

·         xUnit Test Framework: Junit 4

·         An engaged brain

We’ll have all we need to build a framework that looks like this:


Our Test Automation Framework

And our eventual code base, the one that spawns this framework looks like this:


Our Code Layout

We’ll parse, peruse, the aforementioned layout from the bottom up; so, let’s begin.

The Project

We’re going to setup a test that composes an email (Gmail). In terms of whether you’ll create a freestylemaven or gradle project in Eclipse, the choice is yours. This blog series focuses on the techniques used in code to have your test automation framework up and running.

Incidentally, I prefer to create a maven project; the primary dependencies are 
Selenium Java and Selenium Server. You may get the relevant browser maven dependencies on the internet: ChromeFirefoxEdge and Safari dependencies are all on https://mvnrepository.com .

The Core: Java Executors, JUnit Threads

Executors is an interface (a framework really) that the JDK has supplied in its 1.5 offering; it simplifies the execution of asynchronous tasks .

Why Executors? We want stringent control of the nodes that are run – especially if your tests run in the cloud; we may have custom nodes that we have to spin off asynchronously … we’ll need thread management.

As stated before, we’ll need threads and we will not reinvent the wheel. We’ll re-use Junit threads, as I said don’t reinvent the wheel.  Peruse the Junit API (https://junit.org/junit4/javadoc/latest/) and you’ll find a class that we could extend in the org.junit.runners package: the Parameterized class. Junit’s API says this about the Parameterized class …

… custom runner Parameterized implements parameterized tests. When running a parameterized test class, instances are created for the cross-product of the test methods and the test data elements.

Perfect. It is basically used to run tests in parallel. So, let’s go ahead and use it:

 

/**

 * Gives us more fine-tuned control over parallel/concurrent execution.

* We're able to extend JUnit system threads and get help with concurrency

* via Java Executors

 * @author Isaac.Riley

 *

 */

public class GridParallelHelper extends Parameterized{

   public GridParallelHelper(Class<?> klass) throws Throwable {

       super(klass);

        setScheduler(new ThreadPoolScheduler());

    }

}

But we spoke of Executors? Yes, we did. We’ll use Junit threads for our asynchronous tasks. Let’s embed a private class in the one that extends Parameterized. Not only that, Junit has a pertinent RunnerScheduler interface that we could use to our advantage, it is still fit for use, despite its experimental nature (coding to interfaces gives us some measure of control):

/**

 * Manages Executors: really manages Futures (asynchronous tasks); due to

* the asynchronous nature of each WebDriver instance, we wouldn't want

* this to run in the main/UI thread

*  @author Isaac.Riley

 */

private static class ThreadPoolScheduler implements RunnerScheduler{

    private final ExecutorService executor;

    public ThreadPoolScheduler() {

       //we need 6 Junit System threads

        String threads = System.getProperty("junit.parallel.threads", “6’);

        int numThreadsToUse = Integer.parseInt(threads);

        executor = Executors.newFixedThreadPool(numThreadsToUse);

  }

}

Now, we have an Executor (ExecutorService is a sub-interface, also available since Java 1.5); the Executor can be scheduled:

@Override

public void schedule(Runnable child) {

   executor.submit(child);

}

And the Executor can be shutdown when you’re finished with it:

@Override

public void finished()

 executor.shutdown();

    try {

        //existing tasks have 5 minutes to wrap up (maybe too long but...)

        executor.awaitTermination(5, TimeUnit.MINUTES);

     } catch (InterruptedException e) {

     }//handle the exception

}//finished

Let’s underscore a few salient points:

1.   Our GridParallelhelper class extends the Parameterized class, inheriting what it brings to the table (consult the Junit API)

2.   We’ve got a parallel strategy for scheduling when the test cases are run; we did this by implementing Junit’s RunnerScheduler interface

·         By adhering to the interface’s contract, we use Executors, to create a thread pool, in which, we’ll inject Junit threads for parallel test case execution

·         The ExecutorService (as of JDK 1.5) can be shutdown, submitted asynchronously: in our discourse, we get a thread pool of Junit threads, a fixed number of threads that comes off a shared unbounded queue

Design Pattern: Factory Pattern (Support for WebDriver/Selenium Nodes)

Primer…. What is a design pattern, or more formally a software design pattern? Ok, I won’t date myself and provide the Gang of Four definition. Simply put, a software design pattern is a general reusable, well described solution to a common software problem.


The Software Code Design Pattern

The factory pattern, or more formally, the factory method design pattern, is a creational design software design pattern that is used to create objects without exposing the creation logic to the client. Creational software design patterns are used to solve the problem of unwanted complexities that tend to creep in object creation. We’ll use the factory method design pattern to create multiple “subclasses” based on input, a string that describes an environment:

·         We’ll first define an interface for creating an object

·         We’ll let the “subclasses” decide which object to instantiate

·         The factory pattern lets a class defer instantiation to subclasses

Schematically, the factory pattern looks like this:

UML Class Diagram for the Factory Method Design Pattern (By Vanderjoe)

Define the Interface

It is best to code to interfaces; this keeps the coupling loose.

/**

 * As a necessary component of the Factory Pattern, this is the interface

* for creating objects (the "subclasses" do the actual instantiation) … No

 * I’m not doing a lamba expression

 * @author Isaac.Riley

 */

public interface WebDriverForNode {

    public WebDriver getWebDriver() throws MalformedURLException;

}

The classes that implement this interface agree to this contract stipulated thereby: i.e. give me a WebDriver for the node that is being run. For example, if we need a Chrome browser on a PC, the node would be provisioned by implementing the aforementioned interface:

/**

 * Class (part of the factory pattern) creates a WebDriver that's

 * capable of perusing Node that houses a local Chrome browser

 * @author ISAAC.RILEY

 */

public class WebDriverForLocalChromeBrowser implements WebDriverForNode {

   @Override

   public WebDriver getWebDriver() throws MalformedURLException {

     System.setProperty("webdriver.chrome.driver", C:\\selenium\\chromedriver.exe");  

      return new ChromeDriver();

    }

}

This concrete class will provide a WebDriver object that’s capable of perusing a Chrome browser.

The design pattern is not yet complete –we need the factory method. As stated before, object creation is deferred. In our case, we’re going to create an environment specific WebDriver object, akin to the following:

/**

  * Based on a string, we provide a WebDriver that's capable of perusing the

 * browser/app of the target

 * @author Isaac.Riley

 *

 */

public final class NodesFactory {

     //This is our factory method

    public static WebDriverForNode getWebDriverForGrid(String criterion) {

              

       if(criterion.equalsIgnoreCase(GridConstants.CHROME_NODE_LOCAL)){

          return new WebDriverForLocalChromeBrowser();

       }//Chrome

        else if(criterion.equalsIgnoreCase(GridConstants.FIREFOX_NODE_LOCAL)){

           return new WebDriverForFirefoxLocal();

        }//Firefox browser

           :

           ://other browsers/targets

        return null;//if fail return null, OK, they’re better ways to do this   

    }

}

This factory method takes a string that describes its target and returns a custom WebDriver for the target.

Parting Shots

·         We have used Executors and Junit threads to gain a tighter control of the nodes that we want to run

·         We have used the factory method design pattern to create custom WebDriver objects for target nodes; they can be used elsewhere in code and the complexity of the creation of the custom WebDriver object is hid from the caller

Next the Page Object Model.

Comments

Popular posts from this blog

QA Process and Test Automation