从0到1搭建移动App功能自动化测试平台(2):操做iOS应用的控件

转载:http://debugtalk.com/post/build-app-automated-test-platform-from-0-to-1-Appium-interrogate-iOS-UI/node

写在前面

前两天微信忽然发来一条系统消息,提示DebugTalk能够开通原创标识了(同时也有了评论功能),虽然一直在期待,但没想到来得这么快,着实是个不小的惊喜。android

另外,最近在公众号后台也收到好几个朋友的信息,有的是询问某某部分何时能发布,有的是但愿能加快更新速度。说实话,收到这样的信息虽然会有压力,但真的挺开心的,由于这说明DebugTalk至少能给一部分人带去价值,这说明这件事自己仍是值得坚持去作的。ios

不过,在更新频率这件事儿上,的确是要跟你们说抱歉了。由于DebugTalk发布的内容全都是原创,主题基本上都是来源于我平常测试工做的经验积累,或者我近期学习一些测试技术的收获总结,这也意味着,我写的东西不少时候并非本身彻底熟悉的(彻底掌握的东西也没有足够的动力专门花时间去写)。ruby

就拿最近连载的《从0到1搭建移动App功能自动化测试平台》系列来讲,因为我也是边探索边总结,所以中途不免会遇到一些意想不到的坑,形成额外的耗时,并且为了保证文章能尽可能通俗易通,我也须要对涉及到的内容充分进行理解,而且通过大量实践进行验证,而后才能站在半个初学者、半个过来人的角度,从新整理思路,最后以尽量流畅的思路将主题内容讲解清楚。微信

基于这些缘由,DebugTalk要作到每日更新是很难了,可是保证每周发布1~2篇仍是能够的,但愿你们能理解。app

关于UI控件

在上一篇文章中,咱们成功地经过Appium Inspector调用模拟器并运行iOS应用,iOS的自动化测试环境也已所有准备就绪了。工具

那么接下来,咱们就能够开始实现自动化测试了么?post

貌似还不行。在开始以前,咱们先想下什么是APP功能自动化测试。学习

APP的功能自动化测试,简单地来讲,就是让功能测试用例自动地在APP上执行。具体到每个测试用例,就是能模拟用户行为对UI控件进行操做,自动化地实现一个功能点或者一个流程的操做。再细分到每一步,就是对UI控件进行操做。测试

所以,在正式开始编写自动化测试用例以前,咱们还须要熟悉如何与APP的UI控件进行交互操做。

在iOS系统中,UI控件有多种类型,常见的有按钮(UIAButton)、文本(UIAStaticText)、输入框(UIATextField)等等。但无论是对什么类型的UI控件进行操做,基本均可以分解为三步,首先是获取目标控件的属性信息,而后是对目标控件进行定位,最后是对定位到的控件执行动做。

获取UI控件信息

在Appium中,要获取iOS的UI控件元素信息,能够采用两种方式:一种是在前一篇文章中提到的Appium Inspector,另外一种是借助Ruby实现的appium_console,在Terminal中经过命令进行查询。

Appium Inspector

运行Appium Server,并启动【Inspector】后,总体界面以下图所示。

Appium inspector introduction

现对照着这张图对Appium Inspector进行介绍。

在右边部分,是启动的模拟器,里面运行着咱们的待测APP。咱们能够像在真机中同样,在模拟器中执行任意功能的操做,固然,模拟器跟真机毕竟仍是有区别的,跟传感器相关的功能,例如摄像头、重力感应等,是无法实现的。

在左边部分,就是Appium Inspector。Inspector主要由以下四个部分组成:

  • 预览界面区:显示画面与模拟器界面一致;不过,当咱们在模拟器中切换界面后,Inspector的预览区中显示图像并不会自动同步,若要同步,须要点击【Refresh】按钮,而后Inspector会将模拟器当前UI信息dump后显示到预览区;在预览区中,能够点击选择任意UI控件。
  • UI信息展现区:展现当前界面预览区中全部UI元素的层级关系和UI元素的详细信息;在预览区中点击选择任意UI控件后,在“Details”信息框中展现选中控件的详细信息,包括name、label、value、xpath等属性值;经过层级关系,咱们也能了解选中控件在当前界面树状结构中所处的具体位置。
  • 交互操做区:模拟用户在设备上的操做,例如单击(tap)、滑动(swipe)、晃动(shake)、输入(input)等;操做动做是针对预览界面区选中的控件,所以在操做以前,务必须要先在预览区点击选择UI元素。
  • 脚本生成区:将用户行为转换为脚本代码;点击【Record】按钮后,会弹出代码区域;在交互操做区进行操做后,就会实时生成对应的脚本代码;代码语言可经过下拉框进行选择,当前支持的语言类型有:C#、Ruby、Objective-C、Java、node.js、Python。

在实践操做中,Inspector最大的用途就是在能够可视化地查看UI元素信息,而且能够将操做转换为脚本代码,这对初学者尤其有用。

