Framework Setup

Introduction

In this section, we will add driver classes for both the Android and iOS virtual devices we made previously.

We will also add Cucumber hooks for these new drivers, so that we can run tests on the devices when a feature and/or scenario uses an appropriate Cucumber tag (e.g. “@Nexus5xChrome”).

Starting Appium Server Programatically

Because we installed Appium Server globally to our system, it has been added to our PATH and thus, we are able to start and stop the Appium Server programatically in Java using ‘AppiumDriverLocalService’. Let’s set this up now 😀

  1. With the project open in IntelliJ, right-click on the ‘utils.drivers.appium’ package and select ‘New’ -> ‘Java Class’
  2. Name the class ‘AppiumServer’ and click OK
  3. Add the following ‘private static AppiumDriverLocalService’ variable in the class and also add two static methods to start and stop the Appium Server respectively, the whole thing should look like below…
    package utils.drivers.appium;
    
    import io.appium.java_client.service.local.AppiumDriverLocalService;
    
    public class AppiumServer {
    
        private static AppiumDriverLocalService appiumServer = AppiumDriverLocalService.buildDefaultService();
    
        public static void start() {
            appiumServer.start();
        }
    
        public static void stop() {
            appiumServer.stop();
        }
    }

Refactoring Existing Driver Classes

Appium Client contains an AndroidDriver class for Android and an IOSDriver class for iOS, which both inherit/extend from AppiumDriver.

AppiumDriver inherits from DefaultGenericMobileDriver, which in turn inherits from RemoteWebDriver.

RemoteWebDriver is the class that is shared between both Selenium and Appium. In Selenium, the RemoteWebDriver class implements various interfaces, including WebDriver.

Because we are testing a simple web application against mobile browsers, we can use the shared RemoteWebDriver class for both our Appium mobile drivers and our normal Selenium web drivers.

Refactoring ‘Driver’

Let’s change our browser() method to return RemoteWebDriver instead of WebDriver

  1. Open up the ‘Driver’ class in the ‘utils.selenium’ package and edit it so that the browser() method returns RemoteWebDriver…
    package utils.selenium;
    
    import org.openqa.selenium.remote.RemoteWebDriver;
    
    public class Driver {
    
        public static RemoteWebDriver browser() {
            return DriverController.instance.webDriver;
        }
    }

    You’ll notice that you get an error, due to the DriverController.instance.webDriver we are returning still being WebDriver instead of RemoteWebDriver. We will deal with that next.

Refactoring ‘DriverController’

  1. Open up the ‘DriverController’ class in the ‘utils.selenium’ package and change the ‘webDriver’ variable type from WebDriver to RemoteWebDriver…
    package utils.selenium;
    
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    import org.openqa.selenium.remote.RemoteWebDriver;
    import utils.drivers.ChromeWebDriver;
    import utils.drivers.FirefoxWebDriver;
    
    public class DriverController {
    
        public static DriverController instance = new DriverController();
    
        RemoteWebDriver webDriver;
    
        static Logger log = LogManager.getLogger();
    
        public void startChrome(String arg) {
            if(instance.webDriver != null) return;
            instance.webDriver = ChromeWebDriver.loadChromeDriver(arg);
        }
    
        public void startFirefox(String arg) {
            if(instance.webDriver != null) return;
            instance.webDriver = FirefoxWebDriver.loadFirefoxDriver(arg);
        }
    
        public void stopWebDriver() {
            if (instance.webDriver == null) return;
    
            try
            {
                instance.webDriver.quit();
            }
            catch (Exception e)
            {
                log.info(e + "::WebDriver stop error");
            }
    
            instance.webDriver = null;
            log.info(":: WebDriver stopped");
        }
    }

    You’ll notice that you get errors again.  This is because our respective loadDriver() methods in our ChromeWebDriver and FirefoxWebDriver classes are still trying to return WebDriver instead of RemoteWebDriver. Let’s also fix that now 🙂

Refactoring the actual Drivers

ChromeWebDriver

  1. Open up the ‘ChromeWebDriver’ class in the ‘utils.drivers’ package and change the variable type of ‘driver’ from WebDriver to RemoteWebDriver…
    private static RemoteWebDriver driver;
  2. Change the return type of the loadChromeDriver() method from WebDriver to RemoteWebDriver…
    public static RemoteWebDriver loadChromeDriver(String chromeArgument) {
        setupChromeDriver();
    
        ChromeDriverService driverService = ChromeDriverService.createDefaultService();
    
        ChromeOptions options = new ChromeOptions();
        options.addArguments(chromeArgument);
    
        driver = new ChromeDriver(driverService, options);
        return driver;
    }

