译者:胡八
Appium 作者 Jonathan 独家授权

用图片查找元素 Part 1

Java 全平台 全设备

移动端自动化的一个不幸的现实是,并非每个 UI 元素在实践中都是可以自动化的!这可能是因为某些元素是由自定义类构建的,缺少可访问路径或自动化支持。这可能是因为没有唯一可应用于元素的标识定位器策略和选择器(想想一个动态列表视图,从自动化引擎的角度来看,虽然所有项看起来是相同的,但对真实用户来说却是不同的)。或者想象一个没有传统 UI 控件的 2D 或者 3D 游戏——它们只是渲染引擎绘制出来的像素!

icon

从历史上看,Appium 从没有试图去支持这些用例,一直是通过 XCUITests 和 UiAutomator2s 来提供支持。如果它们找不到一个元素,Appium 也就无法找到。

然而,有一把大锤子可以用来击碎这个问题。就像达摩克里斯之剑(或者叫达摩克里斯之锤?),它悬在 Appium 开发者头上已经有一段时间了。我们应该用它吗?还是我们不应该?这把大锤子就是视觉元素的检测。之所以称它为一把大锤子,是因为它用视觉特征来检测元素,来与它们互动。这与人类通常使用的策略完全相同,因此无论应用程序的类型如何,也不管开发者是否记得给特定元素赋予一个测试 ID,两种都适用。

最终 Appium 团队决定咬紧牙关,支持一小部分视觉检测特性,这些特性在 Appium1.9.0 中就能用到了。在本文中,我们关注这些特征最常见的用例,即图像元素查找。(在本系列的 Part 2 中,我们将介绍用于实现此特性的高级方法,但现在我们只看一些基本方法。)

什么是一个图像元素?从客户端代码来看,图像元素与任何其他元素完全一样,只是你已经通过新的-image定位器策略找到了它。和一个典型的选择器(如 “foo”)不同的是,这个新的定位器策略使用的字符串是 Base64 编码的图像文件。特定查找的图像文件代表一个模板图像,Appium 将用它来匹配屏幕的区域,来查找最可能出现的元素。

当然,这意味着你身边必须有一张图片,与你想在应用程序中寻找的图片相匹配。Appium 是如何使用这张图片找到你的元素的呢?在底层,我们依靠OpenCV库来找到与你的参考/模板图片最匹配的屏幕快照区域。OpenCV 是相当强大的,即使屏幕和参考图像之间存在各种差异(例如,旋转或尺寸的差异),也能匹配成功。

准备好看一个例子了吗?那我们开始吧。这是一个全新的特性,所以客户端和服务端的行为都还有些瑕疵。但是它非常神奇!在客户端,我创建了一个新的功能,一个照片列表,按随机顺序显示照片。当你点击一张照片时,一个包含照片描述的 alert 就会弹出来,如下图所示:

icon

如果没有图像匹配,这是一个不可能自动化起来的场景。这些图像在 UI 树中没有任何标识信息,并且每次加载视图时,它们的顺序都会发生变化,因此如果我们想要点击特定的图像,就不能硬编码元素索引。图像查找就是我们的救星!实际上,使用这种策略与使用任何其他策略找到元素是一样的:

WebElement el = driver.findElementByImage(base64EncodedImageFile);
el.click();

一旦我们有了一个图像元素,我们可以像任何其他元素一样点击它。(还有一些其他用于图像元素的命令,但显然大多数常见操作都不适用——例如,sendKeys。这是因为我们实际上并没有对实际 UI 元素的引用,我们只有元素所在的屏幕区域。)
当然,为了能让它跑起来,我们必须有一个 Base64 编码的图像文件。在 Java 8 中,这是非常简单明确的:

// 假设我们有一个叫refImgFile的文件
Base64.getEncoder().encodeToString(Files.readAllBytes(refImgFile.toPath()));

这就是关于图像查找的全部内容了!很赞的是,通过图像查找元素既支持隐式的,也支持显式的等待策略,因此你的测试可以稳健地等待,直到你的参考图像与屏幕上的图像匹配:

By image = MobileBy.image(base64EncodedImageFile);
new WebDriverWait(driver, 10)
  .until(ExpectedConditions.presenceOfElementLocated(image)).click();

综上所述,我们现在可以为上述场景写一个成功的测试:导航到 App 的 Photo 视图,点击我们想要的那张照片,然后通过比较随后显示的文本来验证我们是否点击了照片。为了找到照片,我们使用下面的参考模板:

icon

下面是相关的代码(暂时先忽略上面的模板图片):

private String getReferenceImageB64() throws URISyntaxException, IOException {
    URL refImgUrl = getClass().getClassLoader().getResource("Edition031_Reference_Image.png");
    File refImgFile = Paths.get(refImgUrl.toURI()).toFile();
    return Base64.getEncoder().encodeToString(Files.readAllBytes(refImgFile.toPath()));
}

public void actualTest(AppiumDriver driver) throws URISyntaxException, IOException {
    WebDriverWait wait = new WebDriverWait(driver, 10);

    try {
        // get to the photo view
        wait.until(ExpectedConditions.presenceOfElementLocated(photos)).click();

        // wait for and click the correct image using a reference image template
        By sunriseImage = MobileBy.image(getReferenceImageB64());
        wait.until(ExpectedConditions.presenceOfElementLocated(sunriseImage)).click();

        // verify that the resulting alert proves we clicked the right image
        wait.until(ExpectedConditions.alertIsPresent());
        String alertText = driver.switchTo().alert().getText();
        Assert.assertThat(alertText, Matchers.containsString("sunrise"));
    } finally {
        driver.quit();
    }
}

在上面的示例中,你可以看到我编写的一个帮助函数,用于获取参考图像的 Base64 编码版本,我已经把它作为资源文件存在项目中了。当然,把参考图像放到项目中的做法每个人可能会有所不同。

在本系列的Part 2中,我们将介绍一些可用于帮助查找图像元素的特殊参数。来看一下吧!另外,也看一下完整的代码示例。请注意,要成功运行,你要确保你从源代码中拉取的是最新的 Appium Java 客户端(参见项目中的build.gradle文件,并且至少是 Appium 1.9.0)。


↙↙↙阅读原文可查看相关链接,并与作者交流