背景:对于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日志角度了解相关的操作逻辑
@classmethoddef 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)
建立连接是通过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的不同寻址方式操作元素。
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 element = contextId == null? findElement(by): findElement(by, elementsCache.get(contextId));if (element == null) {throw new ElementNotFoundException();}AndroidElement androidElement = elementsCache.add(element, true, by, contextId);return new AppiumResponse(getSessionId(request), androidElement.toModel());}
这段代码我们可以发现,其实是接受了一个IHttpRequest request然后进行解析,解析的内容有strategy,selector,context,这就解释了上面日志中为什么对find请求的内容做了一次转化的原因。如果元素没有找到,那就会抛出ElementNotFoundException异常。
同样的方式也适用于iOS,相关的内容可以追溯到一个名叫WebDriverAgent的仓库中,他是一个object-c编写的应用。在里面有XCUITest对于ios底层相关操作的实现逻辑。
知道这些后就可以尝试对appium进行二次封装然后在本地使用自己定制化的appium啦!下次有时间再写二次封装的相关内容。(下次一定🧐)最后希望大家看到这里能有所收获,对appium有新的了解,如果有更多想法也欢迎一起探讨!卷起来🤓
感谢每一个认真阅读我文章的人!!!
作为一位过来人也是希望大家少走一些弯路,如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些自动化测试的学习资源,希望能给你前进的路上带来帮助。
软件测试面试文档
我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
视频文档获取方式:
这份文档和视频资料,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!以上均可以分享,点下方小卡片即可自行领取