Appium 你真的了解 appium 吗?

QE LAB for QE LAB · 2023年08月04日 · 4446 次阅读

作者:赵泽鑫 | QE_LAB

对于 QA 同学来说,appium 应该都不陌生,作为市面上最流行的 app 自动化测试框架之一,凭借强大的扩展性、跨平台能力和活跃的社区,使得它成为了移动端自动化测试的首选。今天让我们一起重新了解下这个工具!

appium 运行原理

appium 有几个重要的部分组成,分别是 appium client、web driver 以及 appium server。Appium server,负责接受客户端请求并与移动设备进行通信。它使用 WebDriver 协议来与客户端进行通信,并使用移动设备的原生测试框架 Ui automation2 或者 XCUITest 来执行自动化测试。appium 自动化 app 的所有指令都是基于 W3C 的 web driver 协议的。所以如果你认真看过 appium 的 log 的话,会发现每一个动作查找元素或者点击元素都是一次 http 请求。

官方给我们提供的 driver 有 UIautomator 和 XCUITest 等,所以我们可以直接下载对应的 driver 同 Android 以及 iOS 平台进行通讯,如果是其他平台的话,比如 webOS TV,官方没有提供相应的 driver,那我们就要根据 web driver 协议自定义一份适合 webOS 的 driver 来完成跟 webOS 应用通讯的目的。对于自定义 driver 有兴趣的可以了解下web driver 协议以及base driver

从 appium 日志角度了解相关的操作逻辑

@classmethod
def start(cls):
caps = {
"platformName": "Android",
"appium:deviceName": "liangzai_test_simulator",
"appium:appPackage": "tv.danmaku.bili",
"appium:appActivity": ".MainActivityV2",
"appium:newCommandTimeout": 6000,
"appium:automationName": "UiAutomator2",
"appium:ensureWebviewsHavePages": True,
"appium:nativeWebScreenshot": True,
"appium:connectHardwareKeyboard": True
}
cls.driver = webdriver.Remote("http://127.0.0.1:4723", caps)

从 log 截图中可以看到建立连接是通过 post 请求,产生一个 session,内容是 capability 中的相关信息。

连接建立成功后系统会寻找 ADB 工具,理论上每一个 Android 的 SDK 都会带有一个 adb 工具,这里会全部 list 出来然后选择一个进行使用。首先会去判断 simulator 上是否已经存在 appium.settings,没有则安装。然后检查 io.appium.uiautomator2.server,没有则安装。

ADB 工具负责连接 simulator 查看其中是否存在目标 app,没有找到则尝试使用 adb install app 路径 命令安装。如果在连接的 capability 中没有设置安装 app 的选项,appium 会认为该应用已经被安装在模拟器上并寻找,找到后如果没有设置 NoReset 为 True 的话,adb 会使用 am 和 pm 命令停止正在运行的 app 并且清除已有数据。

打开 app 会使用 ADB 的 shell am start 命令,打开会进入设置好的 main activity 页面。接下来进行的操作就会转移到 Uiautomator2 进行。

driver.implicitly_wait(10)
el1 = driver.find_element(by=AppiumBy.ID, value="tv.danmaku.bili:id/agree").click()
el2 = driver.find_element(by=AppiumBy.ID, value="tv.danmaku.bili:id/tv_skip").click()
el4 = driver.find_element(by=AppiumBy.ID, value="tv.danmaku.bili:id/search_text").click()
el5 = driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value="Search query")
el5.send_keys("Demon Slayer: Kimetsu No Yaiba")
el6 = driver.find_element(by=AppiumBy.ID, value="tv.danmaku.bili:id/action_search")
el6.click()

隐式等待实际上是一个 timeout 的请求,每个请求都会带有一个 session id,-->代表 client 发出的请求,<--代表 server 返回的结果。那么 find_element 和 click 操作的时候是怎么执行的呢?

这里使用的是 by ID 的操作,我们可以通过日志发现其实是有个转化的过程的,UIautomator 并不是直接使用 ID = XX 进行查找的,而是由 ["id","tv.danmaku.bili:id/agree","380fc8dc-7d2e-4426-b326-0a4b97c37cf8"] 的形式转成了{"strategy":"id","selector":"tv.danmaku.bili:id/agree","context":"","multiple":false}这样的形式。

  • strategy: 定位策略,这里是 id,表示使用元素的 id 属性来定位元素。
  • selector: 元素定位器,这里是 "tv.danmaku.bili:id/agree",表示要定位的元素的 id 属性值为"tv.danmaku.bili:id/agree"。
  • context: 上下文环境,这里为空,表示在当前页面中查找元素。
  • multiple: 是否允许定位多个元素,这里为 false,表示只查找一个符合条件的元素。如果使用 find_elements,这里就是 True。

