com.partnet.automation.selenium.AbstractConfigurableDriverProvider.java Source code

Java tutorial

Introduction

Here is the source code for com.partnet.automation.selenium.AbstractConfigurableDriverProvider.java

Source

/*
 * Copyright 2015 Partnet, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.partnet.automation.selenium;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import io.appium.java_client.android.AndroidDriver;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.openqa.selenium.Cookie;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeDriverService;
import org.openqa.selenium.firefox.FirefoxBinary;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.phantomjs.PhantomJSDriver;
import org.openqa.selenium.phantomjs.PhantomJSDriverService;
import org.openqa.selenium.remote.Augmenter;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.LocalFileDetector;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.partnet.automation.Browser;
import com.partnet.automation.RuntimeConfiguration;
import com.partnet.automation.download.OperatingSystem;
import com.partnet.automation.download.StandaloneDriverDownloadAssistant;

/**
 * Delegating implementation that provides {@link WebDriver} instances specified
 * by various means.
 */
public abstract class AbstractConfigurableDriverProvider implements DriverProvider {

    // too bad we can't inject the runConfig into abstract classes...
    protected RuntimeConfiguration runConfig = RuntimeConfiguration.getInstance();

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

    private static final Logger LOG = LoggerFactory.getLogger(AbstractConfigurableDriverProvider.class);

    // TODO: Feb 4, 2015 (bbarker) - Fix proxy setting here
    protected final String USE_PROXY_BY_DEFAULT = "test.config.default.useProxy";
    protected final String BROWSER_SYSTEM_PROPERTY_NAME = "test.config.browser";
    protected final String SELENIUM_REMOTE_URL = "test.config.selenium.url";
    protected final String REMOTE_WEBDRIVER_RETRY_ATTEMPTS = "remote.webdriver.retry.attempts";
    protected final String REMOTE_WEBDRIVER_RETRY_PAUSE_MILLIS = "remote.webdriver.retry.pause.millis";
    protected final String PAGE_LOAD_TIMEOUT_SECONDS = "test.config.page.load.timeout";
    protected final String PHANTOM_JS_BIN_PROP = "test.config.driver.phantomjs.bin";
    protected final String CHROME_DRIVER_BIN_PROP = "test.config.driver.chrome.bin";
    protected final String IE_DRIVER_BIN_PROP = "test.config.driver.ie.bin";
    protected final String ALLOW_SCREENSHOTS = "test.config.driver.screenshots.allow";
    protected final String DRIVER_BIN_PATH_APPEND = ".path";
    protected final String WINDOWS_APPEND = ".windows";
    protected final String MAC_APPEND = ".mac";
    protected final String ANDROID_APP_PKG = "test.config.android.app.package";
    protected final String ANDROID_APP_WAIT_PKG = "test.config.android.app.wait.package";
    protected final String ANDROID_PLATFORM_NAME = "test.config.android.platform.name";
    protected final String ANDROID_PLATFORM_VERSION = "test.config.android.platform.version";
    protected final String ANDROID_SELENDROID_PORT = "test.config.android.selendroid.port";
    protected final String ANDROID_DEVICE_NAME = "test.config.android.device.name";
    protected final String ANDROID_APK_PATH = "test.config.android.apk.path";
    protected final String ANDROID_BROWSER_NAME = "android";

    /**
     * Get a {@link WebDriver} for the specified {@link Browser}
     * 
     * For a quick start, use
     * {@link AbstractConfigurableDriverProvider#getDefaultWebDriver(Browser)}
     * 
     * @param browser desired browser for test
     *                
     * @return handle to {@link WebDriver}
     */
    protected abstract WebDriver getWebDriver(Browser browser);