The whole thing should look like below when complete…

package utils.drivers;

import io.github.bonigarcia.wdm.WebDriverManager;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeDriverService;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.RemoteWebDriver;

public class ChromeWebDriver {

    private static RemoteWebDriver driver;

    public static void setupChromeDriver() {
        WebDriverManager.chromedriver().setup();
    }

    public static RemoteWebDriver loadChromeDriver(String chromeArgument) {
        setupChromeDriver();

        ChromeDriverService driverService = ChromeDriverService.createDefaultService();

        ChromeOptions options = new ChromeOptions();
        options.addArguments(chromeArgument);

        driver = new ChromeDriver(driverService, options);
        return driver;
    }
}

FirefoxWebDriver

  1. Open up the ‘FirefoxeWebDriver’ class in the ‘utils.drivers’ package and change the variable type of ‘driver’ from WebDriver to RemoteWebDriver…
    private static RemoteWebDriver driver;
  2. Change the return type of the loadChromeDriver() method from WebDriver to RemoteWebDriver…
    public static RemoteWebDriver loadFirefoxDriver(String firefoxArgument) {
        setupFirefoxDriver();
    
        FirefoxOptions options = new FirefoxOptions();
        options.addArguments(firefoxArgument);
    
        driver = new FirefoxDriver(options);
        return driver;
    }

The whole thing should look like below when complete…

package utils.drivers;

import io.github.bonigarcia.wdm.WebDriverManager;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.remote.RemoteWebDriver;

public class FirefoxWebDriver {

    private static RemoteWebDriver driver;

    private static void setupFirefoxDriver() {
        WebDriverManager.firefoxdriver().setup();
    }

    public static RemoteWebDriver loadFirefoxDriver(String firefoxArgument) {
        setupFirefoxDriver();

        FirefoxOptions options = new FirefoxOptions();
        options.addArguments(firefoxArgument);

        driver = new FirefoxDriver(options);
        return driver;
    }
}

If you look at the ‘DriverController’ class again, all the errors should be gone.

Next up, let’s begin to add our Appium mobile drivers for our Android and iOS virtual devices 🙂

Adding the Appium Mobile Drivers

Refactoring the ‘drivers’ structure

Because our framework will be running tests against normal Selenium web drivers as well as Appium mobile browsers, we should restructure our ‘utils.drivers’ package.

  1. Right-click on the ‘utils.drivers’ package in the Project explorer and select ‘New’ –> ‘Package’
    • Name the package ‘selenium’ and click OK
  2. Right-click on the ‘utils.drivers’ package in the Project explorer and select ‘New’ –> ‘Package’
  3. Name the package ‘appium’ and click OK
  4. Highlight/select both the ChromeWebDriver and FirefoxWebDriver classes in the project explorer
    • Drag and drop the classes into the ‘selenium’ package
    • Click ‘Refactor’
  5. Right-click the ‘appium’ package and select ‘New’ — ‘Java Class’
    • Name the class ‘AppiumServer’ and click OK
  6. Right-click the ‘appium’ package again and select ‘New’ –> ‘Package’
    • Name the package ‘android’ and click OK
  7. Right-click the ‘appium’ package and select ‘New’ –> ‘Package’ again
    • Name the package ‘ios’ and click OK

Adding the Appium Driver Classes

Android Driver

Appium has come a long way over the years and made it a lot easier to connect to the devices we want to connect to.  Because we will be specifying that we are using AndroidDriver, which is of course specific to the Android platform, we do not need to declare the ‘platformName’ in our Desired Capabilities. If we instead used the more general parent class of AppiumDriver that AndroidDriver inherits from, then it would need the ‘platformName’ defined in the Desired Capabilities to distinguish between iOS and Android.

