Appium iOS 元素定位与操作

概述

iOS App和Android App由于系统差异原因,元素属性和定位方式也存在一些差异,之前分享过Android 元素定位方式, 本篇博文分享iOS App元素定位方式。

运行环境

1.硬件环境
设备类型 | 系统版本
—|—
Mac-mini | Mac OS 10.14.6
iPhone6 | iOS 12.4.3

2.软件环境
软件类型 | 系统版本
—|—
Appium-desktop | 1.15.0.1

iOS自动化框架

对于 iOS 自动化,Appium 依赖苹果提供的系统框架。对于 iOS 9.2 及更低版本,苹果唯一的自动化技术被称为UIAutomation,它运行在 Instruments 中。从 iOS 10 开始,苹果已经完全删除了 UIAutomation 工具,因此 Appium 不可能按照以前的方式进行测试。同时,苹果推出了一款名为 XCUITest 的新型自动化技术,从 iOS 9.3iOS 10 及以上版本,这将是苹果唯一支持的自动化框架。

Appium 从 Appium 1.6 开始支持 XCUITest。因此对于iOS 9.2以下的系统需要使用UIAutomation方式定位,iOS 9.2以上的版本需要使用 XCUITest 定位。考虑到目前iOS系统更新到了iOS13(截止到2019.12.19) iOS 9.2以下系统属于比较老旧系统,设备覆盖率相对较低,所以主要讲解基于XCUITest的元素定位方式。一般在初始化driver时也会指定automationName的值为XCUITest

1
desired_caps['automationName']='XCUITest'

元素定位工具

Android 和iOS元素常用定位工具如下表所示:

工具 支持平台 说明
appium-inspector android,iOS native 官方appium-desktop安装包自带, 命令行安装没有该工具
app-inspector android,iOS native 阿里开源的macaca框架带的工具, 可以单独安装:npm install -g app-inspector
UIAutomatorviewer android native android sdk自带工具软件
Chrome Inspect android,iOS webview android webview可以直接使用, iOS webview需要安装ios-webkit-debug-proxy并且以ios_webkit_debug_proxy -f chrome-devtools://devtools/bundled/inspector.html 启动使用;

这里我们使用Appium的Appium-desktop工具来获取元素,下载Appium-desktop Mac版(dmg结尾的包名) 然后配置应用参数启动Appium-desktop,不太清楚操作流程可以参考之前的博文: Appium capability参数配置简介 启动appium成功之后可以看到如下界面:

image

iOS元素类型与属性

元素常用类型

在 XCUITest 中,苹果已经为构成视图层次结构的 UI 元素提供了不同的类名。例如 XCUIElementTypeButton表示按钮类型元素。从上面的元素结构视图我们可以看到下面这些常用的元素类型。

image

  • XCUIElementTypeApplication: 应用类型,一般位于根节点
  • XCUIElementTypeWindow:窗口类型元素可以包含按钮,文字用于布局。
  • XCUIElementTypeStatusBar:状态类型
  • XCUIElementTypeOther:自定义类型
  • XCUIElementTypeCollectionView:集合视图
  • XCUIElementTypeCell:元件类型
  • XCUIElementTypeTable:表格类型
  • XCUIElementTypeStaticText:文字类型
  • XCUIElementTypeButton:按钮类型

元素属性

每个元素都有不同的属性值,常用属性值如下:

  • type:元素类型,与className作用一致,如:XCUIElementTypeButton
  • value: 元素值
  • name:元素的文本内容,可用作 AccessibilityId定位方式,如:ClearEmail
  • label:元素标记;绝大多数情况下,与 name 作用一致
  • enabled:元素是否可点击,一般值为true或者false
  • visible;元素是否可见,一般值为true或者false

元素定位策略

ios_predicate

在 iOS 的 UI 自动化中,使用原生支持的Predicate定位方式是最好,可支持元素的单个属性和多个属性定位,属性值还可以使用精确和模糊匹配,强烈推荐使用!

