从0到1搭建移动App功能自动化测试平台(2):操作iOS应用的控件
转自:http://debugtalk.com/post/build-app-automated-test-platform-from-0-to-1-Appium-interrogate-iOS-UI/
写在前面
前两天微信突然发来一条系统消息,提示DebugTalk
可以开通原创标识了(同时也有了评论功能),虽然一直在期待,但没想到来得这么快,着实是个不小的惊喜。
另外,最近在公众号后台也收到好几个朋友的信息,有的是询问某某部分什么时候能发布,有的是希望能加快更新速度。说实话,收到这样的信息虽然会有压力,但真的挺开心的,因为这说明DebugTalk
至少能给一部分人带去价值,这说明这件事本身还是值得坚持去做的。
不过,在更新频率这件事儿上,的确是要跟大家说抱歉了。因为DebugTalk
发布的内容全都是原创,主题基本上都是来源于我日常测试工作的经验积累,或者我近期学习一些测试技术的收获总结,这也意味着,我写的东西很多时候并不是自己完全熟悉的(完全掌握的东西也没有足够的动力专门花时间去写)。
就拿最近连载的《从0到1搭建移动App功能自动化测试平台》系列来说,由于我也是边探索边总结,因此中途难免会遇到一些意想不到的坑,造成额外的耗时,而且为了保证文章能尽量通俗易通,我也需要对涉及到的内容充分进行理解,并且经过大量实践进行验证,然后才能站在半个初学者、半个过来人的角度,重新整理思路,最后以尽可能流畅的思路将主题内容讲解清楚。
基于这些原因,DebugTalk
要做到每日更新是很难了,但是保证每周发布1~2篇还是可以的,希望大家能理解。
关于UI控件
在上一篇文章中,我们成功地通过Appium Inspector调用模拟器并运行iOS应用,iOS的自动化测试环境也已全部准备就绪了。
那么接下来,我们就可以开始实现自动化测试了么?
貌似还不行。在开始之前,我们先想下什么是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进行介绍。
在右边部分,是启动的模拟器,里面运行着我们的待测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 Console
。arc
,即是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: dji_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: dji_logo.png
nil
[3] pry(main)> page :cell
UIATableCell
name: DJI’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
返回的控件信息已经足够满足绝大多数场景需求,但有时候情况比较特殊,需要enabled
、xpath
、visible
、坐标等属性信息,这时就可以通过执行source
命令。执行source
命令后,就可以返回当前屏幕中所有控件的所有信息,以xml格式进行展现。
定位UI控件
获取到UI控件的属性信息后,就可以对控件进行定位了。
首先介绍下最通用的定位方式,find
。通过find
命令,可以实现在控件的诸多属性值(name
、label
、value
、hint
)中查找目标值。查询时不区分大小写,如果匹配结果有多个,则只返回第一个结果。
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"
|
不过在实践中发现,采用find
、find_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@dji.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应用编写自动化测试脚本了。
从0到1搭建移动App功能自动化测试平台(2):操作iOS应用的控件的更多相关文章
- 从0到1搭建移动App功能自动化测试平台(0):背景介绍和平台规划
本文作者: 伯乐在线 - debugtalk .未经作者许可,禁止转载!欢迎加入伯乐在线 专栏作者. 转载地址:http://blog.jobbole.com/101221/ 背景 最近新加入DJI的 ...
- Appium 从 0 到 1 搭建移动 App 功能自动化测试平台 (1):模拟器中运行 iOS 应用
转载:https://testerhome.com/topics/4960 在上一篇文章中,我对本系列教程的项目背景进行了介绍,并对自动化测试平台的建设进行了规划. 在本文中,我将在已准备就绪的iOS ...
- .Net语言 APP开发平台——Smobiler学习日志:Poplist控件的正确打开方式以及如何快速实现
最前面的话:Smobiler是一个在VS环境中使用.Net语言来开发APP的开发平台,也许比Xamarin更方便 样式一 一.目标样式 我们要实现上图中的效果,需要如下的操作: 1.从工具栏上的&qu ...
- .Net语言 APP开发平台——Smobiler学习日志:Poplist控件在APP中的应用场景以及代码
最前面的话:Smobiler是一个在VS环境中使用.Net语言来开发APP的开发平台,也许比Xamarin更方便 一.目标样式 我们要实现上图中的效果,需要如下的操作: 1.从工具栏上的”Smobil ...
- IOS之按钮控件--Button全解析及使用 分类: ios技术 2015-01-17 17:09 169人阅读 评论(0) 收藏
IOS开发中伴随我们始终的 最常用的几个空间之一 -- UIButton 按钮,对于button今天在此做一些浅析,并介绍下主流用法以及常见问题解决办法. 首先是继承问题,UIButton继承于UIC ...
- HTML5 Web app开发工具Kendo UI Web中Grid网格控件的使用
Kendo UI Web中的Grid控件不仅可以显示数据,并对数据提供了丰富的支持,包括分页.排序.分组.选择等,同时还有着大量的配置选项.使用Kendo DataSource组件,可以绑定到本地的J ...
- app每个页面都有一个相同的浮层控件 实现思路
可以创建一个window,设置其windowLevel为alert;
- [iOS基础控件 - 5.5] 代理设计模式 (基于”APP列表"练习)
A.概述 在"[iOS基础控件 - 4.4] APP列表 进一步封装,初见MVC模式”上进一步改进,给“下载”按钮加上效果.功能 1.按钮点击后,显示为“已下载”,并且不 ...
- UIScrollView控件实现图片缩放功能
转发自:http://www.cnblogs.com/wendingding/p/3754268.html 一.缩放 1.简单说明: 有些时候,我们可能要对某些内容进行手势缩放,如下图所示 UIScr ...
随机推荐
- 如何写一个简单的http服务器
最近几天用C++写了一个简单的HTTP服务器,作为学习网络编程和Linux环境编程的练手项目,这篇文章记录我在写一个HTTP服务器过程中遇到的问题和学习到的知识. 服务器的源代码放在Github. H ...
- AJAX原生JS代码
var http_request = false;function send_request(method,url,content,responseType,callback){ http_reque ...
- 9.5.8 Optimizing InnoDB Disk I/O
如果你数据库设计以及sq操作都是最佳实践,但是你数据库仍然被较重的io活动拖累的较慢,那么试一试看看top或者windows的任务管理器,cpu使用率和工作量低于70%,那么或许是您的硬盘较慢. 1 ...
- 使用JSONObject遇到的问题,java.lang.NoClassDefFoundError: net/sf/json/JSONObject
先是报 java.lang.NoClassDefFoundError: net/sf/json/JSONObject 这个错误, 打开项目属性找到java build path中的libaries,找 ...
- VC++ 两种动态调整控件位置的方法(CButton设置为Radio形式会出现错误)
((CButton*)GetDlgItem(IDC_CHECK1))->MoveWindow(, cy - , , ); ((CButton*)GetDlgItem(IDC_CHECK2))-& ...
- poj3461 Oulipo(KMP模板)
Oulipo Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 17795 Accepted: 7160 Descripti ...
- 用于模式匹配的String方法
String支持四种使用正则表达式的方法. 1.search()返回第一个与之匹配的子串的起始位置,找不到返回-1.search()参数是一个正则表达式,如果参数不是正则表达式,则会先通过RegExp ...
- Unity使用Windows弹窗保存图片
此功能都在类EditorUtility中(using UnityEditor;) 包括 OpenFilePanel打开文件窗口Displays the "open file" di ...
- Git 查看某个版本修改了哪些文件
. . . . . 查看某个版本提交了哪些文件,其实就是查看该版本与其上一个版本之间的差异,所以通过 git diff 命令来取得结果,并且对比的是要查看的版本与它的上一个版本的 commit 号. ...
- iOS常用库之Masonry
简单介绍 Masonry 源码地址:https://github.com/Masonry/Masonry Masonry是一个轻量级的布局框架 拥有自己的描述语法 采用更优雅的链式语法封装自动布局 简 ...