按照这样的策略在当前页面寻找元素,如果找不到但是又因为设置了隐式等待没有超时的情况下,appium 会重试再次寻找该元素直到超时。找到元素后会返回对应的 ID 值,也就是这里的 element/00000000-0000-005e-ffff-ffff00000011。接下来对这个元素进行点击操作的时候也是继续使用这个 id:POST /element/00000000-0000-005e-ffff-ffff00000011/click] 执行 click 操作。

通过上面的分析我们可以直观的了解 appium client 的各种操作其实都是一次次的 HTTP 请求,每次操作都映射一个对应的请求,这也是 appium 支持各种不同类型编程语言的重要原因。

JSONWP 协议

JSONWP 的全称是 Mobile JSON Wire Protocol,appium client 所有的库都是基于此建立的。所以我们直接使用协议,按照协议的请求方式发送 curl 命令,一样可以完成自动化的操作。它本质是 web driver 协议的扩展协议,所以有些说法是 appium 基于 web driver 协议的也没问题。由于移动端的自动化测试不完全和 web 测试一样,移动端不仅有 native 应用,还有 hybrid 以及纯 H5 的应用,所以原有的 web driver 协议不能满足需求,于是便有了 JSONWP 协议。以下是一些比较常用的内容:

协议增加了 Capabilities,如:automationName、platformName、platformVersion、deviceName 等,增加了定位策略,如 accessibility id;

增加了 Page Source,所以我们可以使用 pagesource 方法获取当前页面所有的元素,这对于我们判断某元素是否存在很有帮助,同时我们也可以通过打印 page source 进行 debug。

为了同时支持 native 和 webview 两种不同格式的元素寻找,还增加了 context 内容,所以我们在进行 hybrid 测试的时候可以用过切换上下文的方式使用 appium 和 selenium 的不同寻址方式操作元素。

查看完整的协议内容请点击:https://github.com/SeleniumHQ/mobile-spec/blob/master/spec-draft.md

appium 源码分析

这一部分主要是给想要看源代码的人提供一点思路,在日志部分我们知道了 appium 使用了很多 adb 命令,如果你更有好奇心想要知道它是怎么操作这些 adb 命令的?或者好奇 find_element 是怎么实现元素查找的?那么你就需要通过查看源代码来解答你的疑问。appium 的源代码分成两个部分,appium 仓库内的代码是将底层内容整合在一起的一个体现,可以通过 appium -> lib - > main.js 开始一步一步的了解这个过程。如果想了解更加底层的东西,可以通过 package.json 中的 dependency 来看。

🌰:如果想看 Android 的底层实现,就在 appium 的 package.json 中寻找相应的 Android 依赖,(注意:在 appium 2.x 中已经将 driver 分离出去了,也就是需要单独 npm install driver,依赖的 driver 不会写在 package.json 中,使用这种方法请切换 1.x 版本分支)可以找到appium-uiautomator2-driver,再进到 appium-uiautomator2-driver 的 package.json 中我们可以找到appium-uiautomator2-server,它是一个 java 编写的应用并且没有依赖其他的库,所以这就是最下面的实现了。将它 clone 下来后我们就能在里面找到 uiautomator2 操作 app 的各种命令。比如 find_element 是这么实现的:

protected AppiumResponse safeHandle(IHttpRequest request) throws UiObjectNotFoundException {
FindElementModel model = toModel(request, FindElementModel.class);
final String method = model.strategy;
final String selector = model.selector;
final String contextId = isBlank(model.context) ? null : model.context;
if (contextId == null) {
Logger.info(String.format("method: '%s', selector: '%s'", method, selector));
} else {
Logger.info(String.format("method: '%s', selector: '%s', contextId: '%s'",
method, selector, contextId));
}
ElementsCache elementsCache = AppiumUIA2Driver.getInstance().getSessionOrThrow().getElementsCache();
final By by = ElementsLookupStrategy.ofName(method).toNativeSelector(selector);
final AccessibleUiObject

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册