Selenium Framework Design in Data-Driven Testing

By Carl Cocchiaro
  • 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. Building a Scalable Selenium Test Driver Class for Web and Mobile Applications

About this book

The Selenium WebDriver 3.x Technology is an open source API available to test both Browser and Mobile applications. It is completely platform independent in that tests built for one browser or mobile device, will also work on all other browsers and mobile devices. Selenium supports all major development languages which allow it to be tied directly into the technology used to develop the applications. This guide will provide a step-by-step approach to designing and building a data-driven test framework using Selenium WebDriver, Java, and TestNG.

The book starts off by introducing users to the Selenium Page Object Design Patterns and D.R.Y Approaches to Software Development. In doing so, it covers designing and building a Selenium WebDriver framework that supports both Browser and Mobile Devices. It will lead the user through a journey of architecting their own framework with a scalable driver class, Java utility classes, JSON Data Provider, Data-Driven Test Classes, and support for third party tools and plugins.

Users will learn how to design and build a Selenium Grid from scratch to allow the framework to scale and support different browsers, mobile devices, versions, and platforms, and how they can leverage third party grids in the Cloud like SauceLabs.

Other topics covered include designing abstract base and sub-classes, inheritance, dual-driver support, parallel testing, testing multi-branded applications, best practices for using locators, and data encapsulation.

Finally, you will be presented with a sample fully-functional framework to get them up and running with the Selenium WebDriver for browser testing.

By the end of the book, you will be able to design your own automation testing framework and perform data-driven testing with Selenium WebDriver.

Publication date:
January 2018
Publisher
Packt
Pages
323
ISBN
9781788473576

 

Chapter 1. Building a Scalable Selenium Test Driver Class for Web and Mobile Applications

In this chapter, we will cover designing and building the Java test driver class required to create and use the Selenium WebDriver API and AppiumDriver API for automated testing. The driver class is the central location for all aspects and preferences of the browser and mobile devices, platforms and versions to run on, support for multithreading, support for the Selenium Grid Architecture, and customization of the driver. This chapter will cover the following topics:

  • Introduction
  • The singleton driver class
  • Using preferences to support browsers and platforms
  • Using preferences to support mobile device simulators, emulators, and real devices
  • Multithreading support for use in parallel and distributed testing
  • Passing optional arguments and parameters to the driver
  • Selenium Grid Architecture support using the RemoteWebDriver and AppiumDriver classes
  • Third-party grid architecture support, including the Sauce Labs Test Cloud
  • Using property files to select browsers, devices, versions, platforms, languages, and so on

Selenium headquarters website

 

Introduction


In this chapter, users will be introduced to data-driven testing, the Selenium Page Object Model, and Don't Repeat Yourself (DRY) approaches to testing, all of which work hand-in-hand with each other, and are required for scalable frameworks. Let's briefly discuss each.

Data-driven testing

The premise of data-driven testing is that test methods and test data are separated to allow the adding of new test permutations without changing the test methods, to reduce the amount of code, reduce the amount of maintenance required for testing, and to store common libraries in a central location—those being the page object classes. Data is encapsulated in a central location such as a database, JSON, or CSV file, property file, or an Excel spreadsheet, to name a few. Test methods then allow dynamic data to be passed into them on the fly using parameters and data providers of choice. The test methods themselves become "templates" for positive, negative, boundary, and/or limit testing, extending coverage of the suite of tests with limited code additions.

Note

TestNG data-driven testing tip:http://testng.org/doc/documentation-main.html

Selenium Page Object Model

The Selenium Page Object Model is based on the programming concepts that a page object class should include all aspects of the page under test, such as the elements on the page, the methods for interacting with those elements, variables, and properties associated with the class. Following that concept, there is no data stored in the page object class. The test classes themselves call methods on the page object instances they are testing, but have no knowledge of the granular elements in the class. Finally, the actual test data is encapsulated outside the test class in a central location. In other words, there is an abstract layer created between the tests and the actual page object classes. This reduces the amount of code being written and allows them to be reused in various testing scenarios, thus following the DRY approaches to programming. From a maintenance point of view, changes to methods and locators are made in limited, central places, reducing the amount of time required to maintain ever-changing applications.

DRY 

DRY approaches to creating page object and test classes simply mean promoting the use of common classes, locators, methods, and inheritance to eliminate and avoid repeating the same actions over and over in multiple places. Instead, abstract base classes are created, containing all common objects and methods, and used as libraries to be called using parameters, which vary based on the data that is passed into them from the test classes. All subclasses derived from these base classes inherit all the common code, objects, locators, and methods, and enforce all of the abstract methods required by the base class. In essence, this approach avoids common copy and paste actions that result in duplicate code in multiple places.

