Nowadays, when the industry is dominated by agile development, lean startup methodology, minimum viable product mindset, companies want to develop, test and deliver their projects to clients as fast as possible. This means that hybrid app development popularity has significantly increased. Even such tech industry giants as Uber, Skype and, in some parts, Instagram are using this kind of technology in order to create apps fast while, at the same time, reducing costs for development and maintenance.

For us, QA engineers, this means that we need to be prepared to work with these kind of applications, which in essence are not very different from native or web apps. As the term “hybrid app” in this sense is almost self explanatory, in order to create test frameworks for these kind of apps we need to know a little bit of both.

Appium test automation framework has support for testing hybrid apps via ChromeDriver (for Android starting from API 19) and Selendroid (for Android API 14 to API 18). In this post we will use Android API >= 19. Since hybrid app automation slightly differs from native app automation, in this article we will tackle specific issues to hybrid app automation testing like managing application contexts, finding element selectors, and using touch actions in a webview.

Requirements

For these examples I’m using:

  • MacOSX
  • Appium 1.6.5
  • Appium Java client 5.0.3
  • Selenium Java 3.5.3
  • Java™ SE Development Kit 8, Update 101 (JDK 8u101)
  • Android SDK platform tools
  • Eclipse Neon.2 Release (4.6.2)
  • Google Chrome
  • An app built using Hybrid app I framework
  • Developer must have set ‘setWebContentsDebuggingEnabled’ flag in the WebView class
  • Device with Android API >= 19

Issues

There are three main issues regarding hybrid app test automation that we will address in this post:

  • Contexts
  • Selectors
  • Touch actions

Contexts

Although depending on the app, mostly hybrid apps run completely in a webview and do not use elements in native UI, therefore, we need to switch between contexts when an app is opened in order for these elements to be located by the driver. There are at least two ways to handle this issue:

1. Use auto webview capability:

In the class where you set capabilities, add AUTO_WEBVIEW capability

capabilities.setCapability(MobileCapabilityType.AUTO_WEBVIEW, true);

This tells Appium to switch to a webview automatically before running the test scenarios.

2. Switch context manually:

Create a method for getting available contexts and setting driver context to webview and call it in test precondition or whenever necessary:

public static void setContextToWebview(){
     Set<String> availableContexts = driver.getContextHandles();
     availableContexts.stream()
             .filter(context -> context.toLowerCase().contains("webview"))
             .forEach(newcontext -> driver.context(newcontext));
}

This method is more recommended if the app contains native UI elements and it is needed to switch between contexts more frequently in order to access these native elements. To switch to native context use this method:

driver.context("NATIVE_APP");

Selectors

Another difference between native and hybrid app automation are selectors. Since elements are in webview context, the elements essentially are the same as in web browsers. Easy way to inspect them is by using Chrome Remote debugger. To start inspecting app elements you should follow these steps:

1. Enable developer mode on your device
2. Enable USB debugging in Developer options menu
3. Connect your device to your computer
4. Type ‘adb devices’ in terminal to be sure the device is connected. The output should contain device’s serial number and type
5. Open your app on the device
6. Open Chrome
7. Type ‘chrome://inspect’ in URL bar

chromeInspect

8. You should be on the page displayed in picture above
9. Click on Inspect link
10. Inspect view will open and you should see the screen of your device and app

inspectingApp

11. From here you can view and inspect desired elements, copy their id’s, CSS selectors or Xpaths basically the same way elements are selected in Web UI automation
12. Hitting ‘Cmd + F’ brings up search bar where you can test your selectors to see how many matching nodes are selected with your created Xpath or CSS selector

After an element’s selector is determined, next steps are the same as in Web UI automation using Selenium – finding the element:

WebElement usernameField = driver.findElement(By.xpath("//input[@placeholder='nom@email.com']"));
WebElement passwordField = driver.findElement(By.id("pwd"));
WebElement loginButton = driver.findElement(By.css("form button"));
usernameField.sendKeys("TestUser");
passwordField.sendKeys("password123");
loginButton.click();

Note that all actions need to be done in webview context or the driver won’t be able to find or perform the actions described in the code above.

Touch actions

Simulating touch actions in a hybrid app is tricky since touch actions in webview are not fully supported. This issue can be managed, but all the solutions look and feel more or less like workarounds not genuine solutions. The easiest way to tackle this issue is to create a function which finds elements center in webview context, converts it to a coordinate in native context, applies offset which is device specific and then create a method which taps on the found coordinates.

public static float[] getElementCenter(AndroidDriver<WebElement> driver, WebElement element){
  setContextToWebview();
  JavascriptExecutor js = (JavascriptExecutor)driver;

// get webview dimensions
  Long webviewWidth = (Long) js.executeScript("return screen.width");
  Long webviewHeight = (Long)  js.executeScript("return screen.height");

// get element location in webview
  int elementLocationX = element.getLocation().getX();
  int elementLocationY = element.getLocation().getY();

// get the center location of the element
  int elementWidthCenter = element.getSize().getWidth() / 2;
  int elementHeightCenter = element.getSize().getHeight() / 2;
  int elementWidthCenterLocation = elementWidthCenter + elementLocationX;
  int elementHeightCenterLocation = elementHeightCenter + elementLocationY;

// switch to native context
  driver.context("NATIVE_APP");
  float deviceScreenWidth, deviceScreenHeight;

// offset
  int offset = 115;

// get the actual screen dimensions
  deviceScreenWidth = driver.manage().window().getSize().getWidth();
  deviceScreenHeight = driver.manage().window().getSize().getHeight();

// calculate the ratio between actual screen dimensions and webview dimensions
  float ratioWidth = deviceScreenWidth / webviewWidth.intValue();
  float ratioHeight = deviceScreenHeight / webviewHeight.intValue();

// calculate the actual element location on the screen
  float elementCenterActualX = elementWidthCenterLocation * ratioWidth;
  float elementCenterActualY = (elementHeightCenterLocation * ratioHeight) + offset;
  float[] elementLocation = {elementCenterActualX, elementCenterActualY};

// switch back to webview context
  setContextToWebview();
  return elementLocation;
}
public static void tapOnElement(AndroidDriver<WebElement> driver, WebElement element){
  float[] elementLocation = getElementCenter(driver, element);
  int coordinateX, coordinateY; 
  elementCoordinateX = (int) Math.round(elementLocation[0]);
  elementCoordinateY = (int) Math.round(elementLocation[1]);
  driver.context("NATIVE_APP");
  TouchAction action = new TouchAction(driver);
  action.tap(elementCoordinateX, elementCoordinateX).perform();
  setContextToWebview();
}

Although this method requires some tweaking when running your scripts on different devices because of the required offset, we have found that this still is the most reliable method when performing touch actions in webview contexts.

Keep in mind that for this method to work, coordinates need to be valid within device’s viewport, so a function which determines if actual element coordinates are within device screen dimensions is recommended, especially when dealing with scrolling (swipe touch action).

Summary

  • To sum up – pay close attention to your current context, if an exception is thrown most of the time the problem is with contexts.
  • Same element selectors can be used as in web automation.
  • Keep in mind that webview context dimensions are not the same as native context dimensions.
  • When using Appium specific APIs like touch actions it is recommended to switch to native context although some APIs will still work in webview contexts.
  • Keep in mind that when element coordinates are not within device screen dimensions the touch action won’t work.