前言
iOS的元素查找跟安卓和web有很大的不一样,所以还是需要单独拎出来写一篇文章探讨一下。
一、原生页面的元素
当我第一次用appium-inspector查看原生页面的元素时,懵了,怎么层级那么多?而且元素的标志性的标识,几乎没有。为了更好地理解那种的复杂,我写了个页面,顶部是一个文本,底部是一个按钮,然后用inspector查看该页面,是这样子的:
iOS目前可以用Object-C使用布局框架布局,可以Swift使用布局框架布局,可以使用storyboard来布局(xip or xib?),可以使用Swift UI来布局,不过大差不差,对于我们测试来说,没差别。
可以看到红色的①是一个文本,蓝色①是一个按钮。这个页面明明很简单,就这俩,但是inspector出来的结果,却是一层一层又一层。而且元素似乎并没有什么标志性的id来区分,如果我八个button都叫button,我怎么找到唯一那个?如果八个button根据不同的天气,位置会变化,我如何找到唯一那个?这个问题,文章后面我会写,我们先需要了解目前iOS支持的几种查找元素的方式。
方式1,ios predicate string
如截图中的消费记录按钮,这个按钮从inspector来看,有三种方式可以去查找,其中一种就是这个predicate string,也就是说,只要是一个元素,文本叫“消费记录”,那么都是要被找到的对象。
对于UI测试来说,这种找对象方法是相当不安全的,万一页面有其他元素也叫这个呢?比如这个按钮往下一层级有一个文本,也叫“消费记录”,那么其实找到的是两个元素,一个按钮和一个文本。
所以,官方对这个查找方式还进行的扩展,不一定必须只有等于某个文本,还可以包含某个文本,可以以什么文本开头,以什么文本结尾,甚至于可以写多个条件,文章见这里。
大概写出来是这个样子:
comsume_record_btn = (AppiumBy.IOS_PREDICATE, 'name == "消费记录"')
但是这个场景下,这样写,是不安全的。
方式2,ios class chain
这种方式呢,感觉其实和xpath是一样的,所以我更多使用这种方式,而不用xpath,因为记得哪里看的文章说这种方式效率比xpath高,只有在确实找不到元素的时候才会考虑xpath。
iOS class chain 和 predict比起来,其实很相似,只是class chain可以更精确,比如截图里的例子,我可以指明是button的“消费记录”,而不是文本的“消费记录”,官方文档见这里。
元素查找大概是这样的:
comsume_record_btn = (AppiumBy.IOS_CLASS_CHAIN, '**/XCUIElementTypeButton[`name == "消费记录"`]')
这里定义好了,需要是个Button,就不会找到文本上去。
方式3,xpath
我不用这个,所以不讲。
方式4,accessibility id
在页面找元素定位的时候,我偶然发现有些元素会多出来一个查找方式,叫accessibility id:
比如截图里这个页面,如果有这个id,那么可以很自然地使用AppiumBy.ACCESSIBILITY_ID这个方式去查找元素,但是仔细一看,这个id很不靠谱,他会跟随用户的金币值的变化而变化。去找开发问这个id怎么来的,他们也搞不懂。于是我只好自己去查资料,才明白,原来iOS为了他们自己的XCUITest框架,专门在iOS中弄了一个类,来设置accessibility id,让测试可以方便地找到元素。
可以看到,图中这个方法是为了测试用的,用户看不到。且每个页面元素均有这样一个函数可以添加id。
对于UI测试来说,我们肯定是尽可能地不让开发去修改代码,但是在确实实现不了的情况下,还是可以找他们加的。最好的方式是一批一批提加的方案,而不是想起加一个就找他们。
实现代码比较简单:
gold_num = (AppiumBy.ACCESSIBILITY_ID, 'this is an id')
二、web页面的元素
web页面的元素,跟selenium中的web元素是一模一样的(安卓不太一样),大概写出来是这样:
vip_dead_time = (AppiumBy.XPATH, '//div[@class="renew-btn"]/div/p[2]')
# 赠送vip
send_vip_btn = (AppiumBy.XPATH, "//p[text()='贈送VIP']")
只是需要注意的是,一定要确定自己已经切换到web的context了,才能对元素进行查找和操作,否则会找不到。还有就是如果回到原生页面,一定也要切换context。
# 切换到最新web页面
new_web = driver.contexts[-1]
driver.switch_to.context(new_web)