    /**
     * This is the default implementation of getting a web driver.
     * 
     * Use this as a quick start guide for
     * {@link AbstractConfigurableDriverProvider#getWebDriver(Browser)}
     * 
     * @param browser browser to obtain
     * @return instance of the specific WebDriver
     */
    protected WebDriver getDefaultWebDriver(Browser browser) {
        Objects.requireNonNull(browser, "browser cannot be null");

        WebDriver driver;
        boolean useRemoteBrowser = (getRemoteSeleniumUrlString() != null);

        LOG.debug("Initialize: '{}' using remote browser: '{}'", browser, useRemoteBrowser);
        switch (browser) {
        case IE:
            driver = useRemoteBrowser ? getRemoteInternetExplorerWebDriver() : getInternetExplorerWebDriver();
            break;

        case CHROME:
            driver = useRemoteBrowser ? getRemoteChromeWebDriver() : getChromeWebDriver();
            break;

        case FIREFOX:

            driver = useRemoteBrowser ? getRemoteFirefoxWebDriver() : getFirefoxWebDriver();
            break;

        case PHANTOMJS:
            driver = useRemoteBrowser ? getRemotePhantomJsWebDriver() : getPhantomJsWebDriver();
            break;

        case HTMLUNIT:
            if (useRemoteBrowser) {

                // Don't throw here, in case running with a Grid.
                LOG.info("Running HTMUNIT on the grid is NOT supported! If you want to take the time to create "
                        + "a HTTP connection to the grid, you might as well just use PhantomJs. ");
            }

            driver = getHtmlUnitWebDriver();
            break;

        case ANDROID:
            driver = getRemoteAndroidWebDriver();
            break;

        default:
            throw new IllegalArgumentException(String.format("Unsupported browser: '%s'", browser.name()));
        }

        LOG.debug("Using WebDriver implementation of type : {}", driver.getClass());

        if (!browser.isAndroid()) {
            driver.manage().timeouts().pageLoadTimeout(Integer.getInteger(PAGE_LOAD_TIMEOUT_SECONDS, 120),
                    TimeUnit.SECONDS);
        }

        return driver;
    }

    /**
     * Sets the given web driver to the current thread
     *
     * @param driver {@link WebDriver} to set for current thread
     */
    protected void set(WebDriver driver) {
        this.delegate.set(driver);
    }

    @Override
    public WebDriver get() {
        WebDriver driver = this.delegate.get();

        if (driver == null) {
            LOG.debug("driver on this thread is not currently running!");
        }
        return driver;
    }

    @Override
    public void end() {
        // suggestion to close web driver before quitting to prevent socket lock on
        // 7054
        // https://code.google.com/p/selenium/issues/detail?id=7272
        // https://code.google.com/p/selenium/issues/detail?id=4790
        WebDriver driver = this.get();

        if (driver == null) {
            LOG.debug("No driver to stop");
            return;
        }

        LOG.debug("Stopping driver");
        //closing android browser is not supported
        if (!Browser.getBrowser(driver).isAndroid()) {
            driver.close();
        }

        this.get().quit();
        this.set(null);
    }

    /**
     * @see #launch(Browser)
     */
    @Override
    public void launch() {
        launch(null);
    }

    /**
     * Initialize this {@link DriverProvider} for the current running thread. If
     * no value (null) is specified, the default browser is used.
     * 
     * @param browser
     *          - The browser to use, unless a higher priority browser as defined
     *          by {@link #getBrowserFromProperty()} is set.
     * 
     * @see #getDefaultBrowser()
     */
    public void launch(Browser browser) {
        // system property value takes priority
        Browser browserFromProp = getBrowserFromProperty();

        if (browserFromProp != null) {
            LOG.debug("Browser from property in use: '{}'", browserFromProp.name());
        } else if (browser == null) {
            browserFromProp = getDefaultBrowser();
            LOG.debug("Default browser in use: '{}'", browserFromProp.name());
        } else {
            browserFromProp = browser;
            LOG.debug("Using specified browser: '{}'", browserFromProp.name());
        }

        Objects.requireNonNull(browserFromProp, "browser to use cannot be null");

        end();
        this.set(getWebDriver(browserFromProp));
    }

