Appium 一招让 IOS 自动化化快的飞起

hello · 2018年09月07日 · 最后由 hello 回复于 2019年12月10日 · 5765 次阅读
本帖已被设为精华帖!

前言

最近在做 IOS 自动化测试,IOS 的 Appium 环境都配置 OK,Demo 脚本运行没有问题,多开执行没有问题,IOS 安卓统一平台调度集成没有问题,可以进行自动化测试。
可是真正执行用例时发现个严重问题:执行速度过慢,慢的像中国男足,Appium 的执行 log 满屏刷,找元素像男国足球场一样骚,一个像无头苍蝇满场跑,一个有模有样的一轮又一轮的查找,就是命中不了。
很是蛋疼,一直疼了好几天,都有放弃的念头了。

调查

Xcode9 SDK 不再支持 snapshot 功能了,没有 snapshot 功能就无法获取 page_source。而从 Appium1.6.5+ 后,Facebook 用 WDA snapshot, 相比于 Xcode SDK 的 snapshot, WDA snapshot 在生成 page source 的时候包含了一个之前没有的属性, 也就是 visibility 属性。计算元素的 visibility 在 XCTest 中是非常痛苦和昂贵的操作。
xpath 好像每次查找时都是重新生成一棵树,WDA 需要额外的努力来实施 XPath 查询,这会严重影响查找时间遍历整个元素树,生成一个 xml 数据,然后再做 xpath 查找。遍历和在 xml 中进行 xpath 查找都相当耗时。论坛比较多的说法是查找元素策略的性能从高到低排列如下:Class Name>Accessibility Id>Link Text>Predicate>Class Chain>XPath
但是以上各种方式试过了,也没有让我的用例快起来。

大坑

搜索了很多帖子,很多人都是反映 IOS 自动化速度太慢。但是都没提怎么解决。由于不想改现在共通的滑动和安卓手机按键操作的代码,没有 java-client 升级,保留当前 5.0.4 的版本,没有想到掉这么个大坑里,搞了两三天百撕不得骑姐。
和 py 最新库执行相同用例后的速度对比后才发现,可能是 jar 包的原因。升级 java-client 版本 5.0.4-->6.1.0,升级前来执行 6 分钟左右的下单用例,升级后 2 分钟 (包括人为设置的等待时间) 不到就执行完成了。
更换版本后真的快的飞起啊,比安卓用例执行更快了。
这里说一下我的环境配置,方便速度慢的同学参考:

  • Xcode:9.4.1
  • Appium:1.8.1
  • 模拟器:11.4
  • java-client:6.1.0(非常重要,和 5 比速度差的不是一个级别的)

元素定位优化策略

  • 尽量不使用 xpath

长的 Xpath 定位可以使用谓词定位,Accessibility Id 定位等来逐步缩小搜索的范围,曲线救国。如下例:

IOSElement inputEL = driver.findElementByXpath("//XCUIElementTypeNavigationBar[@name='LVMMTabBar']/XCUIElementTypeStaticTex");

转换成

IOSElement  barEL = driver.findElementByIosNsPredicate("type == 'XCUIElementTypeNavigationBar' AND name == 'LVMMTabBar'");
IOSElement  inputEL =barEL.findElementByIosNsPredicate("type == 'XCUIElementTypeStaticText'");

但是由于有些复杂的 Xpath 很难完全将 Xpath 的定位方式转换成 Class Name,Accessibility Id,Predicate 或者 classChain 定位,所以 Xpath 还是不能抛弃的。
只能说尽量不使用 xpath 特别是页面元素比较多的时候。不到万不得已,尽量不用 (页面元素少,速度还可以接受时,可以不转换,灵活运用,转换前后性能自行比较一下)。

  • 尽量使用高性能查找

各种查找元素策略的性能从高到低排列如下(未一一按时间来具体验证):
Class Name
AccessibilityId
Link Text
Predicate
classChain
XPath

使用方法这里就不一一赘述了。很多同学估计对谓词不了解,也是 java-client 版本 5.0.4 以及以后版本才可以使用的,用下来感觉蛮好的的。
可以看一下全网最好的谓词扫盲贴---->谓词大法传送门

  • 尽量使用精确查找

例如通常我们知道"name='https://testerhome.com'"的元素只有一个时,尽量使用

IOSElement  niubiEl = driver.findElementByIosNsPredicate("type == '最牛逼的测试交流论坛' AND name == 'https://testerhome.com'");