单个/多个属性定位

1
2
driver.find_element_by_ios_predicate("value == 'ClearEmail'")
driver.find_element_by_ios_predicate("type == 'XCUIElementTypeButton' AND value == 'ClearEmail'")

多个属性可以使用关键词AND连接。

属性值匹配——比较运算符

Predicate定位方式支持比较运算符:>、<、==、>=、<=、!=
可用于数值和字符串的比较:

1
2
driver.find_element_by_ios_predicate("value>100")
driver.find_element_by_ios_predicate("value != 'ClearEmail'")

属性值匹配——范围运算符

支持范围运算符:IN、BETWEEN,可用于数值和字符串的范围核对

1
2
3
4

driver.find_element_by_ios_predicate("value BETWEEN {1,6}")
driver.find_element_by_ios_predicate("value IN {'Clear','Email'}")

属性值匹配——字符串相关

字符串相关语法:CONTAINS、BEGINSWITH、ENDSWITH

1
2
3
4

driver.find_element_by_ios_predicate("value CONTAINS 'Email'") #包含某个字符串
driver.find_element_by_ios_predicate("value BEGINSWITH 'Clear'") #以某个字符串开头
driver.find_element_by_ios_predicate("value ENDSWITH '班级Email'") #以某个字符串结束

属性值匹配——通配符

通配符: LIKE其中:?代表一个字符,*代表多个字符
如:一个元素的value属性为ClearEmail:

1
2
3
4

driver.find_element_by_ios_predicate("value LIKE 'Clear?mail'")
driver.find_element_by_ios_predicate("value LIKE 'Clear*'")

属性值匹配——正则表达式

正则表达式:MATCHES
如:一个元素的value属性为ClearEmail,则可以如下定位。

1
2
3

driver.find_element_by_ios_predicate("value MATCHES '^C.+l$'")

获取多个元素

如果要获取一组属性相同的元素,则需要使用def find_elements_by_ios_predicate()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def find_elements_by_ios_predicate(self, predicate_string):
"""Finds elements by ios predicate string.

Args:
predicate_string (str): The predicate string

Usage:
driver.find_elements_by_ios_predicate('label == "myLabel"')

Returns:
:obj:`list` of :obj:`appium.webdriver.webelement.WebElement`
"""
return self.find_elements(by=MobileBy.IOS_PREDICATE, value=predicate_string)

accessibility_id

该定位方式主要使用元素的labelname(两个属性的值都一样)属性进行定位,如该属性为空,也是不能使用该属性。

1
driver.find_element_by_accessibility_id('ClearEmail')

class_name

使用元素的type属性定位,特别注意该属性的唯一性!class_name唯一的情况并不多,一般情况下用不上。

1
driver.find_element_by_class_name('XCUIElementTypeButton')

class_chain

类链定位方法 仅支持 iOS 10或以上,这是 github 的 Mykola Mokhnach 大神开发,仅限在 WebDriverAgent 框架使用,用于替代 xpath.

1
2
3
driver.find_element_by_ios_class_chain('XCUIElementTypeWindow/XCUIElementTypeButton[3]') # 选择第一个子窗口元素的第三个子按钮