    /**
     * Captures the html of the webdriver, and writes it to the specified path.
     * <p>
     * There is also the option of replacing all of the relative paths with a
     * given base url of the site so the page can be rendered when opened with a
     * browser.
     * 
     * @param htmlPath
     *          - path and filename of where to save the html file to.
     * @param baseUrl
     *          - replace relative path of html with this base url. If it is null
     *          or blank, it will skip replacing the relative paths.
     */
    public void saveHtml(String htmlPath, String baseUrl) {
        if (StringUtils.isNotBlank(baseUrl) && !baseUrl.endsWith("/")) {
            baseUrl += "/";
        }

        LOG.debug("Write html to: {}", htmlPath);

        String pageSource = this.get().getPageSource();

        if (StringUtils.isNotBlank(baseUrl)) {
            pageSource = pageSource.replaceAll("=(\\s)?\"/", "=\"" + baseUrl);
        }

        try {
            // write to file, replacing relative path with something that it will find
            // and render
            FileUtils.write(new File(htmlPath), pageSource, "iso-8859-1");
        } catch (IOException e) {
            LOG.error("Error writing html to '{}'!", htmlPath, e);
        }
    }

    /**
     * Screenshooter for HTMLUnit. It saves the html source to disk following the
     * same pattern as the screenshot path. The HTMLUnit session is transfered to
     * PhantomJs, which takes the screenshot, and is destroyed. The original
     * driver is not destroyed
     * 
     * Note: Javascript events, current page changes, etc.. are not saved and are
     * not captured in the screenshots taken.
     * 
     * @param path
     *          - where to save the file. This assumes a png file will be
     *          generated
     * @param baseUrl
     *          - used to transfer the cookies to the phantomjs driver properly.
     * 
     * @see #getPhantomJsWebDriver()
     */
    public void saveScreenshotForHtmlUnit(String path, String baseUrl) {
        final WebDriver driver = this.get();

        if (!(driver instanceof HtmlUnitDriver)) {
            LOG.warn("Wrong driver called screenshooter for HTMLUnit driver, default to regular screenshooter");
            this.saveScreenshotAs(path);
            return;
        }

        PhantomJSDriver phantomJs = (PhantomJSDriver) getPhantomJsWebDriver();

        try {
            phantomJs.get(baseUrl);
            String url = driver.getCurrentUrl();
            LOG.debug("Url: {}", url);

            for (Cookie cookie : driver.manage().getCookies()) {
                LOG.debug("Cookie: {}", cookie.toString());
                phantomJs.manage().addCookie(cookie);
            }

            phantomJs.get(url);

            // set current thread to phantomjs, and take screenshot in the default way
            this.set(phantomJs);
            LOG.debug("HTML Screenshot taken: {}", this.saveScreenshotAs(path));
        } finally {
            // set back original driver for this thread
            this.set(driver);

            phantomJs.close();
            phantomJs.quit();
        }
    }

    /**
     * Takes screenshot of the current driver. Screeshot will be skipped if
     * test.config.driver.screenshots.allow is set to false.
     * 
     * @param path location where the screenshot should be saved at
     * @return true if succeeded, false otherwise
     */
    @Override
    public boolean saveScreenshotAs(String path) {

        if (!Boolean.valueOf(System.getProperty(ALLOW_SCREENSHOTS, "true"))) {
            LOG.debug("Skipping screenshot");
            return false;
        }

        WebDriver driver = this.get();

        if (driver instanceof HtmlUnitDriver) {
            LOG.info("Additional work needs to be done to take screenshots with HTMLUnit driver, skipping. "
                    + "Email the SeAuto user list for more information.");
            return false;
        }

        if (driver instanceof TakesScreenshot) {
            File dstFile = new File(path);
            File srcFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
            try {
                FileUtils.copyFile(srcFile, dstFile);
            } catch (IOException e) {
                throw new RuntimeException("Can't save screenshot!", e);
            }
            return true;
        }
        return false;

    }

    /**
     * This returns the value stored in the browser property, not the browser for
     * the current thread.
     * 
     * @return Browser, or null if a browser is not defined
     * @see #BROWSER_SYSTEM_PROPERTY_NAME
     */
    protected Browser getBrowserFromProperty() {
        String browser = System.getProperty(BROWSER_SYSTEM_PROPERTY_NAME);

        return browser == null ? null : Browser.valueOfByName(browser.toUpperCase());
    }

    /**
     * Used to indicate whether or not a proxy should be used by default.
     * 
     * @see #USE_PROXY_BY_DEFAULT
     * @return true if using proxy, false otherwise
     */
    protected boolean useProxy() {
        boolean useProxy = true;
        String useProxyValue = System.getProperty(USE_PROXY_BY_DEFAULT);
        if (useProxyValue != null) {
            useProxy = Boolean.getBoolean(USE_PROXY_BY_DEFAULT);
            LOG.info("by default, useProxy: {}", useProxy);
        }
        return useProxy;
    }