不要使用

List<IOSElement> niubiEls  = driver.findElementsByIosNsPredicate("type == '最牛逼的测试交流论坛' AND name == 'https://testerhome.com'");
IOSElement  niubiEl = niubiEls.get(0);

同类的 findElementByAccessibilityId 和 findElementsByAccessibilityId 等

通常情况下 findElements 方法会遍历这个页面去找出所有匹配查询的页面元素,而花费更多的时间, 因为 findElement 不会,而是仅仅返回第一个匹配查询的元素(机器是不会喊累,但也请不要让人家干无用功)。

  • 尽量少通配符

很多人喜欢封装,但是过度封装,使用很多通配符,以达到万能找元素的效果,但是可能带来的后果就是执行效率低。例如:

List<IOSElement>  comnEls  = driver.findElement("//*[contains(@name,'" + targetParam+ "') or contains(@label,'" + targetParam+ "') or contains(@value,'" + targetParam+ "')]");

调用是简单了,只需传个参数 targetParam 就好了,但是这需要扫描每个 UI 元素的所有 name,lable,value 属性 (上面属性还可以继续加,达到万能匹配), 无疑这是极度低效的。
对于爬虫来说,可能需要爬取更多相关的东西,设置上面的封装查找是可以的,但是我们自动化来说,我们的目标很明确,而且对执行速度也是有要求的。老老实实的的使用

IOSElement  niubiEl = driver.findElementByIosNsPredicate("type == '最牛逼的测试交流论坛' AND name == 'https://testerhome.com'");

则更高效。

  • 尽量减少和服务的通信

    if (driver.isElementExist("//XCUIElementTypeButton[@name='未选择']"))
    {
    
    driver.findElement("//XCUIElementTypeButton[@name='未选择']").click();
    }
    

    上述如果存在时,则需要和 Appium 服务通信两次,查找元素两次(也许例子不恰当)。不如把元素存在与否判断的逻辑放在自己代码里如下:

    IOSElement  selEl = driver.findElement("//XCUIElementTypeButton[@name='未选择']");
    if(selEl != null)
    {
    selEl .click();
    }
    

升级后的小问题

升级后一些方法无法正常使用或者过时了。
点击升级前

new TouchAction(driver).tap(x , y).perform();

点击升级后

new AndroidTouchAction(driver).tap(PointOption.point(x , y )).perform();
安卓和IOS分开了
new IOSTouchAction(driver).tap(PointOption.point(x , y )).perform();

滑动升级前

new TouchAction(driver).press(beginX, beginY).waitAction().moveTo(endX, endY).release().perform();

滑动升级后

new AndroidTouchAction(driver).press(PointOption.point(beginX, beginY)).waitAction().moveTo(PointOption.point(endX, endY)).release().perform();
安卓和IOS分开了
new IOSTouchAction(driver).press(PointOption.point(beginX, beginY)).waitAction().moveTo(PointOption.point(endX, endY)).release().perform();

Android 按键操作
升级前

driver.pressKeyCode(AndroidKeyCode.HOME);
driver.pressKeyCode(AndroidKeyCode.BACK);
.
driver.pressKeyCode(AndroidKeyCode.KEYCODE_NUMPAD_9);

升级后

androidDriver.pressKey(new KeyEvent(AndroidKey.HOME));
androidDriver.pressKey(new KeyEvent(AndroidKey.BACK));
.
androidDriver.pressKey(new KeyEvent(AndroidKey.NUMPAD_9));

后记

升级版本后总于解决了执行龟速的问题,蛋也不疼了,如同梦中看到国足秒杀欧冠一样的快意,看着执行速度快的飞起,简直要 GC 了。
这些遇到的一些小坑,总结分享一下。大神轻喷,分享给那些和我一样 low 的人 (看到很多人 Q 群里问环境搭建,Demo 跑不了,求视频,还有很多付费培训广告),我感觉分享还是很有必要的,毕竟大神麼就你们几个。

关于 IOS/Android 自动化的执行速度问题,你有什么骚操作,欢迎一起交流探讨。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 56 条回复 时间 点赞
hello #61 · 2019年12月10日 Author
阿三 回复

我研究一下,多谢分享。

hello 回复