All we really need to set in our DesiredCapabilities is the ‘deviceName’ (of the Android Virtual Device), the mobile ‘browserName’ (e.g. ‘Chrome’), and also the ‘avd’ if we want to launch the Android Virtual Device programmatically (which we should do).

  1. Right-click the ‘android’ package and select ‘New’ –> ‘Java Class’
    • Give the class a suitable name (e.g. ‘Nexus5xOreoChrome‘) and click OK (this will be the class for your Android driver that connects to your Android Virtual Device and opens a browser in it)
  2. Edit the class file so it looks similar to below…
    package utils.drivers.appium.android;
    
    import io.appium.java_client.android.AndroidDriver;
    import org.openqa.selenium.chrome.ChromeOptions;
    import org.openqa.selenium.remote.DesiredCapabilities;
    import utils.drivers.appium.AppiumServer;
    import java.net.MalformedURLException;
    import java.net.URL;
    
    public class Nexus5xOreoChrome {
    
        private static AndroidDriver driver;
    
        public static AndroidDriver loadNexus5xOreoChrome() throws MalformedURLException {
            AppiumServer.start();
    
            DesiredCapabilities cap = new DesiredCapabilities();
            cap.setCapability("avd", "Nexus5xOreoChrome"); //set the AVD (Android Virtual Device) to be launched
            cap.setCapability("deviceName", "Nexus5xOreoChrome"); //set the name of the device to be launched (should be same as AVD)
            cap.setBrowserName("Chromium"); //set the mobile browser that should be launched on the device
    
            ChromeOptions options = new ChromeOptions();
            options.addArguments("--no-first-run"); //ensure Chrome skips any first run Welcome screens when launched
            cap.setCapability("chromeOptions", options);
    
            driver = new AndroidDriver(new URL("http://127.0.0.1:4723/wd/hub"), cap); //set the AndroidDriver to an Appium session with the above DesiredCapabilities
            return driver;
        }
    }

In the above, we create a loadAndroidDriver() method which returns AndroidDriver.

Then, we start up the Appium Server.

Next, we set the DesiredCapabilities that we want in our Appium session when we load this Android Driver…

  • avd – name of our Android Virtual Device
  • deviceName – name of our device (should match AVD name in this example)
  • browserName – name of mobile browser (e.g. Chrome or Chromium)

After, we add ChromeOptions and pass an argument of “–no-first-run” which ensures that Chrome skips any first time Welcome screens when the browser is launched.

We finally set our AndroidDriver to an Appium session with the specified DesiredCapabilities and then return the driver to be used.

iOS Driver

Because we will be specifying that we are using IOSDriver, which is of course specific to the iOS platform, we again do not need to declare the ‘platformName’ in our Desired Capabilities. If we instead used the more general parent class of AppiumDriver that IOSDriver inherits from, then it would need the ‘platformName’ defined in the Desired Capabilities to distinguish between Android and iOS.

All we really need to set in our DesiredCapabilities is the ‘deviceName’ (of the iOS simulator), the mobile ‘browserName’ (e.g. ‘Safari’), and also the ‘platformVersion’ (iOS version) of the iOS simulator we want to launch (one of the ones available when we checked previously in Xcode).

  1. Right-click the ‘ios’ package and select ‘New’ –> ‘Java Class’
    • Give the class a suitable name (e.g. ‘Iphone8Safari‘) and click OK (this will be the class for your iOS driver that connects to your iOS and opens a browser in it)
  2. Edit the class file so it looks similar to below…
    package utils.drivers.appium.ios;
    
    import io.appium.java_client.ios.IOSDriver;
    import org.openqa.selenium.remote.DesiredCapabilities;
    import utils.drivers.appium.AppiumServer;
    import java.net.MalformedURLException;
    import java.net.URL;
    
    public class Iphone8Safari {
    
        private static IOSDriver driver;
    
        public static IOSDriver loadIphone8Safari() throws MalformedURLException {
            AppiumServer.start();
    
            DesiredCapabilities cap = new DesiredCapabilities();
            cap.setCapability("platformVersion", "12.0"); //set the iOS simulator version to iOS 11.4
            cap.setCapability("deviceName", "iPhone 8"); //set the name of the device to be launched
            cap.setBrowserName("Safari"); //set the mobile browser that should be launched on the device
    
            driver = new IOSDriver(new URL("http://127.0.0.1:4723/wd/hub"), cap); //set the IOSDriver to an Appium session with the above DesiredCapabilities
            return driver;
        }
    }

In the above, we create a loadIOSDriver() method which returns IOSDriver.

Then, we start up the Appium Server.

Next, we set the DesiredCapabilities that we want in our Appium session when we load this IOSDriver…

  • platformVersion – the iOS version of the simulator we will be using
  • deviceName – the name of our device / simulator we will be using
  • browserName – the name of the mobile browser we want to run our tests against in the simulator (e.g. ‘Safari’)

We finally set our IOSDriver to an Appium session with the specified DesiredCapabilities and then return the driver to be used.

In the next section, we will hook in our drivers with Cucumber tags so we can run appropriate tests against our mobile devices and browsers 😀

Digiprove sealCopyright secured by Digiprove © 2018
Liked it? Take a second to support Thomas on Patreon!

Previous Article

Next Article

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.