    /**
     * Get the browser to use by default; HTMLUNIT is the optimistic default.
     * @return the default browser to use, if one is not defined.
     */
    protected Browser getDefaultBrowser() {
        return Browser.HTMLUNIT;
    }

    /**
     * Get the value of the remote URL to connect to.
     *
     * @return null if the system property {@link #SELENIUM_REMOTE_URL} is blank, otherwise returns the {@link URL}
     * @see #SELENIUM_REMOTE_URL
     * @throws IllegalArgumentException if the {@link #SELENIUM_REMOTE_URL} is malformed.
     */
    protected URL getRemoteSeleniumUrl() {
        URL url;
        String stringUrl = getRemoteSeleniumUrlString();
        try {
            url = new URL(stringUrl);
        } catch (MalformedURLException e) {
            LOG.error("invalid url: {}", stringUrl, e);
            throw new IllegalStateException(String.format("The url '%s' is malformed!", stringUrl), e);
        }
        return url;
    }

    /**
     * Returns the string property for {@link #SELENIUM_REMOTE_URL}
     * @return the url in string form, or null if it does not exist.
     */
    protected String getRemoteSeleniumUrlString() {
        return StringUtils.trimToNull(System.getProperty(SELENIUM_REMOTE_URL));
    }

    /**
     * Helper method to launch remote web driver.
     *
     * At times there are issues with starting the remote web driver. For firefox,
     * problems with locking port 7054 can arise.
     *
     * See the Selenium <a
     * href="https://code.google.com/p/selenium/issues/detail?id=4790">bug</a> for
     * more info
     *
     * A special case needs to be performed for the Android browser, since it can not be cast to {@link RemoteWebDriver}
     *
     * @param capabilities web driver capabilities.
     * @return {@link WebDriver} instance of the remote web driver
     *
     */
    protected WebDriver initRemoteWebDriver(DesiredCapabilities capabilities) {
        URL remoteUrl = getRemoteSeleniumUrl();
        LOG.debug("Remote Selenium URL: {}", remoteUrl.toString());
        WebDriver driver = null;
        boolean isAndroid = false;
        int tries = 1;

        if (capabilities.getCapability(CapabilityType.BROWSER_NAME).equals(ANDROID_BROWSER_NAME)) {
            isAndroid = true;
        }

        while (driver == null) {
            LOG.debug("Try {} {}", tries, capabilities.toString());

            try {

                if (isAndroid) {
                    driver = new AndroidDriver(remoteUrl, capabilities);
                } else {
                    driver = new RemoteWebDriver(remoteUrl, capabilities);
                }

            } catch (WebDriverException e) {
                LOG.error("Remote WebDriver was unable to start! " + e.getMessage(), e);

                if (tries >= Integer.getInteger(REMOTE_WEBDRIVER_RETRY_ATTEMPTS, 10)) {
                    throw e;
                }

                sleep(Integer.getInteger(REMOTE_WEBDRIVER_RETRY_PAUSE_MILLIS, 5000) * tries);
                tries++;
                driver = null;
            }
        }

        if (!isAndroid) {
            // allow screenshots to be taken
            driver = new Augmenter().augment(driver);
        }

        // Allow files from the host to be uploaded to a remote browser
        ((RemoteWebDriver) driver).setFileDetector(new LocalFileDetector());

        return driver;
    }