例如,在预览区点击选中按钮“BUY NOW”,而后在UI信息展现区的Details窗口就能够看到该按钮的全部属性信息。在交互操做区点击【Tap】按钮后,就会模拟用户点击“BUY NOW”按钮,而且在脚本区域生成当次按钮点击的脚本(选择Ruby语言):

1
find_element( :name, "BUY NOW >").click

如上就是使用Appium Inspector的通常性流程。

Appium Ruby Console

有了Appium Inspector,为何还须要Appium Ruby Console呢?

其实,Appium Ruby Console也并非必须的。通过与多个熟悉Appium的前辈交流,他们也从未用过Appium Ruby Console,这说明Appium Ruby Console并非必须的,没有它也不会影响咱们对Appium的使用。

可是,这并不意味着Appium Ruby Console是多余的。通过这些天对Appium的摸索,我愈加地喜欢上Appium Ruby Console,而且使用的频率愈来愈高,如今已基本上不多使用Appium Inspector了。这种感受怎么说呢?Inspector相比于Ruby Conosle,就像是GUI相比于Linux Terminal,你们应该能体会了吧。

Appium Inspector的功能是很齐全,GUI操做也很方便,可是,最大的问题就是使用的时候很是慢,在预览界面区切换一个页面经常须要好几秒,甚至数十秒,这是很难让人接受的。

在上一节中也说到了,Inspector最大的用途就是在能够可视化地查看UI元素信息,而且能够将操做转换为脚本代码。可是当咱们对Appium的经常使用API熟悉之后,咱们就再也不须要由工具来生成脚本,由于本身直接写会更快,前提是咱们能知道目标控件的属性信息(type、name、label、value)。

在这种状况下,若是能有一种方式能够供咱们快速查看当前屏幕的控件属性信息,那该有多好。

庆幸的是,在阅读Appium官方文档时,发现Appium的确是支持命令行方式的,这就是Appium Ruby Console

Appium Ruby Console是采用Ruby语言开发的,在使用方式上面和Ruby的irb很相似。

在使用Appium Ruby Console时,虚拟机的配置信息并不会从GUI中读取,而是要经过配置文件进行指定。

配置文件的名称统一要求为appium.txt,内容形式以下所示:

1
2
3
4
5
[caps]
platformName = "ios"
platformVersion = '9.3',
app = "/path/to/UICatalog.app.zip"
deviceName = "iPhone Simulator"

其中,platformName指定虚拟机操做系统类型,“ios”或者”android”;platformVersion指定操做系统的版本,例如iOS的’9.3’,或者Android的’5.1’;app指定被测应用安装包的路径。这三个参数是必须的,与Inspector中的配置也能对应上。

在使用Appium Ruby Console时,首先须要启动Appium Server,经过GUI或者Terminal都可。

而后,在Terminal中,进入到appium.txt文件所在的目录,执行arc命令便可启动Appium Ruby Consolearc,便是appium ruby console首字母的组合。

1
2
3
4
➜ ls
appium.txt
➜ arc
[1] pry(main)>

接下来,就能够经过执行命令查询当前设备屏幕中的控件信息。

使用频率最高的一个命令是page,经过这个命令能够查看到当前屏幕中全部控件的基本信息。

例如,当屏幕停留在前面截图中的页面时,执行page命令能够获得以下内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
[1] pry(main)> page
UIANavigationBar
name: HomeView
id: Home => Home
米 => m
去看看 => View
UIAButton
name, label: tabbar category gray
UIAImage
name: debugtalk_logo.png
UIAButton
name, label: tabbar cart gray
UIATableView
value: rows 1 to 4 of 15
UIAPageIndicator
value: page 2 of 2
UIATableCell
name: For the first time ever in a hand held camera, the Osmo brings professional, realtime cinema-quality stabilization.
id: 米 => m
UIAStaticText
name, label, value: For the first time ever in a hand held camera, the Osmo brings professional, realtime cinema-quality stabilization.
id: 米 => m
UIAStaticText
name, label, value: OSMO
UIAButton
name, label: SHOP NOW >
UIATableCell
name: Ronin
UIAStaticText
name, label, value: Ronin
UIAStaticText
name, label, value: Phantom
id: 米 => m
... (略)
UIAButton
name, label: Store
value: 1
id: 门店 => Store
... (略)
UIAButton
name, label: My Account
id: My Account => My Account
nil

经过返回信息,咱们就能够看到全部控件的type、name、label、value属性值。若是在某个控件下没有显示label或value,这是由于这个值为空,咱们能够不予理会。

因为page返回的信息太多,可能不便于查看,所以在使用page命令时,也能够指定控件的类型,至关于对当前屏幕的控件进行筛选,只返回指定类型的控件信息。

指定控件类型时,能够经过string类型进行指定(如 page “Image”),也可经过symbol类型进行指定(如 page :cell)。指定的类型可只填写部份内容,而且不分区大小写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[2] pry(main)> page "Image"
UIAImage
name: debugtalk_logo.png
nil
[3] pry(main)> page :cell
UIATableCell
name: DebugTalk’s smartest flying camera ever.
id: 米 => m
UIATableCell
name: Ronin
UIATableCell
name: Phantom
id: 米 => m
nil

