Mastering Selenium WebDriver

5 (5 reviews total)
By Mark Collin
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Creating a Fast Feedback Loop

About this book

Selenium WebDriver, also known as Selenium 2, is a UI automation tool used by software developers and QA engineers to test their web applications on different web browsers. The Selenium WebDriver API is fully object oriented compared with the deprecated Selenium RC. The WebDriver API provides multi-language support and run tests on all the most popular browsers.

In this wide and complex World Wide Web era, this book will teach you how to tame it by gaining an in-depth understanding of the Selenium API.

This book starts with how to solve the difficult problems that you will undoubtedly come across as you start using Selenium in an enterprise environment, followed by producing the right feedback when failing, and what the common exceptions are, explain them properly (including the root cause) and tell you how to fix them. You will also see the differences between the three available implicit waits and explicit waits, and learn to working with effective page objects.

Moving on, the book shows you how to utilize the Advanced User Interactions API, how you can run any JavaScript you need through Selenium, and how to quickly spin up a Selenium Grid using Docker containers.

At the end, the book will discuss the upcoming Selenium W3C specification and how it is going to affect the future of Selenium.

Publication date:
August 2015
Publisher
Packt
Pages
280
ISBN
9781784394356

 

Chapter 1. Creating a Fast Feedback Loop

One of the main problems you hear people talking about with Selenium is how long it takes to run all of their tests. I have heard figures ranging from a couple of hours to a couple of days. In this chapter, we will have a look at how we can speed things up and get the tests that you are writing running both quickly and regularly.

The second problem that you may come across is getting other people to run your tests; this is usually because it is a pain to set up the project to work on their machine and it's too much effort for them. By making things run fast, we are going to make it very easy for others to check out your code and get themselves up and running.

How does this create a fast feedback loop? Well, if your developers are running all of the tests before every check in, they will know if the changes they have made to the code break things before the code leaves their machine.

They can also update the tests as the code changes, transforming the tests into living documentation.

 

Making it easy for developers to run tests


Ideally, we want our tests to run every time somebody pushes code to the central code repository; part of doing this is ensuring that it's very easy to run our tests. If somebody can just check out our code base, run one command, and have all of the tests just work, it means they are far more likely to run them.

We are going to make this easy by using Apache Maven. To steal a quote from the Maven documentation:

 

"Maven is an attempt to apply patterns to a project's build infrastructure in order to promote comprehension and productivity by providing a clear path in the use of best practices."

 
 --https://maven.apache.org/guides/getting-started

Maven is a tool that can be used to build Java projects and manage project dependencies (including downloading any dependencies that you require) and is used in many companies as part of the standard enterprise infrastructure. Maven is not the only solution to this problem (for example Gradle is quickly gaining popularity and traction) but it is one that you are most likely to see on the ground and one that most Java developers will have used at some point in their career.

One of the major plus points is that it encourages developers to use a standardized project structure that makes it easy for people who know Maven to navigate around the source code; it also makes it very easy to plug into a CI system (such as Jenkins, or TeamCity) as all the major ones understand Maven POM files.

How does this make it easy for developers to run tests? Well, when we have set our project up using Maven, they should be able to check out our test code and simply type mvn clean install into a terminal window. This will automatically download all dependencies, set up the class path, and run all of the tests.

It doesn't really get much easier than that.

 

Building our test project with Apache Maven


Getting a fully working Maven install up-and-running is beyond the scope of this book, but it shouldn't be too hard. Apache has a guide to setting Maven up in 5 minutes at the following link:

http://maven.apache.org/guides/getting-started/maven-in-five-minutes.html

If you are running the Debian derivative of Linux, it is as easy as:

sudo apt-get install maven

Or, if you are running a Mac with homebrew, it is just:

brew install maven

Once you have Maven installed and working, we will start our Selenium project with a basic POM file. We are going to start out by creating a basic Maven directory structure and then creating a file called pom.xml in it. The directory structure is displayed here:

There are two main testing frameworks that you will come across in a Java environment: jUnit and TestNG. I personally find TestNG to be easier to get up-and-running out-of-the-box, but I find jUnit to be more extensible. TestNG certainly seems to be popular on the Selenium mailing list with many threads asking questions about it; you don't often see jUnit questions any more.

I'm not going to suggest either one as the right choice as they are both capable frameworks that you will probably come across in the enterprise world. Since TestNG seems to be the more popular option, we will focus on a TestNG implementation in this chapter.

In this chapter, we will implement the same base project, but we will use jUnit instead of TestNG. This means that, instead of worrying about which one is the best, you can have a look at a TestNG implementation and a jUnit implementation. You can then choose which one you prefer and read the relevant section.

