Java 全平台 全设备
这个两部分的系列讲述的是在 Appium 中某一图像与屏幕区域是如何交互的,这是 Part 2。
如果你还没有读过 Part 1,那就先去读吧!
在本专栏中,我们会看一些 Appium 的 “按图像查找” 特性自带的高级技术用法,这些用法由-image
定位器策略来提供。为什么我们需要这些 “高级” 的技术用法呢?棘手的是图像识别有点复杂,很多事情会影响图像的成功匹配。
例如,你可能会指定一个比设备截图尺寸大的参考图像。这会导致图像匹配算法(在 OpenCV 库中实现)崩溃(因为如果参考图像比屏幕快照还大,怎么在屏幕快照中查找参考图像呢?)或者,如果你通过图像找到的元素,在找到它和对它发起tap
命令的这段时间间隔里位置发生了改变,该怎么办?你最后点击的不过是一个和你的参考图像不匹配的屏幕坐标!
对于上述的每种情况,Appium 都可以运行一些逻辑计算来帮助你解决问题(例如,为你缩放参考图像,或者当你点击图像元素时自动重新查找图像元素,必要时更新其位置)。Appium 可以独立完成这项工作,提供最健壮、最可靠的图像查找体验,无需你费心去了解这个功能的魔力所在。问题是,这些 “修复方案” 中的每一个都需要 Appium 花费大量的时间与自动化引擎工作或沟通。为了加快速度,默认情况下只打开基本的图像元素查找功能。因此,让我们来看看都有哪些可选性,无论如何,基本功能是不够的。
这些可选项中的每一个都可以在 Appiumd 的 Setting API 找到。这是一个只有在 Appium(不是 Selenium)才能找到的 API,它能够在测试过程中切换或重置 capabilities 的设置。它与 Desired Capabilities 有相同的参数类型,只是它允许 Appium 客户端在任何时候,任意次数地更新设置,只要你喜欢。在 Java 客户端中,Settings API 隐藏在 HasSettings 这个接口里。如果我们有一个 AndroidDriver 或 IOSDriver 对象,客户端会实现这个接口并对外暴露 setSetting 方法。(另一方面,如果我们有一个 AppiumDriver,首先我们需要对 HasSettings 进行分类)。
用法相当简单:
driver.setSetting(setting, value);
基本上,我们提供一个 setting 的名称(实际上是Setting
的枚举)和一个 value。我们来看看我们如何使用这个模块来寻找图像元素。
## 改变图像匹配阈值
在 OpenCV,图像匹配的结果不是二进制的 0 或者 1。相反,是在 0 到 1 之间的一定程度的匹配。匹配度本身是任意的,但是 1 代表像素和像素之间的完美匹配,而 0 代表没有可比性。通过一部分实验之后,Appium 的默认匹配阈值设置为 0.4。这意味着,默认情况下,相似性度量小于 0.4 的任何匹配都会认为是失败的。
无论是什么原因,你可能最终会处于 0.4,太严格(你找不到元素)或者不够严格(即使元素不存在,也会给你匹配)的情况。你可以使用IMAGE_MATCH_THRESHOLD
设置来调整:
driver.setSetting(Setting.IMAGE_MATCH_THRESHOLD, 0.2);
你怎么知道究竟要放什么值呢?你必须多试几次,因为度量本身是随机的(而且是非线性的)。幸运的是,你可以使用上述的 Settings API 来针对不同图像元素更改查找操作的阈值。
一旦你找到了一个图像元素,并且调用它的element.click()
方法,Appium 是怎么知道如何点击元素的呢?不幸的是,这里没有魔法可施。Appium 只是简单地匹配屏幕区域的边界,并在这些边界的中心坐标上点击而已。当然,Appium 有几种不同的方式可以用来点击一个点,例如使用 W3C Actions API 或旧的 Touch Actions API(参见 Appium Pro 版本的开头,来了解这两个 API 背后的历史)。
如果 Appium 默认的 tap 策略(使用的是 W3C Actions)不适合你(比如你使用的驱动程序尚未更新来支持 W3C Actions API),你总还是可以用回旧的 API,用 IMAGE_ELEMENT_TAP_STRATEGY 这个设置:
driver.setSetting(Setting.IMAGE_ELEMENT_TAP_STRATEGY, "touchActions");
(两个有效的选项是"w3cActions"
和"touchActions"
)
Appium 的图像匹配算法会对两个图像进行操作:一个基础图像(我们要定位的元素就在其中)和参考的图像或模板(和我们试图找到的元素相对应的图像)。你不用担心基本图像:Appium 只会用到你设备上的屏幕截图,因为它代表的是屏幕上的实时情况。但是如果基础图像(屏幕截图)和屏幕本身的尺寸不一样呢?匹配算法返回的匹配坐标是对应于屏幕截图,而不是设备。不幸的是,tap 操作最终是发生在设备上,这意味着 tap 的位置会和预想的不一致。
为什么屏幕截图会和屏幕本身的尺寸不匹配呢?原因有很多,尤其是每个平台处理像素缩放的方式不同。例如,iOS 先定屏幕宽度是 375 像素(“逻辑” 宽度),然后又高兴地生成宽 750 像素(“视网膜” 宽度)的屏幕截图!
正确地匹配这些维度是非常重要的,因此 Appium 在默认情况下会做掉。但是,如果你不想让 Appium 花费 CPU 时间去匹配,你可以选择放弃:
driver.setSetting(Setting.FIX_IMAGE_FIND_SCREENSHOT_DIMENSIONS, false);
如上所述,参考图像(模板)的尺寸必须小于屏幕截图,这样匹配算法才能正常运行起来。有时,当我们生成参考图像时(比如通过手动截取一个元素的屏幕截图),我们不能完全确定模板的尺寸。这可能是由于我们生成模板的方式导致它的尺寸大于 Appium 截取的屏幕快照的尺寸。
如果你希望可以使用模板图像,无论它的大小如何,请让 Appium 知道通过以下设置可以调整其大小:
driver.setSetting(Setting.FIX_IMAGE_TEMPLATE_SIZE, true);
默认情况下,Appium 不会去做调整,因为它可能会隐藏掉关于可能错误的模板的有价值的反馈(更不用说调整大小耗费一些计算时间和精力)。
## 过期图像元素的检查
你可能从 Selenium 中了解过 “过期元素” 的概念。过期元素指的是,在发现它和将要和它交互的时间间隔里,不知为何它消失了。在这种情况下,你期望的交互是不可能成功了的(tap、send keys 等),因此会抛出StaleElementException
的异常。
同样的情况也可能发生在图像元素上。如果元素(由图像匹配算法返回的一组坐标)在查找和点击之间消失了,该怎么办?因为这将导致 Appium 在元素的过期坐标上 tap,所以无论何时请求 tap 操作,Appium 都会再次尝试验证是否匹配。如果再次匹配成功了,并返回与以前相同的坐标,那么就进行 tap 操作。如果匹配根本不成功,或者坐标不同了,那么就返回StaleElementException
的异常。
如果你想优化性能,并取消这个安全检查,你可以随时通过下面的代码关掉它:
driver.setSetting(Setting.CHECK_IMAGE_ELEMENT_STALENESS, false);
## 自动刷新元素
默认情况下,如果图像元素的坐标已经过时,Appium 会抛出异常让你知道。但是如果元素没有消失,只是改变了位置呢?也许干脆在新的坐标点击它也行了。如果你希望 Appium 在请求点击时自动刷新图像元素的匹配位置时,可以使用UPDATE_IMAGE_ELEMENT_POSITION
来设置:
driver.setSetting(Setting.UPDATE_IMAGE_ELEMENT_POSITION, true);
通常情况下设置为 false,来保持正常的情况:当查找图像和 tap 图像的时间间隔里情况发生了变化,你有办法知道。