    /**
     * Simple Thread sleep method that catches InterruptException.
     * @param millis time (in milliseconds) to sleep
     */
    private void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            LOG.error("Exception occurred sleeping", e);
        }
    }

    private boolean doesFileExist(final String path) {
        final File file = new File(path);
        return file.exists();
    }

    /**
     * Basic method to check if a system property exists
     * @param systemProperty the system property to check
     * @return boolean if system property exists - true if it does, false otherwise.
     */
    private boolean isSysPropAvailable(final String systemProperty) {
        return System.getProperty(systemProperty) != null;
    }

    /*
     * Default WebDriver implementations. These can be overridden by the base
     * class if needed
     */

    /**
     * Default implementation of getting a local Internet Explorer web driver.
     * 
     * @return the WebDriver
     */
    protected WebDriver getInternetExplorerWebDriver() {
        // Note: running ie9 32 bit driver is faster then 64 bit driver. Download
        // the preferred IEDriverServer.exe from selenium's website
        LOG.debug("Start IE");

        // Note: this driver is only used when running the tests locally in IE
        String ieDriverPath = getOsSpecificBinaryPathFromProp(IE_DRIVER_BIN_PROP, "IEDriverServer");

        if (StringUtils.isNotBlank(ieDriverPath) && doesFileExist(ieDriverPath)) {
            LOG.info("Use specified IE Driver path: {}", ieDriverPath);
            System.setProperty("webdriver.ie.driver", ieDriverPath);
        } else {
            LOG.debug("Use default IEDriverServer.exe location");
        }

        return new InternetExplorerDriver(getInternetExplorerCapabilities());
    }

    /**
     * Default implementation of getting a remote WebDriver instance
     * 
     * @return - the remote IE WebDriver
     */
    protected WebDriver getRemoteInternetExplorerWebDriver() {
        return initRemoteWebDriver(getInternetExplorerCapabilities());
    }

    /**
     * Best found default capabilities for Internet Explorer
     * 
     * @return {@link DesiredCapabilities}
     */
    protected DesiredCapabilities getInternetExplorerCapabilities() {
        final DesiredCapabilities capabilities = DesiredCapabilities.internetExplorer();

        // get past certificate security warning pages
        capabilities.setCapability(CapabilityType.ACCEPT_SSL_CERTS, true);

        // setup native events and window focus
        // http://jimevansmusic.blogspot.com/2012/06/whats-wrong-with-internet-explorer.html
        capabilities.setCapability(InternetExplorerDriver.NATIVE_EVENTS, true);
        capabilities.setCapability(InternetExplorerDriver.REQUIRE_WINDOW_FOCUS, true);

        // don't accept alerts automatically
        capabilities.setCapability(CapabilityType.UNEXPECTED_ALERT_BEHAVIOUR, false);

        return capabilities;
    }

    /**
     * Default implementation of getting a local HTMLUnit Driver
     * 
     * @return {@link WebDriver} for HTMLUnit
     */
    protected WebDriver getHtmlUnitWebDriver() {
        // Set to firefox 24 to emulate a friendly javascript engine
        HtmlUnitDriver driver = new HtmlUnitDriver(BrowserVersion.FIREFOX_38);
        driver.setJavascriptEnabled(true);
        return driver;
    }

    /**
     * Default implementation throws UnsupportedOperationException
     * @return WebDriver instance
     */
    protected WebDriver getPhantomJsWebDriver() {
        String pathToBin = getOsSpecificBinaryPathFromProp(PHANTOM_JS_BIN_PROP, "phantomjs");

        DesiredCapabilities capabilities = getPhantomJsCapabilities();
        capabilities.setCapability(PhantomJSDriverService.PHANTOMJS_EXECUTABLE_PATH_PROPERTY, pathToBin);

        return new PhantomJSDriver(capabilities);

    }

    /**
     * Helper method to get the correct driver binary for the given operating
     * system.
     * 
     * @param binProp system property that needs to be OS specific
     * @param def default property
     * @return binary property for the current OS
     */
    protected String getOsSpecificBinaryProperty(String binProp, String def) {
        OperatingSystem os = OperatingSystem.getCurrentOs();

        switch (os) {
        case MAC:
            binProp += MAC_APPEND;
            break;
        case WINDOWS:
            binProp += WINDOWS_APPEND;
            def += StandaloneDriverDownloadAssistant.EXE_EXTENSION;
            break;

        // default case
        case LINUX_32: // fall though
        case LINUX_64: // fall though
        default:
            break;
        }

        return System.getProperty(binProp, def);
    }

    /**
     * Helper method to locate the correct binary for the given driver.
     * 
     * 
     * @param binProp property to pull the binary name from
     * @param def default property
     * @return string containing the entire path and binary name
     */
    protected String getOsSpecificBinaryPathFromProp(String binProp, String def) {
        // append .path to the end of the prop, to see if a path is defined
        String path = System.getProperty(binProp + DRIVER_BIN_PATH_APPEND,
                StandaloneDriverDownloadAssistant.SEAUTO_BIN_DIR);

        String fileName = getOsSpecificBinaryProperty(binProp, def);
        File driverBin = new File(path + fileName);

        if (!driverBin.exists())
            throw new IllegalStateException(String
                    .format("The driver binary does not exist! Expected path to bin: %s", driverBin.getPath()));

        return path + fileName;
    }

    protected WebDriver getRemotePhantomJsWebDriver() {
        return initRemoteWebDriver(getPhantomJsCapabilities());
    }

    protected DesiredCapabilities getPhantomJsCapabilities() {
        DesiredCapabilities capabilities = DesiredCapabilities.phantomjs();
        return capabilities;
    }

    /**
     * Obtains the default firefox web driver.
     * 
     * @return {@link WebDriver} for the Firefox instance
     */
    protected WebDriver getFirefoxWebDriver() {

        final FirefoxBinary fb;
        String fireboxBinPath = runConfig.getFirefoxBinaryPath();

        if (fireboxBinPath != null) {
            LOG.info("Using Firefox binary: {}", fireboxBinPath);
            fb = new FirefoxBinary(new File(fireboxBinPath));
        } else {
            LOG.info("Use system default for the Firefox binary");
            fb = new FirefoxBinary();
        }

        final DesiredCapabilities capabilities = getFirefoxCapabilities();
        capabilities.setCapability(FirefoxDriver.BINARY, fb);

        return new FirefoxDriver(capabilities);
    }

    /**
     * Default remote firefox instance
     * @return {@link WebDriver} for the remote Firefox instance
     */
    protected WebDriver getRemoteFirefoxWebDriver() {
        final DesiredCapabilities capabilities = getFirefoxCapabilities();
        return initRemoteWebDriver(capabilities);
    }

    /**
     * Default firefox capabilities
     * @return capabilities for the Firefox driver.
     */
    protected DesiredCapabilities getFirefoxCapabilities() {
        return DesiredCapabilities.firefox();
    }

    protected WebDriver getChromeWebDriver() {
        String pathToDriverBin = getOsSpecificBinaryPathFromProp(CHROME_DRIVER_BIN_PROP, "chromedriver");

        System.setProperty(ChromeDriverService.CHROME_DRIVER_EXE_PROPERTY, pathToDriverBin);
        DesiredCapabilities capabilities = DesiredCapabilities.chrome();

        return new ChromeDriver(capabilities);
    }

    protected WebDriver getRemoteChromeWebDriver() {
        // TODO Auto-generated method stub
        throw new IllegalStateException("Getting remote chrome driver is not implemented yet!");
    }

    /**
     * Configuration to get a handle to the Android driver
     * @return initialized instance of the {@link AndroidDriver}
     */
    private WebDriver getRemoteAndroidWebDriver() {

        DesiredCapabilities capabilities = new DesiredCapabilities();
        capabilities.setCapability(CapabilityType.BROWSER_NAME, ANDROID_BROWSER_NAME);
        capabilities.setCapability("deviceName", System.getProperty(ANDROID_DEVICE_NAME, "Android"));
        capabilities.setCapability("platformName", System.getProperty(ANDROID_PLATFORM_NAME, "Android"));
        capabilities.setCapability("app", System.getProperty(ANDROID_APK_PATH));

        if (isSysPropAvailable(ANDROID_PLATFORM_VERSION)) {
            capabilities.setCapability("platformVersion", System.getProperty(ANDROID_PLATFORM_VERSION, "5.0"));
        }

        if (isSysPropAvailable(ANDROID_APP_PKG)) {
            capabilities.setCapability("appPackage", System.getProperty(ANDROID_APP_PKG));
        }

        if (isSysPropAvailable(ANDROID_APP_WAIT_PKG)) {
            capabilities.setCapability("appWaitPackage", System.getProperty(ANDROID_APP_WAIT_PKG));
        }

        RemoteWebDriver remoteDriver = (RemoteWebDriver) initRemoteWebDriver(capabilities);
        AndroidDriver driver = (AndroidDriver) remoteDriver;

        return driver;
    }

}