driver.find_element_by_ios_class_chain("**/XCUIElementTypeCell[`name BEGINSWITH "B"`]) # 选择树中所有名称以“ B”开头的单元格

相对定位

相对定位是根据元素层级关系先定位到父级元素,然后再进一步定位目标元素。

1
2
3

root_element=driver.find_element_by_ios_predicate("value == 'ClearEmail'") #定位到父级元素
root_element.driver.find_element_by_ios_predicate("value=='xxx'") #定位目标元素

xpath

xpath定位是一种路径定位方式,主要是依赖于元素绝对路径或者相关属性来定位,但是绝对路径xpath执行效率比较低(特别是元素路径比较深的时候)由于iOS 10开始使用的 XCUITest 框架原声不支持,定位速度很慢,所以官方现在不推荐使用。

xpath路径表达式

表达式 描述
/ 从根节点选取。
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
nodename 选取此节点的所有子节点。
. 选取当前节点。
.. 选取当前节点的父节点。
@ 选取属性。

xpath匹配符

通配符 描述
  • | 匹配任何元素节点。
    @* | 匹配任何属性节点。
    node() | 匹配任何类型的节点。
1
driver.find_element_by_xpath("/XCUIElementTypeWindow[1]/XCUIElementTypeOther/XCUIElementTypeOther")

Xpath轴

XPath轴可定义相对于当前节点的节点集,语法格式如下:

1
轴名称::节点测试[谓语]
轴名称 结果
ancestor 选取当前节点的所有先辈(父、祖父等)。
ancestor-or-self 选取当前节点的所有先辈(父、祖父等)以及当前节点本身。
attribute 选取当前节点的所有属性。
child 选取当前节点的所有子元素。
descendant 选取当前节点的所有后代元素(子、孙等)。
descendant-or-self 选取当前节点的所有后代元素(子、孙等)以及当前节点本身。
following 选取文档中当前节点的结束标签之后的所有节点。
namespace 选取当前节点的所有命名空间节点。
parent 选取当前节点的父节点。
preceding 选取文档中当前节点的开始标签之前的所有节点。
preceding-sibling 选取当前节点之前的所有同级节点。
following-sibling 选取当前节点之后的所有同级节点
self 选取当前节点。

示例

1
xpath=//XCUIElementTypeStaticText[@name="登录/注册"]/preceding-sibling::XCUIElementTypeButton[1]

上面语法表示获取属性为name="登录/注册的元素的同级节点中第一个XCUIElementTypeButton元素

更多示例如下表:

例子 结果
child::book 选取所有属于当前节点的子元素的 book 节点。
attribute::lang 选取当前节点的 lang 属性。
child::* 选取当前节点的所有子元素。
attribute::* 选取当前节点的所有属性。
child::text() 选取当前节点的所有文本子节点。
child::node() 选取当前节点的所有子节点。
descendant::book 选取当前节点的所有 book 后代。
ancestor::book 选择当前节点的所有 book 先辈。
ancestor-or-self::book 选取当前节点的所有 book 先辈以及当前节点(如果此节点是 book 节点)
child::*/child::price 选取当前节点的所有 price 孙节点。

扩展资料:xpath语法

元素操作方法

定位到元素之后我们一般会模拟用户进行点击,文本输入,滑动等操作,那么该如何进行这些操作呢。

点击

点击操作可以直接使用click()方法来进行点击即可。

1
driver.find_element_by_ios_predicate("value == 'ClearEmail'").click()

文本操作

对于文本框元素,我们可以使用send_keys()方法来输入文字,使用clear()方法来清除文本框内容。

1
2
3
4
elem = self.driver.find_element_by_ios_predicate(
"type=='XCUIElementTypeTextField' AND value CONTAINS '%s'" % txtName)
elem.clear() #清除文本框内容
elem.send_keys('xxxx') #传入文本框内容

坐标点点击

对于有些元素无法使用常规的定位方式来点击,这个时候我们可以根据坐标点来进行点击操作。这个和Android app元素操作是一样的。

1
2
3
from appium.webdriver.common.touch_action import TouchAction

TouchAction(self.driver).tap(x=30,y=234).perform() #点击操作

滑动

滑动操作也和Android app应用操作一样,详见之前的博文: Appium滑动操作

报错相关

  1. 定位连接中断
    1
    ProtocolError: ('Connection aborted.', ConnectionResetError(54, 'Connection reset by peer'))
  • 报错原因:Appium 服务超时中断,
  • 解决方案:可以设置newCommandTimeout延长,默认是60s,然后重启appium服务,升级openssl版本。还有一种可能是需要升级 openssl 升级方法见:Mac 升级/更新openSSL版本

参考资料