So to start off with, let's have a look at a basic POM for a TestNG-based Maven project:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <groupId>com.masteringselenium.demo</groupId>
    <artifactId>mastering-selenium-testng</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modelVersion>4.0.0</modelVersion>

    <name>Mastering Selenium TestNG</name>
    <description>A basic Selenium POM file</description>
    <url>http://www.masteringselenium.com</url>

    <properties>
        <project.build.sourceEncoding>UTF-8
        </project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8
        </project.reporting.outputEncoding>
        <!-- Dependency versions -->
        <selenium.version>2.45.0</selenium.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>${selenium.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-remote-driver</artifactId>
            <version>${selenium.version}</version>
        </dependency>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>6.8</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

What you are seeing here is mainly Maven boilerplate code. The groupId, artifactId, and version properties are subject to the standard naming conventions:

  • groupId: This should be a domain that you own/control and is entered in reverse

  • artifactId: This is the name that will be allocated to your JAR file, so remember to make it what you want your JAR file to be called

  • version: This should always be a number with -SNAPSHOT appended to the end; this shows that it is currently a work in process

The important bits that we have added, and that will be useful to our project, are in the dependencies block. To start off with, we have added a dependency for Selenium and a dependency for TestNG. Note that we have given them a scope of test; this ensures that these dependencies are only loaded into the class path when tests are run.

Tip

We have used a property to set the Selenium version. This is just so that we can quickly update Selenium versions without having to work our way through the POM finding each bit of Selenium that we want to update.

You can now open up this POM file using your IDE. (In this book I'm assuming that you are using IntelliJ IDEA; however, any modern IDE should be able to open up a POM file and create a project from it.)

We now have the basis of our Selenium project. The next step is to create a basic test that we can run using Maven. Start off by creating a src/test/java directory and then in this directory we will create a file called basicTest.java. Into this file we are going to put the following code:

package com.masteringselenium;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.annotations.Test;

public class BasicTest {

    private void googleExampleThatSearchesFor(final String searchString) {

        WebDriver driver = new FirefoxDriver();

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

        WebElement searchField = driver.findElement(By.name("q"));

        searchField.clear();
        searchField.sendKeys(searchString);

        System.out.println("Page title is: " + driver.getTitle());

        searchField.submit();

        (new WebDriverWait(driver, 10)).until(new ExpectedCondition<Boolean>() {
            public Boolean apply(WebDriver driverObject) {
                return driverObject.getTitle().toLowerCase().startsWith(searchString.toLowerCase());}
        });

        System.out.println("Page title is: " + driver.getTitle());

        driver.quit();
    }

    @Test
    public void googleCheeseExample() {
        googleExampleThatSearchesFor("Cheese!");
    }

    @Test
    public void googleMilkExample() {
        googleExampleThatSearchesFor("Milk!");
    }
}

These two tests should be quite familiar; it's the basic Google cheese scenario with all the main grunt work abstracted out into a method that we are able to call multiple times with different search terms. We now have everything we need to run our tests. To kick them off, just type the following into the terminal:

mvn clean install

You will now see Maven downloading all of the Java dependencies from Maven central. When this has completed, it will build the project and then run the tests.

Tip

If you have problems downloading the dependencies try adding a -U to the end of the command; this will force Maven to check the Maven central repositories for updated libraries.

Finally, you will see Firefox load up and run through the two basic tests.

Tip

If things don't work it's likely that your versions of Selenium and Firefox are out of sync; check the prerequisites to make sure your environment is set up correctly.

We now have a very basic project set up to run a couple of very basic tests using Maven. Right now this will run very quickly but, as you start adding more and more tests to your project, things are going to start slowing down. To try and mitigate this problem we are going to utilize the full power of your machine by running your tests in parallel.

 

Running your tests in parallel


Running your tests in parallel means different things to different people, as follows:

  • Running all of your tests against multiple browsers at the same time

  • Running your tests against multiple instances of the same browser

Should we run our tests in parallel to increase coverage?

I'm sure that, when you are writing automated tests to make sure things work with the website you are testing, you are initially told that your website has to work on all browsers. The reality is that this is just not true. There are many browsers out there and it's just not feasible to support everything. For example, will your AJAX-intensive site that has the odd Flash object work in the Lynx browser?

Tip

Lynx is a text-based web browser that can be used in a Linux terminal window and was still in active development in 2014.

The next thing you will hear is, "OK, we will support every browser supported by Selenium". Again that's great but we have problems. Something that most people don't realize is that the core Selenium team's official browser support is the current browser version, and the previous version at the time of release of a version of Selenium. In practice it may well work on older browsers and the core team does a lot of work to try and make sure they don't break support for older browsers. However if you want to run a series of tests on Internet Explorer 6, Internet Explorer 7, or even Internet Explorer 8, you are actually running tests against browsers that are not officially supported by Selenium.

We then come to our next set of problems. Internet Explorer is only supported on Windows machines, and you can only have one version of Internet Explorer installed on a Windows machine at any time.

Tip

There are hacks to install multiple versions of Internet Explorer on the same machine, but you will not get accurate tests if you do this. It's much better to have multiple operating systems running with just one version of Internet Explorer.

Safari is only supported on OS X machines, and again you can only have one version installed at a time.

Tip

There is an old version of Safari for Windows hidden away in Apple's archives, but it is no longer actively supported and therefore shouldn't be used.

It soon becomes apparent that, even if we do want to run all of our tests against every browser supported by Selenium, we are not going to be able to do it on one machine.

At this point, people tend to modify their test framework so that it can accept a list of browsers to run against. They write some code that detects, or specifies, which browsers are available on a machine. Once they have done this they start running all of their tests over a few machines in parallel and end up with a matrix that looks like this:

This is great, but it doesn't get around the problem that there's always going to be one or two browsers you can't run against your local machine, so you will never get full cross-browser coverage. Using multiple different driver instances (potentially in multiple threads) to run against different browsers has given us slightly increased coverage. We still don't have full coverage, though.

We also suffer some side effects by doing this. Different browsers run tests at different speeds because JavaScript engines in all browsers are not equal. We have probably drastically slowed down the process of checking that the code works before you push it to a source code repository.

Finally, by doing this we can make it much harder to diagnose issues. When a test fails you have to work out which browser it was running against, as well as why it failed. This may only take a minute of your time, but all those minutes do add up.

So why don't we just run our tests against one type of browser for the moment? Let's make that test run against that browser nice and fast, and then worry about cross-browser compatibility later.

Tip

It's probably a good idea to just pick one browser to run our tests against on our development machines. We can then use a CI server to pick up the slack and worry about browser coverage as part of our build pipeline. It's probably also a good idea to pick a browser with a fast JavaScript engine for our local machines.

 

Parallel tests with TestNG


The TestNG examples used in this chapter will be using TestNG version 6.9.4 and the Maven failsafe plugin version 2.17. If you use older versions of these components, the functionality that we are going to use may not be available.

To start off, we are going to make some changes to our POM file. We are going to add a threads property that will be used to determine the number of parallel threads used to run our checks. Then, we are going to use the maven-failsafe plugin to configure TestNG:

<properties>
    <project.build.sourceEncoding>UTF-8
    </project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8
    </project.reporting.outputEncoding>
    <!-- Dependency versions -->
    <selenium.version>2.45.0</selenium.version>
    <!-- Configurable variables -->
    <threads>1</threads>
</properties>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>2.17</version>
            <configuration>
                <parallel>methods</parallel>
                <threadCount>${threads}</threadCount>
                <includes>
                    <include>**/*WD.java</include>
                </includes>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>integration-test</goal>
                        <goal>verify</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Tip

When using the maven-failsafe plugin, the integration-test goal will ensure that your tests run in the integration test phase. The verify goal ensures that the failsafe-plugin checks the results of the checks run in the integration-test phase, and fails the build if something did not pass. If you don't have the verify goal, the build will not fail!

TestNG supports parallel threads out-of-the-box; we just need to tell it how to use them. This is where the maven-failsafe plugin comes in. We are going to use it to configure our parallel execution environment for our tests. This configuration will be applied to TestNG if you have TestNG as a dependency; you don't need to do anything special.

In our case, we are interested in the configuration settings of parallel and threadCount. We have set parallel to methods. This will search through our project for methods that have the @Test annotation and will collect them all into a great big pool of tests. The failsafe plugin will then take tests out of this pool and run them. The number of tests that will be run concurrently will depend on how many threads are available. We will use the threadCount property to control this.

It is important to note that there is no guarantee in which order tests will be run.

We are using the threadCount configuration setting to control how many tests we run in parallel but, as you may have noticed, we have not specified a number. Instead, we have used the Maven variable ${threads}; this will take the value of the Maven property threads that we defined in our properties block and pass it into threadCount.

Since threads is a Maven property, we are able to override its value on the command line by using the -D switch; if we do not override its value, it will use the value we have set in the POM as a default.

So let's run the following command:

mvn clean install

This command will use the default value of 1 in the POM file. However, if we use this:

mvn clean install -Dthreads=2

It will now overwrite the value of 1 stored in the POM file and use the value 2 instead. As you can see this gives us the ability to tweak the number of threads that we use to run our tests without making any code changes at all.

So we have used the power of Maven and the maven-failsafe plugin to set the number of threads that we want to use when running our tests in parallel. Now we need to modify our code to take advantage of this.

Previously, we were instantiating an instance of FirefoxDriver in each of our tests. Let's pull this out of the test, and put browser instantiation into its own class called WebDriverThread. We will then add a class called DriverFactory that will deal with the marshalling of the threads.

Tip

Downloading the example code

You can download the example code files from your account at http://www.packtpub.com for all the Packt Publishing books you have purchased. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

We are going to now build a project structure that looks like this:

First of all, we need to create our WebDriverThread class:

package com.masteringselenium;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.remote.DesiredCapabilities;

public class WebDriverThread {

    private WebDriver webdriver;

    private final String operatingSystem = System.getProperty("os.name").toUpperCase();
    private final String systemArchitecture = System.getProperty("os.arch");

    public WebDriver getDriver() throws Exception {
        if (null == webdriver) {
            System.out.println(" ");
            System.out.println("Current Operating System: " + operatingSystem);
            System.out.println("Current Architecture: " + systemArchitecture);
            System.out.println("Current Browser Selection: Firefox");
            System.out.println(" ");
            webdriver = new FirefoxDriver(DesiredCapabilities.firefox());
        }

        return webdriver;
    }

    public void quitDriver() {
        if (null != webdriver) {
            webdriver.quit();
            webdriver = null;
        }
    }
}

This class holds a reference to a WebDriver object, and ensures that every time you call getDriver() you get a valid instance of WebDriver back. If one has been started up, you will get the existing one. If one hasn't been started up, it will start one for you.

It also provides a quitDriver() method that will perform a quit() on your WebDriver object. It also nullifies the WebDriver object held in the class. This prevents errors that would be caused by attempting to interact with a WebDriver object that has been closed.

Note

We are using driver.quit(), not driver.close(). As a general rule of thumb, you should not use driver.close() to clean up. It will throw an error if something happened during your test that caused the WebDriver instance to close early. The close and clean up command in the WebDriver API is driver.quit(). You would normally use driver.close() if your test opens multiple windows and you want to shut some of them.

Next, we need to create a class called DriverFactory.java:

package com.masteringselenium;

import org.openqa.selenium.WebDriver;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeSuite;

public class DriverFactory {

    private static ThreadLocal<WebDriverThread> driverThread;

    @BeforeSuite
    public static void instantiateDriverObject() {
        driverThread = new ThreadLocal<WebDriverThread>() {
            @Override
            protected WebDriverThread initialValue() {
                WebDriverThread webDriverThread = new WebDriverThread();
                return webDriverThread;
            }
        };
    }

    public static WebDriver getDriver() throws Exception {return driverThread.get().getDriver();}

    @AfterMethod
    public static void quitDriver() throws Exception {
        driverThread.get().quitDriver();
    }
}

This is a small class that will hold a pool of driver objects. We are using a ThreadLocal object to instantiate our WebDriverThread objects in separate threads. We have also created a getDriver() method that uses the getDriver() method on the WebDriverThread object to pass each test a WebDriver instance it can use.

We are doing this to isolate each instance of WebDriver to make sure that there is no cross contamination between tests. When our tests start running in parallel, we don't want different tests to start firing commands to the same browser window. Each instance of WebDriver is now safely locked away in its own thread.

Since we are using this factory class to start up all our browser instances, we need to make sure that we close them down, as well. To do this we have created a method with an @AfterMethod annotation that will destroy the driver after our test has run. This also has the added advantage of cleaning up if our test fails to reach the line where it would normally call driver.quit()—for example, if there was an error in the test that caused it to fail and finish early.

All that is left now is to clean up the code in our basicTest class and change its name to BasicTestWD. You may have noticed that we added an <includes> configuration item to our POM. This is because Maven will use maven-surefire-plugin to run files that have test at the start or end of their name. We don't want maven-surefire-plugin to pick up our tests; we want to use maven-failsafe-plugin instead.

public class BasicTestWD extends DriverFactory {

    private void googleExampleThatSearchesFor(final String searchString) throws Exception {

        WebDriver driver = DriverFactory.getDriver();

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

        WebElement searchField = driver.findElement(By.name("q"));

        searchField.clear();
        searchField.sendKeys(searchString);

        System.out.println("Page title is: " + driver.getTitle());

        searchField.submit();

        (new WebDriverWait(driver, 10)).until(new ExpectedCondition<Boolean>() {
            public Boolean apply(WebDriver driverObject) {
                return driverObject.getTitle().toLowerCase().startsWith(searchString.toLowerCase());
            }
        });

        System.out.println("Page title is: " + driver.getTitle());
    }

    @Test
    public void googleCheeseExample() throws Exception {
        googleExampleThatSearchesFor("Cheese!");
    }

    @Test
    public void googleMilkExample() throws Exception {
        googleExampleThatSearchesFor("Milk!");
    }
}

We have modified our basic test so that it extends DriverFactory. Instead of instantiating a new FirefoxDriver instance in the test, we are calling DriverFactory.getDriver() to get a valid WebDriver instance. Finally we have removed the driver.quit() from each test as this is all done by our DriverFactory class now.

Let's spin up our test again using the following command:

mvn clean install

You won't notice any difference. However if you now specify some threads by performing this:

mvn clean install -Dthreads=2

You will see that this time two Firefox browsers open, both tests run in parallel, and then both browsers are closed again.

Tip

Are you seeing only one browser start up? In the maven-failsafe-plugin configuration, we have specified all files that end with WD.java. If you use filenames that start or end with Test, they will be picked up by the maven-surefire plugin and the threading configuration will be ignored. Double-check to make sure that your failsafe configuration is correct.

As you may have noticed, with two very small tests such as the ones we are using in our example, you will not see a massive decrease in the time taken to run the complete suite. This is because most of the time is spent compiling the code and loading up browsers. However, as you add more tests, the decrease in the time taken to run the tests becomes more and more apparent.

Note

This is probably a good time to tweak your BasicTest.java file and start adding some more tests that look for different search terms. Play about with the number of threads and see how many concurrent browsers you can get up-and-running at the same time. Make sure that you note down execution times to see what speed gains you are actually getting (they will also be useful later on in this chapter). There will come a point where you reach the limits of your computer's hardware and adding more threads will actually slow things down rather than making them faster. Tuning your tests to your hardware environment is an important part of running your tests in multiple threads.

So how can we speed things up even more? Well, starting up a web browser is a computationally intensive task, so we could choose not to close the browser after every test. This obviously has some side effects. You may not be at the usual entry page of your application, and you may have some session information that is not wanted.

First of all, we will try and deal with our session problem. WebDriver has a command that will allow you to clear out your cookies, so we will trigger this after every test. We will then add a new @AfterSuite annotation to close the browser once all of the tests have finished.

package com.masteringselenium;

import org.openqa.selenium.WebDriver;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeSuite;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class DriverFactory {

    private static List<WebDriverThread> webDriverThreadPool = Collections.synchronizedList(new ArrayList<WebDriverThread>());
    private static ThreadLocal<WebDriverThread> driverThread;

    @BeforeSuite
    public static void instantiateDriverObject() {
        driverThread = new ThreadLocal<WebDriverThread>() {
            @Overrideprotected WebDriverThread initialValue() {
                WebDriverThread webDriverThread = new WebDriverThread();
                webDriverThreadPool.add(webDriverThread);
                return webDriverThread;
            }
        };
    }

    public static WebDriver getDriver() throws Exception {
        return driverThread.get().getDriver();
    }

    @AfterMethod
    public static void clearCookies() throws Exception {
        getDriver().manage().deleteAllCookies();
    }

    @AfterSuite
    public static void closeDriverObjects() {
        for (WebDriverThread webDriverThread : webDriverThreadPool) {
            webDriverThread.quitDriver();
        }
    }
}

The first addition to our code is a synchronized list where we can store all our instances of WebDriverThread. We then modified our initialValue() method to add each instance of WebDriverThread that we create to this new synchronized list. This is so that we can keep track of all our threads.

Next we renamed our @AfterSuite method, to ensure that the method names stay as descriptive as possible. It is now called closeDriverObjects(). This method does not just close down the instance of WebDriver that we are using like it did previously. Instead, it iterates through our webDriverThreadPool, closing every threaded instance we are keeping track of.

We don't actually know how many threads we are going to have running since this will be controlled by Maven. Thanks to this code, we don't have to know. What we do know is that, when our tests are finished, each WebDriver instance will be closed down cleanly, and without errors.

Finally we have added an @AfterMethod method called clearCookies() that will clear down the browser's cookies after each test. This should reset the browser to a neutral state without closing it so that we can start another test safely.

Note

Have a go at tweaking your BasicTest.java file again by adding some more tests that look for different search terms. Based on your previous experimentation, you will probably have a rough idea of what the sweet spot for your hardware is. Time how long it takes to execute your tests again when you only close all the browsers down when all the tests have finished executing. How much time did you shave off your execution time?

 

There are no silver bullets


As with everything, keeping your browser windows open while you run all of your tests will not work in every instance.

Sometimes, you may have a site that sets server-side cookies that Selenium is unaware of. In this case, clearing down your cookies may have no effect and you may find that closing down the browser is the only way to ensure a clean environment for each test.

If you use InternetExplorerDriver, you will probably find when using slightly older versions of Internet Explorer (for example, Internet Explorer 8 and Internet Explorer 9) that your tests will get slower and slower until they grind to a halt. Unfortunately, older versions of IE are not perfect and they do have some memory leak issues.

Using InternetExplorerDriver does exacerbate these issues because it is really stressing the browser. As a result it does get a lot of unfair press. It's an excellent bit of code that deals with an awful lot of crap thrown at it.

This is not to say that you can't use this method; you may not see any issues with your site.

Removing the browser shutdown and start-up time after each test really does make a massive difference to the speed of your test runs. You should always try to keep the browser open whenever realistically possible.

The only way to be sure if it will work for you is experimentation and hard data. Just remember to do that investigation first. Once you are done, you should then tailor your thread usage to each browser/machine combination. Or you should set a baseline that works with everything in your environment.

 

Multiple browser support


So far, we have parallelized our tests so that we can run multiple browser instances at the same time. However, we are still using only one type of driver, the good old FirefoxDriver. I mentioned problems with Internet Explorer in the previous section, but right now we have no obvious way to run our tests using Internet Explorer. Let's have a look at how we can fix this.

To start off with, we will need to create a new Maven property called browser and a new configuration setting inside our failsafe plugin configuration called systemPropertyVariables. This is pretty much what is says on the tin; everything defined inside systemPropertyValues will become a system property, which will be available to your Selenium tests. We are going to use a Maven variable to reference a Maven property so that we can dynamically change this value on the command line.

Here are the changes you need to make to your POM:

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <!-- Dependency versions -->
    <selenium.version>2.45.0</selenium.version>
    <!-- Configurable variables -->
    <threads>1</threads>
    <browser>firefox</browser>
</properties>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>2.17</version>
            <configuration>
                <parallel>methods</parallel>
                <threadCount>${threads}</threadCount>
                <systemProperties>
                    <browser>${browser}</browser>
                </systemProperties>
                <includes>
                    <include>**/*WD.java</include>
                </includes>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>integration-test</goal>
                        <goal>verify</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

We now need to create a package where we are going to store our driver configuration code. We are going to add an interface, and an enum into this package.

DriverSetup is a very simple interface that DriverType will implement:

package com.masteringselenium.config;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.DesiredCapabilities;

public interface DriverSetup {

    WebDriver getWebDriverObject(DesiredCapabilities desiredCapabilities);

    DesiredCapabilities getDesiredCapabilities();
}

DriverType is where all the work is done:

package com.masteringselenium.config;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.opera.OperaDriver;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.safari.SafariDriver;

import java.util.Arrays;
import java.util.HashMap;

public enum DriverType implements DriverSetup {

    FIREFOX {
        public DesiredCapabilities getDesiredCapabilities() {
            DesiredCapabilities capabilities = DesiredCapabilities.firefox();
            return capabilities;
        }

        public WebDriver getWebDriverObject(DesiredCapabilities capabilities) {
            return new FirefoxDriver(capabilities);
        }
    },
    CHROME {
        public DesiredCapabilities getDesiredCapabilities() {
            DesiredCapabilities capabilities = DesiredCapabilities.chrome();
            capabilities.setCapability("chrome.switches", Arrays.asList("--no-default-browser-check"));
            HashMap<String, String> chromePreferences = new HashMap<String, String>();
            chromePreferences.put("profile.password_manager_enabled", "false");
            capabilities.setCapability("chrome.prefs", chromePreferences);
            return capabilities;
        }

        public WebDriver getWebDriverObject(DesiredCapabilities capabilities) {
            return new ChromeDriver(capabilities);
        }
    },
    IE {
        public DesiredCapabilities getDesiredCapabilities() {
            DesiredCapabilities capabilities = DesiredCapabilities.internetExplorer();capabilities.setCapability(CapabilityType.ForSeleniumServer.ENSURING_CLEAN_SESSION, true);capabilities.setCapability(InternetExplorerDriver.ENABLE_PERSISTENT_HOVERING, true);
            capabilities.setCapability("requireWindowFocus", true);
            return capabilities;
        }

        public WebDriver getWebDriverObject(DesiredCapabilities capabilities) {
            return new InternetExplorerDriver(capabilities);
        }
    },
    SAFARI {
        public DesiredCapabilities getDesiredCapabilities() {
            DesiredCapabilities capabilities = DesiredCapabilities.safari();
            capabilities.setCapability("safari.cleanSession", true);
            return capabilities;
        }

        public WebDriver getWebDriverObject(DesiredCapabilities capabilities) {
            return new SafariDriver(capabilities);
        }
    },
    OPERA {
        public DesiredCapabilities getDesiredCapabilities() {
            DesiredCapabilities capabilities = DesiredCapabilities.operaBlink();
            return capabilities;
        }

        public WebDriver getWebDriverObject(DesiredCapabilities capabilities) {
            return new OperaDriver(capabilities);
        }
    };
}

As you can see, our basic enum allows us to choose one of the default browsers supported by Selenium. Each enum entry implements a getDesiredCapabilities() method and a getWebDriverObject() method. This allows us to get a default set of capabilities that we could extend, if required. These desired capabilities can then be used to instantiate a new WebDriver object when calling the getWebDriverObject() method.

Let's have a look at the default desired capabilities we have set for each driver to help things run smoothly.

Firefox

Nothing to see here; the default Firefox profile is normally fine so it's just using all the defaults.

Chrome

We have a couple of options here to try and keep things running smoothly. Chrome has various command-line switches that can be used when starting it up with ChromeDriver. When we load up Chrome to run our tests, we don't want it asking us if it can be made the default browser every time it starts, so we have disabled that check. We have also turned off the password manager so that it does not ask if you would like to save your login details every time you have a test that performs a login action.

Internet Explorer

InternetExplorerDriver has a lot of challenges; it attempts to work with multiple different versions of Internet Explorer and generally does a very good job. These capabilities are used to try and ensure that sessions are properly cleaned out when reloading the browser (IE8 is particularly bad at clearing its cache), and then trying to fix some issues with hovering. If you have ever tested an application that needs you to hover over an element to trigger some sort of popup, you have probably seen the popup flickering a lot, and had intermittent failures when trying to interact with it. Setting ENABLE_PERSISTENT_HOVERING and requireWindowFocus should work around these issues.

Safari

Safari used to have some problems clearing down http-only cookies and secure cookies. The safari.cleanSession capability is a way to try and force a clear down of settings that SafariDriver (a JavaScript-only implementation) has problems with.

Opera

Again, nothing to see here. Opera Blink is relatively new so we are just going to go with the default set of options for now.

You don't need to use any of the desired capabilities described earlier; however, I have found them to be useful in the past, and I now use these as my default set.

If you don't want to use them, just remove the bits you aren't interested in and set up each getDesiredCapabilities() method like the FirefoxDriver one. Of course the opposite is also true; you can add in any desired capabilities that you find useful in your tests. This is going to be the place that instantiates a driver object so it's the best place to do it.

Now that everything is in place, we need to rewrite our WebDriverThread method:

package com.masteringselenium;

import com.masteringselenium.config.DriverType;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.DesiredCapabilities;

import java.net.MalformedURLException;

import static com.masteringselenium.config.DriverType.FIREFOX;
import static com.masteringselenium.config.DriverType.valueOf;

public class WebDriverThread {

    private WebDriver webdriver;
    private DriverType selectedDriverType;

    private final DriverType defaultDriverType = FIREFOX;
    private final String browser = System.getProperty("browser").toUpperCase();
    private final String operatingSystem = System.getProperty("os.name").toUpperCase();
    private final String systemArchitecture = System.getProperty("os.arch");

    public WebDriver getDriver() throws Exception {
        if (null == webdriver) {
            selectedDriverType = determineEffectiveDriverType();
            DesiredCapabilities desiredCapabilities = selectedDriverType.getDesiredCapabilities();
            instantiateWebDriver(desiredCapabilities);
        }

        return webdriver;
    }

    public void quitDriver() {
        if (null != webdriver) {
            webdriver.quit();
        }
    }

    private DriverType determineEffectiveDriverType() {
        DriverType driverType = defaultDriverType;
        try {
            driverType = valueOf(browser);
        } catch (IllegalArgumentException ignored) {
            System.err.println("Unknown driver specified, defaulting to '" + driverType + "'...");
        } catch (NullPointerException ignored) {
            System.err.println("No driver specified, defaulting to '" + driverType + "'...");
        }
        return driverType;
    }

    private void instantiateWebDriver(DesiredCapabilities desiredCapabilities) throws MalformedURLException {
        System.out.println(" ");
        System.out.println("Current Operating System: " + operatingSystem);
        System.out.println("Current Architecture: " + systemArchitecture);
        System.out.println("Current Browser Selection: " + selectedDriverType);System.out.println(" ");
        webdriver = selectedDriverType.getWebDriverObject(desiredCapabilities);
    }
}

There is quite a lot going on here. First of all, we have added some new variables:

  • One to read in the browser that we have specified

  • One to set a default browser type, in case one has not been specified for any reason

  • One to hold the type of driver that is going to be used for our tests after we have decided which browser we are going to use

We have then written a couple of new methods. The method called instantiateWebDriver() is basically the code that was previously inside getDriver(). We have just moved it out to isolate the function of actually instantiating a WebDriver object. The method called determineEffectiveDriverType() will take the browser variable and then try and work out which enum value maps across to the value it holds. If it cannot map a DriverType across to the value of browser, it will log an error and then default to our defaultDriverType.

The final part is the changes we have made to the getDriver() method. It now uses the new methods in WebDriverThread and DriverType to set up and return a valid WebDriver instance for your checks.

Let's try it out. First of all, let's check that everything still works like it used to:

mvn clean install –Dthreads=2

This time you should have seen no difference from the last time you ran it. Let's check the error handling next:

mvn clean install –Dthreads=2 –Dbrowser=iJustMadeThisUp

Again, it should have looked exactly the same as the previous run. We couldn't find an enum entry called IJUSTMADETHISUP, so we defaulted to FirefoxDriver.

Finally, let's try a new browser:

mvn clean install –Dthreads=2 –Dbrowser=chrome

You have probably had mixed success with this one. You will see that it tried to start up ChromeDriver, but if you don't have its executable installed on your system and a system property set up, it most likely threw an error saying that it couldn't find the ChromeDriver executable.

You can fix this by downloading the ChromeDriver binary and then setting up system properties to provide the path to the binary. This isn't really making our tests easy to run out-of-the-box for developers, though. It looks like we have more work to do.

 

Downloading the WebDriver binaries automatically


I came across this problem a few years ago, and at the time there wasn't an easy way to get hold of the binaries using Maven. So I did what anybody who is into open source software would do: I wrote a plugin to do it for me.

This plugin will allow you to specify a series of driver binaries to automatically download and remove the manual setup steps. It also means that you can enforce the version of driver binaries that are used, which removes lots of intermittent issues caused by people using different versions of the binaries that can behave differently, on different machines.

We are now going to enhance our project structure so that it looks like this:

Let's start off by tweaking our POM; we will need a new property that we will call overwrite.binaries.

<properties>
    <project.build.sourceEncoding>UTF-8
    </project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8
    </project.reporting.outputEncoding>
    <!-- Dependency versions -->
    <selenium.version>2.45.0</selenium.version>
    <!-- Configurable variables -->
    <threads>1</threads>
    <browser>firefox</browser>
    <overwrite.binaries>false</overwrite.binaries>
</properties>

We then need to add the driver-binary-downloader plugin:

<plugin>
    <groupId>com.lazerycode.selenium</groupId>
    <artifactId>driver-binary-downloader-maven-plugin</artifactId>
    <version>1.0.7</version>
    <configuration>
        <rootStandaloneServerDirectory>${project.basedir}
        /src/test/resources/selenium_standalone_binaries
        </rootStandaloneServerDirectory>
        <downloadedZipFileDirectory>${project.basedir}
        /src/test/resources/selenium_standalone_zips
        </downloadedZipFileDirectory>
        <customRepositoryMap>${project.basedir}
        /src/test/resources/RepositoryMap.xml
        </customRepositoryMap>
        <overwriteFilesThatExist>${
        overwrite.binaries}</overwriteFilesThatExist>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>selenium</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Finally, we need to add some new system properties to our maven-failsafe-plugin configuration:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>2.17</version>
    <configuration>
        <parallel>methods</parallel>
        <threadCount>${threads}</threadCount>
        <systemProperties>
            <browser>${browser}</browser>
            <!--Set properties passed in by the driver binary downloader-->
            <webdriver.chrome.driver>${webdriver.chrome.driver}</webdriver.chrome.driver>
            <webdriver.ie.driver>${webdriver.ie.driver}</webdriver.ie.driver>
            <webdriver.opera.driver>${webdriver.opera.driver}</webdriver.opera.driver>
        </systemProperties>
        <includes>
            <include>**/*WD.java</include>
        </includes>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>integration-test</goal>
                <goal>verify</goal>
            </goals>
        </execution>
    </executions>
</plugin>

The plugin runs in the TEST_COMPILE phase by default. The order in which it is placed in the POM should not matter, as there shouldn't be any tests actually running in this phase. The new overwite.binaries property that we have added allows us to set the overwriteFilesThatExist configuration setting of driver-binary-downloader-maven-plugin. By default it will not overwrite files that already exist. This gives us an option to force the plugin to overwrite existing files if we want to download a new binary version, or just refresh our existing binaries.

We have two more configuration settings that are just specifying file paths. The downloadedZipFileDirectory setting is used to specify the file path that will be used to download the binary zip files. The rootStandaloneServerDirectory setting is the file path where we extract the driver binaries.

Finally, we use customRepositoryMap to point at customRepositoryMap.xml. The customRepositoryMap.xml file is where download locations for all the binaries we want to download are stored.

Finally we have added some system properties variables to maven-failsafe-plugin, to expose the locations of the binaries when they have been downloaded. The plugin, driver-binary-downloader-maven-plugin, will set a Maven variable that will point to the location of the downloaded binaries. Even though it looks like the variables we are using to set our system properties don't exist, it will be fine.

This is where we have been slightly clever; we have set the system properties that Selenium will use automatically to find the location of the driver binaries. This means that we don't need to add any additional code to make things work.

We now need to create a RepositoryMap.xml to define the download locations for our binaries; we will probably also need to create the src/test/resources directory since we haven't used it before. Here is a basic RepositoryMap.xml file using the default download locations for the binaries:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<root>
    <windows>
        <driver id="internetexplorer">
            <version id="2.45.0">
                <bitrate sixtyfourbit="true">
                    <filelocation>http://selenium-release.storage.googleapis.com/2.45/IEDriverServer_x64_2.45.0.zip</filelocation><hash>b3cdacc846d7b9c3f8fb8b70af0a9cfc5839bd83</hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
                <bitrate thirtytwobit="true">
                    <filelocation>http://selenium-release.storage.googleapis.com/2.45/IEDriverServer_Win32_2.45.0.zip</filelocation>
                    <hash>cc822d30efe3119b76af9265c47d42fca208f85a</hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
            </version>
        </driver>
        <driver id="googlechrome">
            <version id="2.14">
                <bitrate thirtytwobit="true" sixtyfourbit="true">
                    <filelocation>http://chromedriver.storage.googleapis.com/2.14/chromedriver_win32.zip</filelocation>
                    <hash>4fe4aaf625073c39c29da994d815ffcc2c314c40</hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
            </version>
        </driver>
        <driver id="operachromium">
            <version id="2.14">
                <bitrate thirtytwobit="true" sixtyfourbit="true">
                    <filelocation>https://github.com/operasoftware/operachromiumdriver/releases/download/v0.1.0/operadriver_win32.zip</filelocation>
                    <hash>4a4ad051c315e4141048f0ae587c05f4c8720c24</hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
            </version>
        </driver>
    </windows>
    <linux>
        <driver id="googlechrome">
            <version id="2.14">
                <bitrate sixtyfourbit="true">
                    <filelocation>http://chromedriver.storage.googleapis.com/2.14/chromedriver_linux64.zip</filelocation>
                    <hash>acb76a3eb2bc94ee96b6a17121980e2662c88650</hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
                <bitrate thirtytwobit="true">
                    <filelocation>http://chromedriver.storage.googleapis.com/2.14/chromedriver_linux32.zip</filelocation>
                    <hash>237a5ed160bb23118a9ea5b84700e8799e897bd4</hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
            </version>
        </driver>
        <driver id="operachromium">
            <version id="2.14">
                <bitrate thirtytwobit="true">
                    <filelocation>https://github.com/operasoftware/operachromiumdriver/releases/download/v0.1.0/operadriver_linux32.zip</filelocation>
                    <hash>feda76d61190161bd9923f8f1613447f722f12fc</hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
                <bitrate sixtyfourbit="true">
                    <filelocation>https://github.com/operasoftware/operachromiumdriver/releases/download/v0.1.0/operadriver_linux64.zip</filelocation><hash>c36234222efccc1f874682b2ce2add639d544e9d</hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
            </version>
        </driver>
    </linux>
    <osx>
        <driver id="googlechrome">
            <version id="2.14">
                <bitrate thirtytwobit="true" sixtyfourbit="true">
                    <filelocation>http://chromedriver.storage.googleapis.com/2.14/chromedriver_mac32.zip</filelocation>
                    <hash>64ef44893a87a0e470b60ff8f5fc83a588b78023</hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
            </version>
        </driver>
        <driver id="operachromium">
            <version id="2.14">
                <bitrate thirtytwobit="true">
                    <filelocation>https://github.com/operasoftware/operachromiumdriver/releases/download/v0.1.0/operadriver_mac32.zip</filelocation>
                    <hash>7ab79a1c70bb0f5998b9c5c8d08160ef86b618e9</hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
                <bitrate sixtyfourbit="true">
                    <filelocation>https://github.com/operasoftware/operachromiumdriver/releases/download/v0.1.0/operadriver_mac64.zip</filelocation>
                    <hash>32e5e0fc63bed0f61bb4e8695fd7a8faaebd7b37</hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
            </version>
        </driver>
    </osx>
</root>

If you are on a corporate network that does not allow you to access the outside world, you can, of course, download the binaries and put them on a local file server. You can then update your RepositoryMap.xml file to point at this local fileserver instead of the Internet. This gives you a great deal of flexibility.

Right, let's run our project again to check that everything works. First of all, let's use this command:

mvn clean install –Dthreads=2

Everything should still work as normal. Next, let's see if we can now select Chrome and have everything automatically downloaded for us so that it can just run:

mvn clean install –Dthreads=2 –Dbrowser=chrome

This time you should see two Chrome browsers open up instead of the Firefox ones. The chromedriver binary will have been automatically downloaded and the system property that tells Selenium where to find it has been set.

We can now give anybody access to our code, and when they check it out and run it, things should just work.

 

Going headless


Going headless seems to be all the rage these days, so let's have a look at how we can add GhostDriver into the mix. We already have pretty much all of the code we need, so this is just going to be a few minor tweaks. Let's start off by updating our POM to bring in a dependency on GhostDriver:

<dependency>
    <groupId>com.codeborne</groupId>
    <artifactId>phantomjsdriver</artifactId>
    <version>1.2.1</version>
</dependency>

GhostDriver depends upon PhantomJS. This is another binary that most people will not have by default, but that's fine; the driver-binary-downloader plugin can get it for us.

Tip

This is not the official PhantomJSDriver distribution, but it fixes an error that came in with Selenium 2.44.0. Keep an eye on the group ID com.github.detro for official releases, or watch the official phantomjsdriver Github page https://github.com/detro/.

Let's update our RepositoryMap.xml file as follows:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<root>
    <windows>
        <driver id="internetexplorer">
            <version id="2.45.0">
                <bitrate sixtyfourbit="true">
                    <filelocation>http://selenium-release.
                    storage.googleapis.com/2.45/IEDriverServer
                    _x64_2.45.0.zip</filelocation>
                    <hash>b3cdacc846d7b9c3f8fb8b70af0a9cfc5839bd83
                    </hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
                <bitrate thirtytwobit="true">
                    <filelocation>http://selenium-release.storage.
                    googleapis.com/2.45/IEDriverServer
                    _Win32_2.45.0.zip</filelocation>
                    <hash>cc822d30efe3119b76af9265c47d42fca208f85a
                    </hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
            </version>
        </driver>
        <driver id="googlechrome">
            <version id="2.14">
                <bitrate thirtytwobit="true" sixtyfourbit="true">
                    <filelocation>http://chromedriver.storage.
                    googleapis.com/2.14/chromedriver_win32.zip
                    </filelocation>
                    <hash>4fe4aaf625073c39c29da994d815ffcc2c314c40
                    </hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
            </version>
        </driver>
        <driver id="operachromium">
            <version id="2.14">
                <bitrate thirtytwobit="true" sixtyfourbit="true">
                    <filelocation>https://github.com/operasoftware
                    /operachromiumdriver/releases/download/v0.1.0/
                    operadriver_win32.zip</filelocation>
                    <hash>4a4ad051c315e4141048f0ae587c05f4c8720c24
                    </hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
            </version>
        </driver>
        <driver id="phantomjs">
            <version id="1.9.8">
                <bitrate thirtytwobit="true" sixtyfourbit="true">
                    <filelocation>https://bitbucket.org/ariya/
                    phantomjs/downloads/phantomjs-1.9.8-
                    windows.zip</filelocation>
                    <hash>4531bd64df101a689ac7ac7f3e11bb7e77af8eff
                    </hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
            </version>
        </driver>
    </windows>
    <linux>
        <driver id="googlechrome">
            <version id="2.14">
                <bitrate sixtyfourbit="true">
                    <filelocation>http://chromedriver.storage.
                    googleapis.com/2.14/chromedriver_linux64.zip
                    </filelocation>
                    <hash>acb76a3eb2bc94ee96b6a17121980e2662c88650
                    </hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
                <bitrate thirtytwobit="true">
                    <filelocation>http://chromedriver.storage.
                    googleapis.com/2.14/chromedriver_linux32.zip
                    </filelocation>
                    <hash>237a5ed160bb23118a9ea5b84700e8799e897bd4
                    </hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
            </version>
        </driver>
        <driver id="operachromium">
            <version id="2.14">
                <bitrate thirtytwobit="true">
                    <filelocation>https://github.com/operasoftware
                    /operachromiumdriver/releases/download/v0.1.0/
                    operadriver_linux32.zip</filelocation>
                    <hash>feda76d61190161bd9923f8f1613447f722f12fc
                    </hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
                <bitrate sixtyfourbit="true">
                    <filelocation>https://github.com/operasoftware
                    /operachromiumdriver/releases/download/v0.1.0/
                    operadriver_linux64.zip</filelocation>
                    <hash>c36234222efccc1f874682b2ce2add639d544e9d
                    </hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
            </version>
        </driver>
        <driver id="phantomjs">
            <version id="1.9.8">
                <bitrate sixtyfourbit="true">
                    <filelocation>https://bitbucket.org/ariya/
                    phantomjs/downloads/phantomjs-1.9.8-linux-
                    x86_64.tar.bz2</filelocation>
                    <hash>d29487b2701bcbe3c0a52bc176247ceda4d09d2d
                    </hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
                <bitrate thirtytwobit="true">
                    <filelocation>https://bitbucket.org/ariya/
                    phantomjs/downloads/phantomjs-1.9.8-linux-
                    i686.tar.bz2</filelocation>
                    <hash>efac5ae5b84a4b2b3fa845e8390fca39e6e637f2
                    </hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
            </version>
        </driver>
    </linux>
    <osx>
        <driver id="googlechrome">
            <version id="2.14">
                <bitrate thirtytwobit="true" sixtyfourbit="true">
                    <filelocation>http://chromedriver.storage.
                    googleapis.com/2.14/chromedriver_mac32.zip
                    </filelocation>
                    <hash>64ef44893a87a0e470b60ff8f5fc83a588b78023
                    </hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
            </version>
        </driver>
        <driver id="operachromium">
            <version id="2.14">
                <bitrate thirtytwobit="true">
                    <filelocation>https://github.com/operasoftware
                    /operachromiumdriver/releases/download/v0.1.0
                    /operadriver_mac32.zip</filelocation>
                    <hash>7ab79a1c70bb0f5998b9c5c8d08160ef86b618e9
                    </hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
                <bitrate sixtyfourbit="true">
                    <filelocation>https://github.com/operasoftware
                    /operachromiumdriver/releases/download/v0.1.0/
                    operadriver_mac64.zip</filelocation>
                    <hash>32e5e0fc63bed0f61bb4e8695fd7a8faaebd7b37
                    </hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
            </version>
        </driver>
        <driver id="phantomjs">
            <version id="1.9.8">
                <bitrate thirtytwobit="true" sixtyfourbit="true">
                    <filelocation>https://bitbucket.org/ariya/
                    phantomjs/downloads/phantomjs-1.9.8-
                    macosx.zip</filelocation>
                    <hash>d70bbefd857f21104c5961b9dd081781cb4d999a
                    </hash>
                    <hashtype>sha1</hashtype>
                </bitrate>
            </version>
        </driver>
    </osx>
</root>

We will then need to update the system properties that we are setting in our POM so that Selenium knows where it needs to look for the PhantomJS binary:

<systemProperties>
    <browser>${browser}</browser>
    <!--Set properties passed in by the driver binary downloader-->
    <phantomjs.binary.path>${phantomjs.binary.path}</phantomjs.binary.path>
    <webdriver.chrome.driver>${webdriver.chrome.driver}</webdriver.chrome.driver>
    <webdriver.ie.driver>${webdriver.ie.driver}</webdriver.ie.driver>
    <webdriver.opera.driver>${webdriver.opera.driver}</webdriver.opera.driver>
</systemProperties>

Finally, we need to add a PhantomJS option into DriverType:

PHANTOMJS {
    public DesiredCapabilities getDesiredCapabilities() {
        DesiredCapabilities capabilities = DesiredCapabilities.phantomjs();
        final List<String> cliArguments = new ArrayList<String>();
        cliArguments.add("--web-security=false");
        cliArguments.add("--ssl-protocol=any");
        cliArguments.add("--ignore-ssl-errors=true");
        capabilities.setCapability("phantomjs.cli.args", cliArguments);
        capabilities.setCapability("takesScreenshot", true);

        return capabilities;
    }

    public WebDriver getWebDriverObject(DesiredCapabilities capabilities) {
        return new PhantomJSDriver(capabilities);
    }
}

As you can see, we have set some new capabilities. We are turning off any Web security or SSL error checking and enabling all SSL protocols. Most test environments don't have valid certificates set up; this will allow us to bypass the majority of certificate problems. We can then set a capability to allow us to take screenshots, as it's really quite useful.

We can now run our project again, but this time we are going to specify phantomjs as the browser:

mvn clean install –Dthreads=2 –Dbrowser=phantomjs

PhantomJS will automatically download, and all of the tests will run again. This time it will be slightly different, you won't see any browsers open up.

The really nice thing about this addition is that it is effectively downloading the browser that is required to run the tests. So a machine with no browsers installed will be able to run our tests and everything that is needed will be automatically downloaded. You can't really make your tests more accessible than that! Don't forget that you can easily change the default browser with a minor tweak to the defaultDriverType variable set in WebDriverThread. You may decide that PhantomJS is a better option for your default test runs.

 

Summary


After reading through this chapter, you should:

  • Be able to set up a basic project using Maven to download your dependencies, configure your class path, and build your code.

  • Know what advantages you gain by running your tests in parallel with multiple instances of the same browser in TestNG.

  • Know how to automatically download the driver binaries using a Maven plugin, making your test code very portable.

  • Be able to determine the correct number of threads to use as a default value when running your tests. You should also know how to override this, if required.

  • Know how to add GhostDriver into the mix so that you can run your tests heedlessly.

In the next chapter, we are going to have a look at how to cope when things go wrong. We will also examine how we can keep track of things, now that we have lots of tests all running at the same time.

About the Author

  • Mark Collin

    Mark Collin has been working in the software industry since 2001. He started his career in the financial sector before moving into consultancy. He has an eclectic range of skills and proficiencies, which include test automation, security and penetration testing, and performance testing. Mark is the creator and maintainer of driver-binary-downloader maven-plugin, and the Query library used in this book. He is also a core contributor to jmeter-maven-plugin, a tool that allows you to run JMeter tests through Maven. He has also contributed code to the core Selenium code base.

    Browse publications by this author

Latest Reviews

(5 reviews total)
Good book for learing how to use selenium.
I got what I wanted. Great
Again - it was a BARGAIN, as I paid $5 for it only. Content is worth a regular price.
Book Title
Access this book, plus 7,500 other titles for FREE
Access now