As per Wikipedia (https://en.wikipedia.org/wiki/Data-driven_testing):

"Data-driven testing (DDT) is a term used in the testing of computer software to describe testing done using a table of conditions directly, as test inputs and verifiable outputs as well as the process where test environment settings and control are not hardcoded. In the simplest form the tester supplies the inputs from a row in the table and expects the outputs which occur in the same row. The table typically contains values which correspond to boundary or partition input spaces. In the control methodology, test configuration is "read" from a database."

What you will learn

Users will learn how to design and build the Java singleton class required to control the Selenium driver of choice for the Application Under Test (AUT).

 

The singleton driver class


In this section, a Java singleton class will be used to create the driver class. This will force the user to use the same object for all instances where the WebDriver is required. The WebDriver events will never get out of sync during the run, and all WebDriver events will get sent to the correct browser or mobile device instance. And since the instance of the class is created on a single thread, referencing it won't interfere with other WebDriver instances running on the same node simultaneously.

As per Wikipedia (https://en.wikipedia.org/wiki/Singleton_pattern):

"In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system. The concept is sometimes generalized to systems that operate more efficiently when only one object exists, or that restrict the instantiation to a certain number of objects. The term comes from the mathematical concept of a singleton."

Requirements

In order to start building the framework, users must import the required JAR files into their project to use the Selenium WebDriver, AppiumDriver, and TestNG APIs. Additionally, there will be various Java JAR files required, such as Apache, Spring, File I/O, and other utilities as the framework develops:

import io.appium.java_client.AppiumDriver;
import io.appium.java_client.MobileElement;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.ios.IOSDriver;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.edge.EdgeOptions;
import org.openqa.selenium.firefox.*;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.LocalFileDetector;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.safari.SafariDriver;
import org.openqa.selenium.safari.SafariOptions;
import org.testng.*;

Note

A good source location for finding these JAR files is https://mvnrepository.com/.

The class signature

The class should be named something obvious such as Driver.java, CreateDriver.java, SeleniumDriver.java, and so on. Since this will be a Java singleton class, it will contain a private constructor and a static getInstance method as follows:

/**
 * Selenium Singleton Class
 *
 * @author CarlCocchiaro
 *
 */
@SuppressWarnings("varargs")
public class CreateDriver {

    // constructor
    private CreateDriver() {
    }

    /**
     * getInstance method to retrieve active driver instance
     *
     * @return CreateDriver
     */
    public staticCreateDriver getInstance() {
        if ( instance == null ) {
            instance = new CreateDriver();
        }

        return instance;
    }
}

Class variables

Initially, when building the class, there will be various private and public variables used that should be declared at the top of the class. This organizes the variables into one place in the file, but of course, this is a coding style guideline. Some of the common variables required to start are as follows:

public class CreateDriver {
    // local variables
    private static CreateDriver instance = null;
    private String browserHandle = null;
    private static final int IMPLICIT_TIMEOUT = 0;

    private ThreadLocal<WebDriver> webDriver =
            new ThreadLocal<WebDriver>();

    private ThreadLocal<AppiumDriver<MobileElement>> mobileDriver =
            new ThreadLocal<AppiumDriver<MobileElement>>();

    private ThreadLocal<String> sessionId =
            new ThreadLocal<String>();

    private ThreadLocal<String> sessionBrowser =
            new ThreadLocal<String>();

    private ThreadLocal<String> sessionPlatform =
            new ThreadLocal<String>();

    private ThreadLocal<String> sessionVersion =
            new ThreadLocal<String>();

    private String getEnv = null;
}

JavaDoc

Before introducing the common methods in this driver class, it is prudent to note that requiring JavaDoc for all methods in the class will be helpful for users who are learning the framework. The JavaDoc can be built automatically in Java using a build tool such as Maven, Gradle, or Ant. An example of the JavaDoc format is as follows:

/**
 * This is the setDriver method used to create the Selenium WebDriver
 * or AppiumDriver instance!
 *
 * @param parameter 1
 * @param parameter 2
 * @param parameter 3
 * @param parameter 4
 *
 * @throws Exception
 */

Parameters

The driver class will be designed with various get and set methods. The main setDriver method can take parameters to determine the browser or mobile type, platform to run on, environment for testing, and a set of optional preferences to allow changing driver behavior on the fly:

@SafeVarargs
public final void setDriver(String browser, 
                            String platform,
                            String environment,
                            Map<String, Object>... optPreferences)

Examples of some of the parameters of setDriver are as follows:

  • browser: Chrome, Firefox, Internet Explorer, Microsoft Edge, Opera, Safari (iPhone/iPad, or Android for mobile)
  • platform: Linux, Windows, Mac, Sierra, Win10 (iPhone/iPad, or Android for mobile)
  • environment: Local, remote, and Sauce Labs
  • optPrefs: Map of driver preferences (this will be covered later in detail)

Class methods

All the methods in this class should pertain to the web or mobile driver. This includes things such as setDriver, getDriver, getCurrentDriver, getSessionID/Browser/Version/Platform, driverWait, driverRefresh, and closeDriver. Each will be outlined in this section:

  • setDriver: The setDriver methods (standard and overloaded) will allow users to create a new instance of the driver for testing browser or mobile devices. The method will take parameters for browser, platform, environment, and optional preferences. Based on these preferences, the WebDriver/AppiumDriver of choice will be created. Here are some key points of the method, including a code sample:
  • The driver preferences are set up using the DesiredCapabilities class
  • The method will be segregated according to the browser or mobile type, platform, and environment
  • The method will be overloaded to allow switching back and forth between multiple drivers running concurrently

The following code demonstrates the standard setDriver method:

/**
 * setDriver method
 *
 * @param browser
 * @param environment
 * @param platform
 * @param optPreferences
 * @throws Exception
 */
@SafeVarargs
public final void setDriver(String browser,
                            String environment,
                            String platform,
                            Map<String, Object>... optPreferences)
                            throws Exception {

    DesiredCapabilities caps = null;
    String localHub = "http://127.0.0.1:4723/wd/hub";
    String getPlatform = null;

    switch (browser) {
        case "firefox":
            caps = DesiredCapabilities.firefox();
            webDriver.set(new FirefoxDriver(caps));

            break;
        case "chrome":
            caps = DesiredCapabilities.chrome();
            webDriver.set(new ChromeDriver(caps));

            break;
        case "internet explorer":
            caps = DesiredCapabilities.internetExplorer();
            webDriver.set(new 
                          InternetExplorerDriver(caps));

            break;
        case "safari":
            caps = DesiredCapabilities.safari();
            webDriver.set(new SafariDriver(caps));

            break;
        case "microsoftedge":
            caps = DesiredCapabilities.edge();
            webDriver.set(new EdgeDriver(caps));

            break;
        case "iphone":
        case "ipad":
            if (browser.equalsIgnoreCase("ipad")) {
                caps = DesiredCapabilities.ipad();
            }

            else {
                caps = DesiredCapabilities.iphone();
            }

            mobileDriver.set(new IOSDriver<MobileElement>(
                             new URL(localHub), caps));

            break;
        case "android":
            caps = DesiredCapabilities.android();
            mobileDriver.set(new 
                             AndroidDriver<MobileElement>(
                             new URL(localHub), caps));

            break;
    }
}

Here is the overloaded setDriver method:

/** 
 * overloaded setDriver method to switch driver to specific WebDriver
 * if running concurrent drivers
 *
 * @param driver WebDriver instance to switch to
 */
public void setDriver(WebDriver driver) {
    webDriver.set(driver);

    sessionId.set(((RemoteWebDriver) webDriver.get())
    .getSessionId().toString());

    sessionBrowser.set(((RemoteWebDriver) webDriver.get())
    .getCapabilities().getBrowserName());

    sessionPlatform.set(((RemoteWebDriver) webDriver.get())
    .getCapabilities().getPlatform().toString());

    setBrowserHandle(getDriver().getWindowHandle());
}

/**
 * overloaded setDriver method to switch driver to specific AppiumDriver
 * if running concurrent drivers
 *
 * @param driver AppiumDriver instance to switch to
 */
public void setDriver(AppiumDriver<MobileElement> driver) {
    mobileDriver.set(driver);

    sessionId.set(mobileDriver.get()
    .getSessionId().toString());

    sessionBrowser.set(mobileDriver.get()
    .getCapabilities().getBrowserName());

    sessionPlatform.set(mobileDriver.get()
    .getCapabilities().getPlatform().toString());
}
  • getDriver and getCurrentDriver: The getDriver and getCurrentDriver methods (standard and overloaded) will allow users to retrieve the current driver, whether that be browser or mobile. The driver should be instantiated at the beginning of the test, and will remain available throughout the test by using these methods. Since many of the Selenium WebDriver methods require the driver to be passed to it, these methods will allow users to retrieve the currently active session:
/**
 * getDriver method will retrieve the active WebDriver
 *
 * @return WebDriver
 */
public WebDriver getDriver() {
    return webDriver.get();
}

/**
 * getDriver method will retrieve the active AppiumDriver
 *
 * @param mobile boolean parameter
 * @return AppiumDriver
 */
public AppiumDriver<MobileElement> getDriver(boolean mobile) {
    return mobileDriver.get();
}

/**
 * getCurrentDriver method will retrieve the active WebDriver
 * or AppiumDriver
 *
 * @return WebDriver
 */
public WebDriver getCurrentDriver() {
    if ( getInstance().getSessionBrowser().contains("iphone") ||
         getInstance().getSessionBrowser().contains("ipad") ||
         getInstance().getSessionBrowser().contains("android") ) {

        return getInstance().getDriver(true);
    }

    else {
        return getInstance().getDriver();
    }
}
  • driverWait and driverRefresh: The driverWait method will "pause" the script for the designated amount of time in seconds, although this should not be used to synchronize event handling. The driverRefresh method will reload the currently active browser page:
/**
 * driverWait method pauses the driver in seconds
 *
 * @param seconds to pause
 */
public void driverWait(long seconds) {
    try {
        Thread.sleep(TimeUnit.SECONDS.toMillis(seconds));
    }

    catch (InterruptedException e) {
        // do something
    }
}

/**
 * driverRefresh method reloads the current browser page
 */
public void driverRefresh() {
    getCurrentDriver().navigate().refresh();
}
  • closeDriver: The closeDriver method will retrieve the current driver and call the WebDriver's quit method on it, browser or mobile:
/**
 * closeDriver method quits the current active driver
 */
public void closeDriver() {
    try {
        getCurrentDriver().quit();
    }

    catch ( Exception e ) {
        // do something
    }
}
 

Using preferences to support browsers and platforms


The browser preferences and behavior can be set to specific defaults when the driver is created, set on the fly using optional parameters, or set as system properties. Preferences can be set for different languages, geolocations, focus, download folders, and so on. This section will cover the basics of how to set default preferences and capabilities in the driver method.

Note

The Selenium HQ documentation on Desired Capabilities is located at https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities.

Browser preferences

  • Firefox: Preferences for this browser are set using the FirefoxProfile class, the FirefoxOptions class, and Desired Capabilities. The list of preferences and options set in the profile are then passed to the driver as DesiredCapabilites. The following example shows various profile preferences passed into the driver as default settings using both profile preferences and Desired Capabilities:
switch (browser) {
    case "firefox":
        caps = DesiredCapabilities.firefox();

FirefoxOptions ffOpts = new FirefoxOptions();
FirefoxProfile ffProfile = new FirefoxProfile();
        ffProfile.setPreference("browser.autofocus",
                                true);

        caps.setCapability(FirefoxDriver.PROFILE,
                           ffProfile);
        caps.setCapability("marionette",
                           true);

        webDriver.set(new FirefoxDriver(caps));

        // Selenium 3.7.x
        // webDriver.set(new FirefoxDriver(ffOpts.merge(caps)));
        }

        break;
}

Note

Firefox preferences can be found by typing the following into the Firefox location bar: about:config or at https://github.com/mozilla/geckodriver/.accessibility.AOM.enabled; falseaccessibility.accesskeycausesactivation; trueaccessibility.blockautorefresh; false...

  • Chrome: Preferences for this browser are set using the ChromeOptions class and Desired Capabilities. The list of preferences and/or arguments are then passed to the driver as DesiredCapabilites. The following example shows various preferences and arguments passed into the driver as default settings using both preferences and Desired Capabilities:

switch (browser) {
    case "chrome":
        caps = DesiredCapabilities.chrome();

        ChromeOptions chOptions = new ChromeOptions();
Map<String, Object> chromePrefs = 
        new HashMap<String, Object>();

        chromePrefs.put("credentials_enable_service",
                        false);
        chOptions.setExperimentalOption("prefs",
                                        chromePrefs);
        chOptions.addArguments("--disable-plugins", 
                               "--disable-extensions",
                               "--disable-popup-blocking");

        caps.setCapability(ChromeOptions.CAPABILITY,
                           chOptions);
        caps.setCapability("applicationCacheEnabled",
                           false);

        webDriver.set(new ChromeDriver(caps));

        // Selenium 3.7.x
        // webDriver.set(new ChromeDriver(chOptions.merge(caps)));

        break;
    }

Note

Chrome preferences can be found by typing the following into the Chrome location bar: chrome://flags or https://sites.google.com/a/chromium.org/chromedriver/capabilities.

  • Internet Explorer, Safari, and Microsoft Edge: Preferences for these browsers are also set using the InternetExplorerOptions, SafariOptions, EdgeOptions classes, and Desired Capabilities. Users can query for the available options and capabilities for each of these browsers. The following code sample shows an abbreviated case for each.

For Internet Explorer:

switch (browser) {
    case "internet explorer":
        caps = DesiredCapabilities.internetExplorer();

        InternetExplorerOptions ieOpts =
        new InternetExplorerOptions();
        ieOpts.requireWindowFocus();

ieOpts.merge(caps);
        caps.setCapability("requireWindowFocus",
                           true);

        webDriver.set(new InternetExplorerDriver(caps));

        // Selenium 3.7.x
        // webDriver.set(new InternetExplorerDriver(
                         ieOpts.merge(caps)));

        break;
}

