Edition 61

How to Accurately Select Webviews Using the `fullContextList` Capability

When working on native apps which contain a webview, one needs to move the entire test session into the webview context in order to automate the elements within (this process is explained in the Appium documentation). In the case where multiple webviews are present, how do we select the right one?

Getting the list of all webviews using the getContexts() command will return a list which looks something like:

["NATIVE_APP", "WEBVIEW_41490.2", "WEBVIEW_41490.3"]

Each of the webviews are named with a non-descriptive number, and the order is not guaranteed to be the same on every test run. If only there was a way to get more information about each webview when we call the getContexts() command.

This is where the fullContextList desired capability comes in. When set to true, the contexts returned by the getContexts() command are returned as a JSON string. Once parsed, each context is an object which includes not only the ID of the webview, but also the URL the webview is currently displaying and the Title of the page. Here's an example response when fullContextList is set to true:

[
  {
    "id":"NATIVE_APP"
  },
  {
    "id":"WEBVIEW_41490.2",
    "title":"Appium Pro: The Awesome Appium Tips Newsletter",
    "url":"https://appiumpro.com/"
  }
]

If multiple webviews are present in the current view, we can check the urls or titles of each so we can make sure to pick the right one.

Unfortunately, this capability is only supported when testing on iOS devices. There is a way Appium could implement this functionality on Android, but the work has not been done yet. Volunteers are welcome!

Selecting the right webview is a bit tricky in Java: you have to know that the contexts can be cast to Map<String,Object>, which is not obvious.

Here's a sample test written in Java, and as usual the full file can be found in our example code repository.

@Test
public void useFullContextList() throws MalformedURLException, InterruptedException {
    DesiredCapabilities caps = new DesiredCapabilities();
    caps.setCapability("platformName", "iOS");
    caps.setCapability("platformVersion", "12.1");
    caps.setCapability("deviceName", "iPhone XS");
    caps.setCapability("automationName", "XCUITest");
    caps.setCapability("app", IOS_APP);
    caps.setCapability("fullContextList", true);


    driver = new AppiumDriver(new URL("http://localhost:4723/wd/hub"), caps);
    WebDriverWait wait = new WebDriverWait(driver, 10);

    // get to webview screen
    wait.until(ExpectedConditions.presenceOfElementLocated(hybridScreen)).click();
    wait.until(ExpectedConditions.presenceOfElementLocated(urlInput)).sendKeys("https://appiumpro.com");
    driver.findElement(goButton).click();

    Optional<Map<String, Object>> appiumProWebview;
    do {
        Thread.sleep(100);
        // get full list of contexts
        Set<Map<String, Object>> contexts = driver.getContextHandles();
        appiumProWebview = contexts.stream()
                .filter(c -> !c.get("id").equals("NATIVE_APP"))
                .filter(c -> c.get("url").equals("https://appiumpro.com/"))
                .findFirst();
    } while (!appiumProWebview.isPresent());


    driver.context(appiumProWebview.get().get("id").toString());

    // now we know we're in the webview context which was pointing to "appiumpro.com"
    assertTrue(driver.getTitle().equals("Appium Pro: The Awesome Appium Tips Newsletter"));
}