若是须要查看当前屏幕的全部控件类型,能够执行page_class命令进行查看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[4] pry(main)> page_class
14x UIAButton
8x UIAStaticText
4x UIAElement
4x UIATableCell
2x UIAImage
2x UIAWindow
1x UIAPageIndicator
1x UIATableView
1x UIAStatusBar
1x UIANavigationBar
1x UIATabBar
1x UIAApplication
nil

基本上,page返回的控件信息已经足够知足绝大多数场景需求,但有时候状况比较特殊,须要enabledxpathvisible、坐标等属性信息,这时就能够经过执行source命令。执行source命令后,就能够返回当前屏幕中全部控件的全部信息,以xml格式进行展示。

定位UI控件

获取到UI控件的属性信息后,就能够对控件进行定位了。

首先介绍下最通用的定位方式,find。经过find命令,能够实如今控件的诸多属性值(namelabelvaluehint)中查找目标值。查询时不区分大小写,若是匹配结果有多个,则只返回第一个结果。

1
2
3
4
[5] pry(main)> find('osmo')
#<Selenium::WebDriver::Element:0x..febd52a30dcdfea32 id="2">
[6] pry(main)> find('osmo').label
"Osmo"

另外一个通用的定位方式是find_element,它也能够实现对全部控件进行查找,可是相对于find,能够对属性类型进行指定。

1
2
3
4
[7] pry(main)> find_element(:class_name, 'UIATextField')
#<Selenium::WebDriver::Element:0x31d87e3848df8804 id="3">
[8] pry(main)> find_element(:class_name, 'UIATextField').value
"Email Address"

不过在实践中发现,采用findfind_element这类通用的定位方式并很差用,由于定位结果常常不是咱们指望的。

通过反复摸索,我推荐根据目标控件的类型,选择对应的定位方式。总结起来,主要有如下三种方式。

针对Button类型的控件(UIAButton),采用button_exact进行定位:

1
2
[9] pry(main)> button_exact('Login')
#<Selenium::WebDriver::Element:0x..feaebd8302b6d77cc id="4">

针对Text类型的控件(UIAStaticText),采用text_exact进行定位:

1
2
[10] pry(main)> text_exact('Phantom')
#<Selenium::WebDriver::Element:0x1347e89100fdcee2 id="5">

针对控件类型进行定位时,采用tag;以下方式等价于find_element(:class_name, 'UIASecureTextField')

1
2
[11] pry(main)> tag('UIASecureTextField')
#<Selenium::WebDriver::Element:0x..fc6f5efd05a82cdca id="6">

基本上,这三种方式就已经足够应付绝大多数测试场景了。固然,这三种方式只是我我的通过实践后选择的定位方式,除了这三种,Appium还支持不少种其它定位方式,你们可自行查看Appium官方文档进行选择。

另外,除了对控件进行定位,有时候咱们还想判断当前屏幕中是否存在某个控件(一般用于结果检测判断),这要怎么作呢?

一种方式是借助于Appium的控件查找机制,即找不到控件时会抛出异常(Selenium::WebDriver::Error::NoSuchElementError);反过来,当查找某个控件抛出异常时,则说明当前屏幕中不存在该控件。

1
2
3
[12] pry(main)> button_exact('Login_invalid')
Selenium::WebDriver::Error::NoSuchElementError: An element could not be located on the page using the given search parameters.
from /Library/Ruby/Gems/2.0.0/gems/appium_lib-8.0.2/lib/appium_lib/common/helper.rb:218:in `_no_such_element'

该种方式可行,但比较暴力,基本上不会采用这种方式。

另外一种更好的方式是,查找当前屏幕中指定控件的个数,若个数不为零,则说明控件存在。具体操做上,将button_exact替换为buttons_exact,将text_exact替换为texts_exact

1
2
3
4
[12] pry(main)> buttons_exact('Login').count
1
[13] pry(main)> buttons_exact('Login_invalid').count
0

除此以外,基于Ruby实现的appium_lib还支持exists方法,可直接返回Boolean值。

1
2
3
4
[14] pry(main)> exists { button_exact('Login') }
true
[15] pry(main)> exists { button_exact('Login_invalid') }
false

对控件执行操做

定位到具体的控件后,操做就比较容易了。

操做类型很少,最经常使用就是点击(click)和输入(type),这两个操做能覆盖80%以上的场景。

对于点击操做,才定位到的控件后面添加.click方法;对于输入操做,在定位到的输入框控件后面添加.type方法,并传入输入值。

例如,帐号登陆操做就包含输入和点击两种操做类型。

1
2
3
4
5
6
[16] pry(main)> find_element(:class_name, 'UIATextField').type 'leo.lee@debugtalk.com'
""
[17] pry(main)> find_element(:class_name, 'UIASecureTextField').type '123456'
""
[18] pry(main)> button_exact('Login').click
nil

To be continued …

在本文中,咱们学习了对iOS UI控件进行交互操做的通常性方法,为编写自动化测试脚本打好了基础。

在下一篇文章中,咱们就要正式开始针对iOS应用编写自动化测试脚本了。

相关文章
相关标签/搜索