For Safari:

switch (browser) {
    case "safari":
        caps = DesiredCapabilities.safari();

        SafariOptions safariOpts = new SafariOptions();
        safariOpts.setUseCleanSession(true);

        caps.setCapability(SafariOptions.CAPABILITY,
                           safariOpts);
caps.setCapability("autoAcceptAlerts",
                           true);

        webDriver.set(new SafariDriver(caps));

        // Selenium 3.7.x
        // webDriver.set(new SafariDriver(safariOpts.merge(caps)));

        break;
}

For Microsoft Edge:

switch(browser) {
    case "microsoftedge":
        caps = DesiredCapabilities.edge();

        EdgeOptions edgeOpts = new EdgeOptions();
        edgeOpts.setPageLoadStrategy("normal");

        caps.setCapability(EdgeOptions.CAPABILITY,
                           edgeOpts);
caps.setCapability("requireWindowFocus",
                           true);

        webDriver.set(new EdgeDriver(caps));

        // Selenium 3.7.x
        // webDriver.set(new EdgeDriver(edgeOpts.merge(caps)));

        break;
}

Platforms

There are some specific system properties that need to be set for each driver; specifically, the path to the local driver in the GIT repository of the project. By storing the driver in the project, users will not have to download or install the drivers for each browser when testing locally from their IDE. The path also depends on the OS of the development platform. The following examples are for Windows platforms:

  • Firefox:System.setProperty("webdriver.gecko.driver","gecko_driver_windows_path/geckodriver.exe");
  • Chrome:System.setProperty("webdriver.chrome.driver","chrome_driver_windows_path/chromedriver.exe");
  • IE:System.setProperty("webdriver.ie.driver","ie_driver_windows_path/IEDriverServer.exe");
  • Edge:System.setProperty("webdriver.edge.driver","edge_driver_windows_path/MicrosoftWebDriver.exe");
  • Safari: The Safari driver is now built into the browser by Apple
 