问题一:
Xcode:10.2.1
Appium:1.12.1
java-client:7.0.0
确实最后有点击操作效果(iOS 的),后来解决了改成
new IOSTouchAction(driver).longPress(PointOption.point(beginX, beginY)).waitAction().moveTo(PointOption.point(endX, endY)).release().perform();
问题二:
driver.pressKey(new KeyEvent(AndroidKey.ENTER));
这个回车没实际效果,代码运行成功(运用场景:手机谷歌浏览器地址栏输入地址回车访问)

hello #57 · 2019年04月25日 Author
阿三 回复

我这边没有点击的操作啊。你版本多少?

iOS 升级后滑动 ,用了这个在最后滑动点,实际有点击的操作,怎么办。
new IOSTouchAction(driver).press(PointOption.point(beginX, beginY)).waitAction().moveTo(PointOption.point(endX, endY)).release().perform();

hello 2018 年 度个人测开账单 中提及了此贴 01月25日 18:37
simple [精彩盘点] TesterHome 社区 2018 年 度精华帖 中提及了此贴 01月07日 12:08
simple 专栏文章:[精华帖] 社区历年精华帖分类归总 中提及了此贴 12月13日 14:44
hello #51 · 2018年11月13日 Author
FFFFF 回复

嗯,骚的无头苍蝇

无头苍蝇是最骚的

wtnhz 回复

感谢😄 我琢磨下它这个 api....能用了我来分享哈

chend 回复

遇到了同样的问题,直接通过 label 或者 name 可以拿到,但是这个元素是会变的,如果直接去拿到当前的,之后变了就拿不到了。。很尴尬~ 我昨天看了下那个 api 有个 find_elements_by_ios_uiautomation 的方法,Android 里面的 find_elements_by_android_uiautomation 方法是可以去通过父子元素 或者兄弟元素定位的,不知道 find_elements_by_ios_uiautomation 这个方法是不是可以做到相同的用法,但是我看了下 api 不会用,老哥可以研究下

def find_elements_by_ios_uiautomation(self, uia_string):
    """Finds elements by uiautomation in iOS.

    :Args:
     - uia_string - The element name in the iOS UIAutomation library

    :Usage: 这个用法有点看不懂
        driver.find_elements_by_ios_uiautomation('.elements()[1].cells()[2]')
    """
    return self.find_elements(by=By.IOS_UIAUTOMATION, value=uia_string)
hello 回复

图像识别么, 有些尴尬的元素,比如一些文本输入框,页面有很多...可能不太适合图像识别,全空白...
如果有 get_parent(element) 这样的实现思路最好了, 不熟悉 APP 这块, 在 Client 端的源码里翻了翻没找到这样的方法...

hello #49 · 2018年11月09日 Author
chend 回复

对于非常尴尬特别的元素,可以试试https://testerhome.com/topics/16569

请问个问题, 在不适用 xpath 轴的情况下, 有什么办法能够通过已知的元素得到它的父元素吗?
比如用谓词定位到了 element-A, 现在想通过 element-A 得到它的父元素, 或者兄弟元素,有思路吗?

hello #47 · 2018年11月07日 Author
wtnhz 回复

这个我还没研究过哎,你研究的结果 共享一下,我补充在帖子后面

请问,ios 定位用 find_elements_by_ios_uiautomation 的时候是不是和 android 是一样的用法

hello #43 · 2018年10月12日 Author
chend 回复

我也是瞎折腾,凑合着用的

hello 回复

谦虚谦虚了...
准备开展 APP 测试这块,但是调度平台如何整好一直没个太详细的思路...楼主发帖后喊下哈,我来取取经🙏

hello #43 · 2018年10月12日 Author
chend 回复

这个啊,改天发一帖吧,只是我搭的框架太挫,怕被大神们笑话啊

楼主,可否分享下 “IOS 安卓统一平台调度集成没有问题” 这个调度平台呢,或者实现思路.拜谢🙏 🙏

hello #41 · 2018年10月11日 Author

512433465

hello #40 · 2018年10月11日 Author
群主是狗 回复

通配符有利有弊,查找不准确经常有

仅楼主可见

通配符的很经典,我之前为了封装统一查找使用了 *
结果慢的要死,而且元素找不准

hello #36 · 2018年10月09日 Author
daisyzou 回复

应该有很大提升的,你可以试试更新一下 python-client

颜如玉 回复

你试用最新的 python-client,ios 自动化的 case 速度有提升么?

hello #35 · 2018年10月09日 Author
bi 回复

