导语

最近很多团队都在接入移动端的 UI 自动化,相信大家在使用过程中一个很大的困惑就是如何定位页面上的一个控件,从最大化降低自动化的维护成本上考虑,我们希望这个控件 id 是唯一的,相对稳定的,对于安卓 app 来讲,很多控件是带有 resource-id 的,这个是因为开发同学在使用过程中同样需要这个 id 去定位元素,但是对于 iOS 应用,这个问题是比较头疼的,因为不管是 Macaca 底层所依赖的 XCUITest,还是 Appium 所依赖的 UIAutomation,都是通过 accessibilityIdentifier 来定位元素,但是在 iOS 开发中,基本没有开发同学会去设置这个 id,因此造成一个现状是对于 iOS 应用的元素定位,大家需要变换多种策略,比如通过 class,name,xpath 等等其他属性来获取,如果能给 iOS 上的控件设置 id,将能大大的方便 UI 自动化的工作,更进一步,如果这个添加的过程能自动化实现,而不需要开发同学手工添加,就更加事半功倍了。这篇文章便是对这一策略的实践。

技术可行性分析

了解 iOS 开发的同学都知道,所有视图都是 UIView 或者其子类,如果我们能拦截到所有 UIView 初始化的入口,添加生成 id 的处理,就能为所有的 UIView 添加 id 了,这点是可行的,通过 hook UIView 的 load 方法就可以实现。

怎么生成唯一的 id?

现在我们已经确定是可以通过 Hook 的方式添加 id 的,但是这个 id 要如何取值,这又是一个问题,上面导语部分已经讲到,要最大化的降低自动化脚本的维护成本,我们希望这个 id 在单页面内是唯一的,同时又是相对稳定的,不会因为一次版本的升级就变化,这是在思考中最纠结的一个点,因为要找到一种策略满足这个要求真的不是一件容易的事情,后来在网上看到一篇文章,深受启发,详情参考:http://yulingtianxia.com/blog/2016/03/28/Add-UITest-Label-for-UIAutomation/

概括一下其主要思路如下:

  1. 如果控件对应变量是某个类的属性,则取其属性名称作为标签 id。因为代码没有改动,则标签也不会变化。即使代码有变动,也肯定是因为业务逻辑变更导致了界面上的变化,那么测试脚本肯定也是要改的,所以无需多虑此种情况。
  2. 如果控件对象是临时创建的局部变量,同一页面中很有可能有相同名字的局部变量。而且 Objective-C Runtime 无法获取局部变量名称,所以针对此种情况尽量采用其他来源的内容作为标签。比如对于 UILabel(文本控件),可以取其 text 作为 id,对于 UIButton(按钮) 对象,可以取其 title 标题作为 id,对于 UIImage 对象,可以取其资源名也就是 image 作为 id.

实践

实践是检验真理的唯一标准。我们按照对应的思路在我们的应用工程中添加了对应的处理,主要体现为新增两个扩展类,如图所示:

hookId.png

下面我们对比一下在进行 Hook 操作前后对同一个视图的 inspector 的结果:

在处理之前,我们对某 app 进行 inspect,得到视图树如下:

inspector-1.png

在我们添加了对应的几个文件之后,重新编译,然后对 app 进行 Inspect,得到视图树如下:

inspector-2.png

可以看到视图中的控件都已经添加了对应的 id,对应 dom 树中的 rawIdentifier 控件,就是我们需要的标签了,这样我们就可以通过这个 id 来操作这个金额控件。在此之前,我们想点击这个金额控件是不太好办的,因为这个控件没有 id,他的 class 是 StaticText,而这个类在整个视图中有若干个,通过 class 去取很难,xpath 一是太长,二是一旦视图层级变化,xpath 就会变化,维护成本会很高,也不可取,现在我们就可以通过操作这个 id 直接拿到这个控件了,而且这个控件取的是代码里对应这个控件的属性值,除非逻辑发生变化,一般没有人去修改一个变量的名字,维护成本也相对降低了,可见这个方案是可取的。

完美了吗?

通过上面的方案,我们实现了给控件自动生成 id,但是这个方案能完美解决我们的需要吗?

答案是否定的,因为你会发现,视图中有可能存在两个控件,他们的 Id 是一样的!就像下面这个:

登录页获取 “手机号” 控件 id:

inspect-1.png

登录页获取"密码"控件 id:

inspect-2.png

我们会发现对于 “手机号” 和"密码"两个控件,他们的 id 是一样的,这是为什么呢?

这就要回到我们上面讲到的生成 id 的方案,我们讲到,对于一个控件,如果他是对应父类的一个属性,那我们会取他的属性名字作为控件的 id,到这里相信大部分同学已经找到了上面问题的答案:因为手机号这一行和密码这一行,他们是同一个 UIView 的子类,“手机号” 和 “密码” 这两个控件,他们都是同一个属性!

问题已经定位,接下来要怎么做呢?要保证控件的唯一性吗?

如果我们一定要保证每个控件的唯一性,也不是没有办法,最简单的就是我们给同一个属性的控件添加序号标记,根据出现的先后,依次为控件添加编号,就能实现控件唯一的效果。

但是,一定要这么做吗?

这时候,我们要回归到我们的应用场景上来,什么时候会出现这种 id 重复的情况呢,在一个视图中存在同一个 UIView 的多个对象,这种场景,更多的是出现在我们的列表视图中,也就是 iOS 的 tableView 中,对于这样的视图,我们自动化的策略是什么呢?一般情况下,这种视图的数据都是服务端返回的,而我们在对这样的视图进行自动化的时候,无非是下面几种策略:

  1. 遍历点击所有的 cell
  2. 选择某一个特定的 cell 进行操作,比如第 3 行,也就是 Index = 2

在这种情况下,一个唯一的 id 对我们的作用有多大呢,事实上在这种场景下,我们需要的是一个数组,对于同类控件的一个数组,这种场景下同样的 id 对我们反而是比较好用的,因为我们可以拿到这个数组,然后决定做遍历操作还是对某一个索引的控件做指定的操作。

因此,我们的最终策略是保持这种方式,没有最完美的,只有最适合的,that's all!

源文件

https://github.com/Yinxl/iosHookViewId

使用方法:将 hook 目录下的几个文件拖进 XCode 工程下面编译即可

附录

“Macaca 开源社区” 群的钉钉群号: 11775486(欢迎入群讨论,钉钉顶部搜索框搜索群号加入即可)

更多相关文章

UI 自动化框架调研总结
从无到有搭建 Macaca 环境 (forMac)
Macaca-Java 版入门指南
UI 自动化 Macaca-Java 版实践心得
UI 自动化利器 - 为你的应用自动添加控件 ID 探索
Macaca 基础原理浅析


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