Part 3. Hooking in the Mobile Drivers

Hooking in the Mobile Drivers

Introduction

In this section, we will hook in our mobile Appium drivers in to our DriverController class so that we can appropriately start and stop them.

We will then add Cucumber tags and hooks to appropriately start and stop them before/after all scenarios in a Feature file and/or an individual test scenario.

Let’s get started 😀

Updating DriverController with new Mobile Drivers

startNexus5xOreoChrome() Method

  1. Open up the ‘DriverController’ class in the ‘utils.selenium’ package
  2. Add the following method to start the Android driver when the method is called…
    public void startNexus5xOreoChrome() throws MalformedURLException {
        if (instance.webDriver != null) return;
        instance.webDriver = Nexus5xOreoChrome.loadNexus5xOreoChrome();
    }

startIphone8Safari() Method

  1. Open up the ‘DriverController’ class in the ‘utils.selenium’ package again
  2. Add the following method to start the iOS driver when the method is called…
    public void startIphone8Safari() throws MalformedURLException {
        if (instance.webDriver != null) return;
        instance.webDriver = Iphone8Safari.loadIphone8Safari();
    }

Because we are making both Selenium and Appium work against RemoteWebDriver, we can leave our stopWebDriver() method as is.

Or, if you’re pedantic about naming like me, right click the method, select Refactor –> Rename and rename it to something suitable for both Selenium and Appium, like simply ‘stopDriver()’ 🙂

Adding CucumberHooks Methods

beforeNexus5xOreoChrome() Method

We will now add a method that calls an instance of our startNexus5xOreoChrome() method when we run a test scenario with a Cucumber tag of “@Nexus5xChrome”.

  1. Open up the ‘CucumberHooks’ class in the ‘utils.hooks’ package and add the following method…
    @Before("@Nexus5xChrome")
    public void beforeNexus5xOreoChrome() throws MalformedURLException {
        DriverController.instance.startNexus5xOreoChrome();
    }

beforeIphone8Safari() Method

We will now add a method that calls an instance of our startIphone8Safari() Method when we run a test scenario with a Cucumber tag of “@Iphone8Safari”.

  1. Open up the ‘CucumberHooks’ class in the ‘utils.hooks’ package again and add the following method…
    @Before("@Iphone8Safari")
    public void beforeIphone8Safari() throws MalformedURLException {
        DriverController.instance.startIphone8Safari();
    }

    Also make sure the correct import has been added at the top…

    import utils.drivers.appium.AppiumServer;

afterAppiumRun() Method

We will now add a method that calls the AppiumServer.stop() method when we finish running tests with a Cucumber tag of either “@Nexus5XChrome” or “@Iphone8Safari”.

  1. Open up the ‘CucumberHooks’ class in the ‘utils.hooks’ package again and add the following method…
    @After("@Nexus5xChrome,@Iphone8Safari")
    public void afterAppiumRun() {
        AppiumServer.stop();
    }

Running the Tests against the Mobile Devices

Let’s now check that the ‘SearchScenarios’ test passes when run on both the Android and iOS emulators.

Running the test against Nexus5xChrome tag

  1. Open up the ‘SearchScenarios.feature’ file
  2. Put a “@Nexus5xChrome”(without quotes) tag either at the top of the feature file or above the individual test scenario
  3. Right-click on the test scenario and run the scenario

Running the test against Iphone8Safari tag

  1. Open up the ‘SearchScenarios.feature’ file again
  2. Change the Cucumber tag to “@Iphone8Safari” (without quotes)
  3. Right-click on the test scenario again and run the scenario

OH NO!! The test fails…

This is because it tries to assert the page title of Reddit before the page has finished loading.

There are various ways we can fix this, with the simplest being to add a ‘Thread.sleep(seconds)’ to our validatePageTitle() method.  However, this is very bad practice and we should always try to avoid using sleep methods.

Alternatively, we could grab an element off the Reddit page and wait for that to be displayed before continuing.

The approach I have decided to go with is a bit more advanced, and uses a Java8 feature of CompletableFutures added to our weClick() wrapper method, which only gets run if we are running the test against iOS.

  1. Open up the ‘WebElementExtensions’ class in the utils.extensions’ package
  2. Edit the ‘weClick()’ static method so it looks similar to below (I have added comments to help explain what is going on here)…
    public static void weClick(WebElement element) {
        weElementToBeClickable(element);
        weHighlightElement(element);
        element.click();
        
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            while (element.isDisplayed()) {
                //do nothing
            }
        });
        try {
            future.get(3, TimeUnit.SECONDS);
        } catch (InterruptedException | TimeoutException | ExecutionException e) {
            e.printStackTrace();
        }
    }

In the above method, we click on an element passed into the method. we then run a while loop that checks if the element we clicked on is still displayed.

Whenever we click on an element that takes us to a new page, the element we click on will generally no longer be displayed once we land on the new page.  We use the CompletableFuture to wait until the element is no longer displayed and then carry on with the rest of the test after we leave that while loop.

Sometimes though, we will click on an element that either does not take us to a new page (void click), or the element will also be displayed on the new page.  This is why we add a timeout of 3 seconds to our ‘future.get()’ call.

To learn more about CompletableFutures in Java8, you can check out a great explanation at https://www.callicoder.com/java-8-completablefuture-tutorial/

Congratulations, your test scenario should now pass on both Android and iOS 😀