是的,感觉很流畅,模拟器其实也蛮流畅的。

真机做的 IOS 自动化测试,几乎没怎么用 xpath 找,运行速度并不慢

hello #31 · 2018年09月27日 Author
颜如玉 回复

客气了。

hello 回复

pip3 install --upgrade Appium-Python-Client
Requirement already up-to-date: Appium-Python-Client in /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages (0.28)

现在更新到 0.28 了,速度我再测试下,谢谢

hello #31 · 2018年09月26日 Author
颜如玉 回复

0.27 不是最新的啊

hello 回复

谢谢及时回复,我是直接用命令 pip install Appium-Python-Client,现在的版本是 0.27,不知道对不对?

hello #28 · 2018年09月26日 Author
颜如玉 回复

py 的试过了的。python-client 升级了么?

QJJauto 回复

你好你试了 python-client 了吗?有没有加快速度,我试了,没什么变化呀

hello #27 · 2018年09月26日 Author
deepwater 回复

模拟器,真机太贵了😂

楼主是真机测试吗

hello 回复

我没找到到关于 python 版本的最新 API 文档,,醉醉的

hello #22 · 2018年09月10日 Author
QJJauto 回复

java 的升级后,滑动是换方法了,py 的我没有具体调查。

hello 回复

之前没做过 ios 的 ,,我现在用的是 1.9 跟 python client 0.28 的版本 使用 swipe 或者是 Action 都不能进行滑动操作,提示 Message: An unknown server-side error occurred while processing the command. Original error: Unhandled endpoint 请问如何传入特定坐标来进行滑动操作 ,

hello #10 · 2018年09月10日 Author
QJJauto 回复

appium:1.8.1
python_client:0.28 好像,用的是最新的

请问一下你们等 appium 版本 跟 appium python client 版本都是多少

hello 回复

好的,有空试试

hello #18 · 2018年09月10日 Author
Flamingo 回复

是 python-client,你试试,速度很快的

请问楼主文中提到的 py 最新库是指 python-client 吗,已经做到和 java-client6.1.0 相同的效果了吗(ios 运行速度变快)

hello #17 · 2018年09月10日 Author
jeky2017 回复

是的。你升级一下 jar 试试。

以前做 ios 自动化测试,速度确实有点慢,主要是 wda 初始化的时间长

hello #15 · 2018年09月10日 Author
CC 回复

瞎水一贴,谢谢支持~

hello #13 · 2018年09月10日 Author
liuli 回复

😂 怒其不争,5 以下的 client 真的超级慢的,简直难以忍受

hello #12 · 2018年09月10日 Author
梦洁 回复

android 的 uiautomator 定位是比 xpath 快很多

hello #12 · 2018年09月10日 Author
shizhongping 回复

可以试试看~

不错的文章,多谢

啥都不服,就服你那几个男足的比喻

我主要测试 android,使用 xpath 较慢,明明看到元素在当前界面,需要等数 10s 左右,所以我建议能用其他的定位方式就用其他的,文本、查找兄弟元素等我都是使用 android 的 uiautomator 定位方式

hello 回复

那挺好的呀,我前天 5.0.4 升的 6.1.0 还没在 ios 上面试过,看了你的分享,回头试试去。

shizhongping 回复

是的。不过现在 java-client:6.1.0 的包升级后,执行速度已经和安卓的差不多了,应该是他们算法有所改进的。

记得 appium 的架构师说 ios 用 xpath 慢,是因为 ios 的源生框架 xcuitest 的 accessibility 其实并不支持 xpath。 之间的 gap 是 appium team 自己填上的。他也承认,就算填上了 性能也很差。所以个人觉得,能用原厂的还是用原厂的 locator 最高效吧。

是啊。确实是 appium 的 xpath 对于 iOS 的不太好用,jar 没有升级前,简直不能忍受。android 换成 uiautomator 定位确实好用。
期待社区开源项目解决这个问题。

我在 appcrawler 里也用了一招,把 xpath 定位自动换成 android 的 uiautomator 定位,ios 的还没来得及做,也快了不少。本质上是 xpath 定位 appium 实现的不太好,如果做的好其实也是挺快的。后面我想借助于社区的 TTF 基金会孵化一个开源项目解决这个痛点。

思寒_seveniruby 将本帖设为了精华贴 09月07日 15:37
思寒_seveniruby 将本帖设为了精华贴 09月07日 15:37
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册