Using preferences to support mobile device simulators, emulators, and real devices


The mobile device preferences and behaviors can be set to specific defaults when the driver is created, set on the fly using optional parameters, or set as system properties. Preferences can be set for loading applications on the device, device options, timeouts, platform versions, device versions, and so on. This is accomplished using the Desired Capabilities class, as with browser testing. The following section provides examples of some of the mobile simulator, emulator, and physical device preferences.

iOS preferences

Preferences for iPhone/iPad mobile devices are set using the Desired Capabilities class. Capabilities are set for the iPhone and iPad simulators, or physical devices. The following example shows various capabilities for these iOS devices:

switch(browser) {
    case "iphone": case "ipad":
        if ( browser.equalsIgnoreCase("ipad") ) {
            caps = DesiredCapabilities.ipad();
        }

        else {
            caps = DesiredCapabilities.iphone();
        }

        caps.setCapability("appName",
                           "https://myapp.com/myApp.zip");

        caps.setCapability("udid", 
                           "12345678"); // physical device
caps.setCapability("device",
                           "iPhone"); // or iPad

        mobileDriver.set(new IOSDriver<MobileElement>
                         (new URL("http://127.0.0.1:4723/wd/hub"),
                         caps));

        break;

Note

The Desired Capabilities for iOS and Android can be found at http://appium.io/slate/en/master/?java#the-default-capabilities-flag.

Android preferences

Android: Preferences for these mobile devices are set using the Desired Capabilities class. Capabilities are set for Android Emulators, or physical devices. The following example shows various capabilities for these Android devices:

switch(browser) {
    case "android":
        caps = DesiredCapabilities.android();

        caps.setCapability("appName",
                           "https://myapp.com/myApp.apk");

        caps.setCapability("udid",
                           "12345678"); // physical device
        caps.setCapability("device",
                           "Android");

        mobileDriver.set(new AndroidDriver<MobileElement>
                         (new URL("http://127.0.0.1:4723/wd/hub"),
                         caps));

        break;
 

Multithreading support for parallel and distributed testing


In order to leverage the TestNG parallel testing features, users must create a separate thread for each driver instance to control event processing requests. This is done in Java using the ThreadLocal<T> class. By declaring variables with this class, each thread has its own initialized copy of the variable, and can return specifics of that session. The following variables are declared in the singleton driver class, and have getter and setter methods to retrieve the session ID, browser, platform, and version:

private ThreadLocal<WebDriver> webDriver = new ThreadLocal<WebDriver>();
private ThreadLocal<AppiumDriver<MobileElement>> mobileDriver = new ThreadLocal<AppiumDriver<MobileElement>>();

private ThreadLocal<String> sessionId = new ThreadLocal<String>();
private ThreadLocal<String> sessionBrowser = new ThreadLocal<String>();
private ThreadLocal<String> sessionPlatform = new ThreadLocal<String>();
private ThreadLocal<String> sessionVersion = new ThreadLocal<String>();

Key points:

  • The set methods are called by the setDriver methods during instantiation of the driver.
  • The get methods are stored in the singleton driver class and can be called after the driver is created. Users can retrieve session parameters for each specific instance of the driver that is running.
  • To leverage the separate instances during parallel test runs, TestNG suite parameters must also be used. For example:
<suite name="Parallel_Test_Suite" preserve-order="true" parallel="classes" thread-count="10">

These are examples of the getter methods for the driver class:

/**
 * getSessionId method gets the browser or mobile id
 * of the active session
 *
 * @return String
 */
public String getSessionId() {
    return sessionId.get();
}

/**
 * getSessionBrowser method gets the browser or mobile type
 * of the active session
 *
 * @return String
 */
public String getSessionBrowser() {
    return sessionBrowser.get();
}

/**
 * getSessionVersion method gets the browser or mobile version
 * of the active session
 * 
 * @return String
 */
public String getSessionVersion() {
    return sessionVersion.get();
}

/**
 * getSessionPlatform method gets the browser or mobile platform
 * of the active session
 *
 * @return String
 */
public String getSessionPlatform() {
    return sessionPlatform.get();
}

How to set:

The session ID, browser, version, and platform can be set during driver creation in the setDriver methods as follows:

getEnv = "local";
getPlatform = platform;

if ( browser.equalsIgnoreCase("iphone") ||
     browser.equalsIgnoreCase("android") ) {

    sessionId.set(((IOSDriver<MobileElement>)
    mobileDriver.get()).getSessionId().toString());

    sessionId.set(((AndroidDriver<MobileElement>)
    mobileDriver.get()).getSessionId().toString());

    sessionBrowser.set(browser);
    sessionVersion.set(caps.getCapability("deviceName").toString());
    sessionPlatform.set(getPlatform);
}

else {
    sessionId.set(((RemoteWebDriver) webDriver.get())
             .getSessionId().toString());

    sessionBrowser.set(caps.getBrowserName());
    sessionVersion.set(caps.getVersion());
    sessionPlatform.set(getPlatform);
}
 

Passing optional arguments and parameters to the driver


In many instances, users will want to change the default behavior of the browser before the test starts, or on the fly when creating a new driver during the test run. We previously covered setting default preferences and options in the setDriver method to keep the test environment static. Now, we can alter the default preferences using the varargs parameter in Java, as an optional parameter to the setDriver method. Here are the basics:

  • The varargs parameter to setDriver will be a Map<String, Object> type
  • Map can be passed into the driver when creating a new browser instance, or by setting a JVM argument of mapped preferences
  • JVM arguments used to pass in mapped preferences can be done in a TestNG XML file as a parameter, an IDE Run Configuration using a JVM arg, or as a -Dswitch to the command-line executable
  • Each browser type will need to process the map of Desired Capabilities, preferences, and options

varargs

The following example shows how to use the varargs parameter in the setDriver method, which is called optPreferences. This is the setDriver method so far, from what we have built:

@SafeVarargs
public final void setDriver(String browser,
                            String environment,
                            String platform,
Map<String, Object>... optPreferences)
                            throws Exception {

    DesiredCapabilities caps = null;
    String localHub = "http://127.0.0.1:4723/wd/hub";
    String getPlatform = null;

    switch (browser) {
        case "firefox":
            caps = DesiredCapabilities.firefox();
            FirefoxProfile ffProfile = new FirefoxProfile();

            ffProfile.setPreference("browser.autofocus",
                                     true);
            caps.setCapability(FirefoxDriver.PROFILE,
                               ffProfile);
            caps.setCapability("marionette",
                                true);
            System.setProperty("webdriver.gecko.driver",
            "gecko_driver_windows_path/geckodriver.exe");

if ( optPreferences.length > 0 ) {
                processFFProfile(ffProfile, optPreferences);
            }

            webDriver.set(new FirefoxDriver(caps));
            break;
        case "chrome":
            caps = DesiredCapabilities.chrome();
            ChromeOptions chOptions = new ChromeOptions();

            Map<String, Object> chromePrefs =
                                new HashMap<String, Object>();
            chromePrefs.put("credentials_enable_service",
                             false);
            chOptions.setExperimentalOption("prefs",
                                             chromePrefs);
            chOptions.addArguments("--disable-plugins",
                                   "--disable-extensions",
                                   "--disable-popup-blocking");
            caps.setCapability(ChromeOptions.CAPABILITY,
                               chOptions);
            caps.setCapability("applicationCacheEnabled",
                               false);
            System.setProperty("webdriver.chrome.driver",
            "chrome_driver_windows_path/chromedriver.exe");

if ( optPreferences.length > 0 ) {
                processCHOptions(chOptions, optPreferences);
            }

            webDriver.set(new ChromeDriver(caps));
            break;
        case "internet explorer":
            caps = DesiredCapabilities.internetExplorer();

            InternetExplorerOptions ieOpts =
                    new InternetExplorerOptions();

            ieOpts.requireWindowFocus();
            ieOpts.merge(caps);
            caps.setCapability("requireWindowFocus",
                                true);
            System.setProperty("webdriver.ie.driver",
            "ie_driver_windows_path/IEDriverServer.exe");

if ( optPreferences.length > 0 ) {
                processDesiredCaps(caps, optPreferences);
            }

            webDriver.set(new InternetExplorerDriver(caps));
            break;
    }

    // etc...
}

Note

The Oracle Java doc for varargs is located at https://docs.oracle.com/javase/8/docs/technotes/guides/language/varargs.html.

The parameter for setDriver

The next example shows how to pass Map into the setDriver method using the varargs parameter:

// first, create a map for the key:value pairs to pass into the driver
Map<String, Object> preferences = new HashMap<String, Object>;

// then put the key:value pairs into the map
preferences.put("applicationCacheEnabled",false);
preferences.put("network.cookie.cookieBehavior", 0);

// then, pass the map into the setDriver method
CreateDriver.getInstance().setDriver("firefox",
                                     "Windows 10",
                                     "local",
preferences);

JVM argument – -Dswitch

Finally, the next example shows how to set the optional browser preferences as a JVM argument using the TestNG parameter attribute in the suite XML file:

// pass in the key:value pairs as a runtime argument
-Dbrowserprefs=applicationCacheEnabled:false,
               network.cookie.cookieBehavior:0

// pass in the key:value pairs as a TestNG XML parameter
<test name="Selenium TestNG Test Suite">
    <parameter name="browser" value="chrome" />
    <parameter name="platform" value="Windows 10" />
    <parameter name="browserPrefs" value="intl.accept_languages:fr" />

    <classes>
        <class name="com.myproject.MyTest" />
    </classes>
</test>

// for convenience, create a setPreferences method
// to build the map to pass into the driver
public Map<String, Object> setPreferences() {
    Map<String, Object> prefsMap = new HashMap<String, Object>();
    List<String> allPrefs = Arrays.asList(
                 System.getProperty("browserPrefs").split(",", -1));

    // extract the key/value pairs and pass to map...
    for ( String getPref : allPrefs ) {
        prefsMap.put(getPref.split(":")[0], getPref.split(":")[1]);
    }

    return prefsMap;
}

// set JVM arg, call this method on-the-fly, create new driver
if ( System.getProperty("browserPrefs") != null ) {
    CreateDriver.getInstance().setDriver("firefox",
                                         "Windows 10",
                                         "local",
        CreateDriver.getInstance().setBrowserPrefs()
                                         );
}

Parameter processing methods

Once the optional preferences are passed into the setDriver method, the user then has to process those options. For instance, there may be DesiredCapabilities,  ChromeOptions, or FirefoxProfile preferences that need to be processed. First, for each driver-type instance, there needs to be a check to see if the options have been passed in, then if so, they have to be processed. Each type will be outlined as shown here:

/**
 * Process Desired Capabilities method to override default browser
 * or mobile driver behavior
 * 
 * @param caps - the DesiredCapabilities object
 * @param options - the key: value pair map
 * @throws Exception
 */
private void processDesiredCaps(DesiredCapabilities caps,
                                Map<String,
                                Object>[] options)
                                throws Exception {

    for ( int i = 0; i < options.length; i++ ) {
        Object[] keys = options[i].keySet().toArray();
        Object[] values = options[i].values().toArray();

        for ( int j = 0; j < keys.length; j++ ) {
            if ( values[j] instanceof Integer ) {
                caps.setCapability(keys[j].toString(),
                                  (int) values[j]);
            }
            else if ( values[j] instanceof Boolean) {
                caps.setCapability(keys[j].toString(),
                                  (boolean) values[j]);
            }
            else if ( isStringInt(values[j].toString()) ) {
                caps.setCapability(keys[j].toString(),

                Integer.valueOf(values[j].toString()));
            }
            else if ( Boolean.parseBoolean(values[j].toString()) ) {
                caps.setCapability(keys[j].toString(),

                Boolean.valueOf(values[j].toString()));
            }
            else {
                caps.setCapability(keys[j].toString(),
                                   values[j].toString());
            }
        }
    }
}

/**
 * Process Firefox Profile Preferences method to override default
 * browser driver behavior
 * 
 * @param caps - the FirefoxProfile object
 * @param options - the key: value pair map
 * @throws Exception
 */
private void processFFProfile(FirefoxProfile profile, Map<String, Object>[] options) throws Exception {
    for (int i = 0; i < options.length; i++) {
        Object[] keys = options[i].keySet().toArray();
        Object[] values = options[i].values().toArray();

        // same as Desired Caps except the following difference
        for (int j = 0; j < keys.length; j++) {
            if (values[j] instanceof Integer) {
                profile.setPreference(keys[j].toString(), 
                (int) values[j]);
            }

            // etc...
        }
    }
}
/**
 * Process Chrome Options method to override default browser
 * driver behavior
 * 
 * @param caps - the ChromeOptions object
 * @param options - the key: value pair map
 * @throws Exception
 */
private void processCHOptions(ChromeOptions chOptions, Map<String, Object>[] options) throws Exception {
    for (int i = 0; i < options.length; i++) {
        Object[] keys = options[i].keySet().toArray();
        Object[] values = options[i].values().toArray();

        // same as Desired Caps except the following difference

        for (int j = 0; j < keys.length; j++) {
            if (values[j] instanceof Integer) {
                values[j] = (int) values[j];
                chOptions.setExperimentalOption("prefs", options[i]);
            }

            // etc...
        }
    }
}
 

Selenium Grid Architecture support using the RemoteWebDriver and AppiumDriver classes


When creating a WebDriver instance, users will pass specified preferences, options, and capabilities to the driver running locally in their environment. As previously mentioned, users can store the actual Chrome driver, Firefox driver, and other driver files in their repo, so they won't have to be installed in each development environment. They can then point the local driver instance to the repo location using a desired capability.

Now, when designing and using the Selenium Grid Architecture to run tests against, the user will have to cast the browser or mobile capabilities to the RemoteWebDriver class, or remote AppiumDriver server. This capability should be built into the driver class as well, so the same class can support local, remote, and third-party test platforms. The Selenium Grid Architecture will be discussed in great detail in a separate chapter, but the relevance here is what needs to go into this driver class. Also, keep in mind that users must pass parameters into their driver class to change the environment from local to remote, or thirdParty to direct traffic to the grid nodes.

  • WebDriver: The URL of the remote grid hub, browser capabilities, driver-specific casting, and any Selenium Grid Node capabilities that control directing traffic to the specific Selenium standalone server node
  • AppiumDriver: The URL of the remote grid hub, mobile device capabilities, and any Selenium Grid Node capabilities that control directing traffic to the specific Appium server node

Here is the code for the preceding explanation:

// for each browser instance
if ( environment.equalsIgnoreCase("remote") ) {
    // set up the Selenium Grid capabilities...
    String remoteHubURL = "http://mygrid-
    hub.companyname.com:4444/wd/hub";

    caps.setCapability("browserName",
                        browser);
    caps.setCapability("version",
                        caps.getVersion());
    caps.setCapability("platform",
                        platform);

    // unique user-specified name
    caps.setCapability("applicationName",
                        platform + "-" + browser);

    webDriver.set(new RemoteWebDriver(new URL(remoteHubURL), caps));
    ((RemoteWebDriver) webDriver.get()).setFileDetector(
                                        new LocalFileDetector());
}

// for each mobile device instance
if ( environment.equalsIgnoreCase("remote") ) {
    // setup the Selenium Grid capabilities...
    String remoteHubURL = "http://mygrid-
    hub.companyname.com:4444/wd/hub";

    caps.setCapability("browserName",
                        browser);
    caps.setCapability("platform",
                        platform);

    // unique user-specified name
    caps.setCapability("applicationName",
                        platform + "-" + browser);

    if ( browser.contains("iphone") ) {
        mobileDriver.set(new IOSDriver<MobileElement>
                        (new URL(remoteHubURL),
                         caps));
    }

    else {
        mobileDriver.set(new AndroidDriver<MobileElement>
                        (new URL(remoteHubURL),
                         caps));
    }
}
 

Third-party grid architecture support including the Sauce Labs Test Cloud


When adding support to the driver class for third-party grids such as Sauce Labs or Perfecto Mobile, users must add conditions in the driver class that set specific preferences, credentials, URLs, and so on, to direct traffic to that test platform. They are really just other Selenium grids to run against in the cloud, which free up the tester from all the maintenance requirements of an in-house grid. The condition to run on one of these third-party platforms can be passed as a parameter to the test, specifically environment. For instance, here is an example of a TestNG XML file using parameters to set up the driver:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">

<suite name="My Test Suite" preserve-order="true" parallel="false" thread-count="1" verbose="2">

<!-- suite parameters -->
    <!-- "local", "remote", "saucelabs" -->
    <parameter name="environment" value="saucelabs" />

    <test name="My Feature Test">
        <!-- test parameters -->
        <parameter name="browser" value="chrome" />
        <parameter name="platform" value="Windows 10" />

        or

        <parameter name="browser" value="iphone"/>
        <parameter name="platform" value="iphone"/>

        <classes>
            <class name="com.myproject.MyTest" />
        </classes>
    </test>
</suite>

Each provider will require a different RemoteWebDriver URL, credentials to access their test cloud, preferences, and various other features that would allow access to a DMZ inside a corporate Firewall. Here are some examples of specific Sauce Labs Cloud platform requirements:

  • Tunnel: If the web server, or any other servers, are behind a corporate Firewall and not open to the internet, then a unique tunnel will have to be set up and passed to the driver class as a Desired Capability.
  • Remote URL: Sauce Labs has its own RemoteWebDriver URL for accessing its server at http://SAUCE_USERNAME:[email protected]:80/wd/hub.
  • Preferences: Sauce Labs has a set of unique capabilities that allow the passing of when creating the driver for the test. Examples include screen resolution, browser versions (including latest and beta versions), mobile device types (including physical and simulator/emulator devices), Selenium versions, driver versions, session parameters, results processing, and so on.

Note

The Sauce Labs Wiki documentation, which includes Desired Capabilities and Platform Configurator, is located at https://wiki.saucelabs.com/.

// third party preferences for SauceLabs...

if ( environment.equalsIgnoreCase("saucelabs") ) {
    // setup the Selenium Grid capabilities...
    String remoteHubURL =  
           "http://SAUCE_USERNAME:SAUCE_ACCESS_KEY
           @ondemand.saucelabs.com:80/wd/hub";

    caps.setCapability("screenResolution",
                       "1920x1080");
    caps.setCapability("recordVideo",
                        false);
    caps.setCapability("tunnelIdentifier",
                        System.getProperty("TUNNEL_IDENTIFIER"));
   ...
}
 

Using property files to select browsers, devices, versions, platforms, languages, and many more


Rather than hardcoding default URLs, paths, revisions, mobile device settings, and so on into the driver class itself, it makes more sense to encapsulate all those settings into a properties file. This way, users do not have to traverse through code to change a setting, driver version, or any paths required to support running the driver across platforms such as Windows, iOS, and Linux. Also, different sets of properties can be stored in the file for different environments such as local, remote, or third-party grids. Properties can be stored and retrieved in Java using the Properties class. The following code examples show property file formats, and the use of properties files in the Selenium driver class:

// Properties Class
public class CreateDriver {
    private Properties driverProps = new Properties();
    private static final String propertyFile = new File
        ("../myProject/com/path/selenium.properties").getAbsolutePath();

    @SafeVarargs
    public final void setDriver(String browser,
                                String environment,
                                String platform,
                                Map<String, Object>... optPreferences)
                                throws Exception {

        DesiredCapabilities caps = null;

// load properties from file...
        driverProps.load(new FileInputStream(propertyFile));

        switch (browser) {
            case "firefox":
                caps = DesiredCapabilities.firefox();

                // see previous example for caps...
                if ( environment.equalsIgnoreCase("local") ) {
                    if ( platform.toLowerCase().contains("windows") ) {
                        System.setProperty("webdriver.gecko.driver",
driverProps.getProperty(
                        "gecko.driver.windows.path"));
                    }

                    webDriver.set(new FirefoxDriver(caps));
                }

                break;
        }
}

Here is the selenium.properties file:

// selenium.properties file
# Selenium 3 WebDriver/AppiumDriver Properties File

# Revisions
selenium.revision=3.4.0
chrome.revision=2.30
safari.revision=2.48.0
gecko.revision=0.17.1

# Firefox Settings
gecko.driver.windows.path=../path/geckodriver-v0.17.1-win64/geckodriver.exe
gecko.driver.linux.path=../path/geckodriver-v0.17.1-linux64/geckodriver
gecko.driver.mac.path=../path/geckodriver-v0.17.1-macos/geckodriver
 

Summary


The Selenium driver class is the "engine" that controls the browser or mobile device under test. It determines which driver type to create, the look and feel of the driver, the default preferences, multithreading capabilities, settings, and whether to run the test locally or on the Selenium grid. It is a self-contained singleton class that creates one instance of the driver that is used throughout the entire test run. All session parameters are retrievable throughout the run, and they can be tracked to allow multiple drivers to run concurrently, in a browser-to-mobile test, or in a parallel/distributed environment.

As we progress through the framework components, users will see how important this class becomes to the integrity of the test. We will start by designing and building utility classes to support the framework.

About the Author

  • Carl Cocchiaro

    Carl Cocchiaro has a bachelor's degree in business and over 30 years of experience in the software engineering field, designing and building test frameworks for desktop, browser, and mobile applications. He is an expert in the Selenium WebDriver/TestNG Java-based technologies. He is a certified SilkTest engineer and has architected UI and RESTful API automation frameworks for 25 major corporations. Carl is currently a software architect, quality engineering at RSA/Dell technologies, Boston, MA, USA.

    Browse publications by this author
Book Title
Access this book, plus 7,500 other titles for FREE
Access now