Appium自动化测试-入门
一、Appium简介
Appium是一个移动端的自动化框架,是跨平台的。可用于IOS和Android以及firefox的操作系统。
• 原生应用是指用android或ios的sdk编写的应用;
• 移动网页web应用是指网页应用,类似于ios中safari应用或者Chrome应用或者类浏览器的应用;
• 混合应用是指一种包裹webview的应用。
1.1 Appium架构原理
Appium是在手机操作系统自带的测试框架基础上实现的,Android4.2版本以上使用的是UIAutomator,Android4.2及以下使用的是基于Android Instrumentation框架实现的自动化测试工具;iOS是基于iOS自带的UI自动化工具UIAutomation实现的。
Appium由客户端和服务器组成,客户端与服务器通过JSON Wire Protocol进行通信。下图简单的介绍了各部分。
Appium Server:
Appium server使用node.js写的http服务器,遵守REST风格。主要作用是接受从Appium客户端发起的连接,监听客户端发送来的命令,将命令发送给Bootstrap.jar(或Bootstrap.js)执行,并将执行结果通过HTTP应答反馈给Appium客户端。
Bootstrap.jar:
在Android手机上运行的一个应用程序,它在手机上扮演TCP服务器的角色。当Appium需要运行命令时,Appium服务器会与Bootstrap.jar建立TCP通信,Bootstrap.jar负责运行测试。
Appium Clients:
是一个扩展WebDriver 协议的库,负责与Appium服务端建立连接,并将脚本的指令发动到服务端。支持多种语言。
Session:
Appium的客户端于服务端之间进行通信都必须在一个Session的上下文中进行。客户端在发起通信的时候,会首先发动一个叫“Desired Capabilities”的JSON对象给服务器。服务器收到该数据后,会创建一个Session并将Session ID返回给客户端。客户端可以用此ID发送命令。
Desired Capabilities:
是一组设置的键值对的集合,主要用于通知Appium服务器建立需要的Session,其中一些设置可以在Appium运行过程中改变Appium服务器的运行行为。
1.2 Appium优缺点
优点:
- 支持多种应用程序的测试
- 支持使用多种语言来编写测试脚本
- 被测试的应用程序不需要特殊的编译
- Appium支持应用之间跳转的测试
缺点:
- 由于服务端运行在电脑上,该工具必须连接电脑才可以运行
- 只能用于UI的自动化测试,在很多情况下的测试验证只能通过验证界面来进行
1.3 WebDriver
Appium采用底层驱动商提供统一的WebDriver API,它和Selenium有着千丝万缕的联系,很多方法的使用都很相似,可以参考笔者之前写过的Selenium文章。
Selenium自动化测试-入门
Selenium自动化测试-unittest单元测试框架使用
二、Appium环境搭建
2.1 安装Appium运行环境
- Android运行环境
安装Android SDK后,并将其加入到系统环境变量中。 - 安装Python
- 安装Node.js
是为了用命令行的方式启动Appium。 - 安装Appium服务器
可以从此网站下载安装http://appium.io/
2.2 Appium服务器启动
打开Appium软件后,点击右上角的三角形,可以打开启动服务器,如下所示:
如果输出类似如下信息,没有错误提示,就表示启动成功了。
> Launching Appium server with command: C:\tools\Appium\node.exe lib\server\main.js --address 127.0.0.1 --port 4723 --platform-name Android --platform-version 23 --automation-name Appium --device-name "8c28b78c" --log-no-color
> info: Welcome to Appium v1.4.16 (REV ae6877eff263066b26328d457bd285c0cc62430d)
> info: Appium REST http interface listener started on 127.0.0.1:4723
> info: [debug] Non-default server args: {"address":"127.0.0.1","logNoColors":true,"deviceName":"8c28b78c","platformName":"Android","platformVersion":"23","automationName":"Appium"}
> info: Console LogLevel: debug
启动之后,可以在浏览器里面访问http://localhost:4723/看看是否有反应,如果正常启动的话,肯定是有反应的。我们需要在设置中改变一些设置,也可以将界面中的log信息导出到文件中,便于我们处理。
三、编写脚本前的准备
3.1 查看页面元素
Native APP:
我们可以使用Android SDK安装目录下的uiautomatorviewer来查看APP的页面元素,~\sdk\tools\uiautomatorviewer.bat
。
也可以使用Appium inspector来查看,但是没有uiautomatorviewer那么好用。
含有webview的APP:
可以通过Chrome的DevTools来获取,在Chrome中输入chrome://inspect/#devices
后,如果有连接上的设备,可以点击inspect进入查看页面。
不过,有时通过这种方法是无法获取到页面的,原因可能是被测程序的WebView没有开debug模式等。这时我们可以获取当前页面的URL然后通过Chrome或Firefox等来访问并且查看元素。
3.2 相关文档
这个网站上说明了Appium的方方面面,如设计理念、各个平台的安装、脚本编写等等。http://appium.io/slate/cn/master/?python#about-appium
通过appium在GitHub上的介绍我们可以获取编写脚本的一些方法,这里给出的是Python语言的链接。https://github.com/appium/python-client
3.3 简单示例
用Python写Appium的脚本时,只需以下几步即可以构造一个基本的用例,如下代码片断所示:
#构造Desired Capabilities
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '6.0.1'
desired_caps['deviceName'] = '8c28b78c'
desired_caps['appPackage'] = 'com.ss.android.article.news'
desired_caps['appActivity'] = '.activity.SplashActivity'
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
#1.获取元素
videoBtn = driver.find_element_by_name("视频")
#2.操作元素
videoBtn.click()
#3.结果验证
我们首先需要构造一个Desired Capabilities,设置一些参数,用它来连接到APP中,然后就是进行UI自动化操作的标准3步了。
- 获取页面控件
- 操作控件
- 控件信息验证
对这三步很熟悉了之后,我们再在这个基础上做一些封装,使得脚本更加健壮,可维护性更高。接下来按照以上几步来一步步做吧。
四、Desired Capabilities说明
Desired Capabilities就是一组设置,这些设置可以让测试脚本控制Appium的运行行为。下面对这些设置做一个简单的说明。从其官方网站我们可以得到全面的信息,网址为:http://appium.io/slate/en/master/?java#appium-server-capabilities
4.1 与Appium服务器相关的
Capability | 是否为必填项 | 描述 | 值 |
---|---|---|---|
automationName | 否 | Appium使用的测试引擎 | Appium(默认) |
platformName | 是 | 被测设备的系统平台 | iOS,Android,Firefox OS,null(默认) |
platformVersion | 否 | 手机系统版本 | 如6.6.1,null(默认) |
deviceName | 否 | 测试设备类型(测试Android时被忽略) | null(默认) |
app | 否 | 指向APP安装文件,Android中如果设置了appActivity和appPackage,则此会被忽略 | null(默认) |
browserName | 否 | 手机网页测试时浏览器的名称 | 设置为Safari在测iOS和Chrome时,设置为Browser在测Android时 |
newCommandTimeout | 否 | Appium服务器等待Appium客户端发送新消息的时间,单位为s | 60s(默认) |
language | 否 | (仅模拟器使用)设置模拟器的语言 | null(默认) |
locale | 否 | (仅模拟器使用)设置模拟器的使用国家 | null(默认) |
udid | 否 | (仅真机使用)测试设备的ID | 在多台设备与同一台电脑连接时必须指定 |
orientation | 否 | (仅模拟器使用)屏幕方向 | LANDSCAPE,PORTRAIT,null(默认) |
autoWebview | 否 | 直接切换到WebView上下文 | false(默认),true |
noReset | 否 | 在一个Session开始前不重置被测程序的状态 | false(默认),true |
fullReset | 否 | 完全重置(Android通过卸载程序的方式),Session完成后会卸载程序 | false(默认),true |
~
4.2 仅对Android测试有效的设置
Capability | 是否为必填项 | 描述 | 值 |
---|---|---|---|
appActivity | 是 | 被测APP启动的Activity名称 | 如.MainActivity |
appPackage | 是 | 被测APP的包名 | 例如:com.example.android.myApp |
deviceReadyTimeout | 否 | 等待设备ready的超时时间 | 5s(默认) |
ignoreUnimportantViews | 否 | 会忽略一些控件,加快运行 | false(默认),true |
disableAndroidWatchers | 否 | 只针对基于UIAutomator的测试有效,不会监控ANR和Crash,这将较少CPU消耗 | false(默认),true |
unicodeKeyboard | 否 | 是否支持Unicode的键盘,如果输入中文,设置为是 | false(默认),true |
resetKeyboard | 否 | 测试结束后是否恢复键盘,为正常的手机键盘 | false(默认),true |
androidScreenshotPath | 否 | 截图存放的目录 | /data/local/tmp(默认) |
… | … | … | … |
~
关于Android测试的Capability非常的多,以上只是其中常用的一部分。还有iOS相关的没有在这里叙述了,有兴趣的可以访问前面给出的官网地址去查看。
五、获取控件
5.1 Native APP
API | 方法描述 |
---|---|
find_element_by_id(self,id) | 通过控件的resource id来查找控件 |
find_element_by_name(self,name) | Native APP中,name就是控件的Text |
find_element_by_class_name(self,name) | 控件的class name,网页测试也可以用此 |
find_element_by_accessibility_id(self,id) | 控件的accessibility_id就是Content Description |
find_element_by_android_uiautomator(self,uia_string) | 根据UIAutomator的语法查找控件,是WebDriver在兼容Appium时才新加的语法 |
~
页面中同一个ID的控件可能不止一个,最常见的就是列表项。find_element_by_id
是查找页面中第一个ID为指定参数的控件,find_elements_by_id
是查找页面中所有ID为指定参数的控件,返回一个控件列表。其他的查找方法类似。
5.2 Web&Hybrid APP
API | 方法描述 |
---|---|
find_element_by_xpath(self,xpath) | 通过控件的xpath来查找控件 |
find_element_by_css_selector(self,css_selector) | 通过控件的css_selector来查找控件 |
find_element_by_link_text(self,link_text) | 通过链接的text来查找控件 |
find_element_by_partiallink_text(self,link_text) | 通过链接的部分文本来查找控件 |
find_element_by_tag_name(self,tag_name) | 通过网页元素的Tag查找控件 |
~
这一部分的控件查找和Selenium中的几乎一样,可以查看笔者之前的相关文章。
5.3 获取控件举例
下面代码片段为获取图中底部tab的视频按钮的两种方法。第一种用到了name属性,先找到其父控件,进一步缩小范围,因为页面中可能在其他地方也有相同的name。第二种是用find_elements系列的方法,再拿到列表中的第2项,因为下方的几个控件id都是相同的。
self.driver.find_element_by_id("android:id/tabs").find_element_by_name("视频").click() self.driver.find_elements_by_id("com.ss.android.article.news:id/b5e")[1].click()
对于一些找不到方法去定位的元素怎么办呢?首先想到的就是坐标定位,为了兼容更多的机型,可以用相对坐标或者距离元素的位置来定位。更高端的方法就是可以采用图像识别来确定要定位的元素,从而进行点击,可以参考这篇文章。http://tmq.qq.com/2017/02/test_guide/
六、操作控件
6.1 获取控件信息(部分)
API | 方法描述 |
---|---|
text(self) | 获取控件显示的文本信息 |
is_enabled(self) | 判断是否可用了,可用返回true |
is_selected(self) | 是否被选中了,是的话返回true |
id_displayed(self) | 判断控件是否显示,是的话返回true |
get_attribute(self,name) | 获取控件某项信息,如element.get_attribute(“displayed”)等同于id_displayed方法 |
parent(self) | 返回控件的父控件,返回值为一个控件对象 |
6.2 手势操作(部分)
主要有点击、滑动、拖拽、放缩等常用的操作。
API | 方法描述 |
---|---|
click(self) | 点击控件 |
clear(self) | 清楚文本框控件的文本 |
send_keys(self,*value) | 发送文本到控件中 |
tap(self,positions,duration=None) | positions是一个列表,每个列表是一个二元组最多可以同时点击5个点;duration为时间长短,给参数的话则是长按操作 |
swipe(self,start_x,start_y,end_x,end_y,duration=None) | 从一点滑动到另一点,时长为毫秒 |
flick(self,start_x,start_y,end_x,end_y) | 两点快速的滑动 |
scroll(self,origin_ele,destination_ele) | 从origin_ele控件滚动到destination_ele控件 |
drag_and_drop(self,origin_ele,destination_ele) | 把origin_ele控件拖拽到destination_ele控件的位置 |
pinch(self,element=None,percent=200,steps=50) | 在指定控件上执行缩小操作,默认缩放比例为2,分50步完成 |
zoom(self,element=None,percent=200,steps=50) | 在指定控件上执行放大操作,默认缩放比例为2,分50步完成 |
6.3 系统操作API(部分)
系统操作用于模拟硬件操作、设置网络环境、获取系统信息等,下表简单的介绍一下常用的方法。
API | 方法描述 |
---|---|
launch_app(self) | 启动Capability中指定的APP |
is_app_installed(self,package_name) | 判断应用程序是否安装 |
install_app(self,app_path) | 安装APP,app_path指的是电脑上的apk路径 |
close_app(self) | 如果Capability指定的APP在运行,则关闭它 |
background_app(self,seconds) | 将APP放到后台运行一段时间 |
reset(self) | 重置当前被测APP到初始状态 |
current_activity(self) | 获取当前正在显示的Activity |
start_activity(self,app_package,app_activity,**opts) | 启动某个Activity |
pull_file(self,path) | 拉取手机上的一个文件,并以base64格式编码返回数据,path为手机文件路径 |
pull_folder(self,path) | 拉取手机上的一个文件夹,打包后以base64格式编码返回数据,path为手机上的文件夹路径 |
push_file(self,path,base64data) | 将一个base64编码格式的文件从电脑推送到手机上的路径path上 |
press_keycode(self,keycode,metastate=None) | 模拟发送一个硬件码到手机,如返回等 |
open_notification(self) | 打开通知栏 |
network_connection(self) | 返回当前网络连接的类型 |
set_network_connection(self,connectionType) | 设置网络,值为:0 未设置,1 飞行模式,2 WiFi only, 4 Data only, 6 WiFi& Data |
get_screenshot_as_file(self,filename) | 截图并保存在电脑上,filename为路径及截图名称 |
save_screenshot(filename) | 截图并保存在电脑上,filename为路径及截图名称 |
七、控件信息验证
这里我们要说的是查找并操作控件后,怎么确定我们的操作起了作用。在实际的测试中也把它叫做检查点,检查点的划分和验证是UI自动化中的一个重点也是难点。常用的有以下方法:
- 判断某个控件是否显示(操作之后新出现的控件)
- 判断某个控件是否被选中
- 判断某个开关控件是否处于check状态
- 判断某个控件是否enabled
- 截图之后和正确的进行比对
…
可以将以上判断的方式进行封装,便于我们在if语句和assert中使用。关于截图对比的方式,首先要有正确的操作截图,然后再进行对比得出结论看看是否一致,会涉及一些算法相关的知识。
八、常见问题
8.1 A new session could not be created
有一次在执行的过程中,发现输出了以下错误;
selenium.common.exceptions.WebDriverException: Message: A new session could not be created. (Original error: An unknown server-side error occurred while processing the command. (Original error: unknown error: com.android.chrome is not installed on device 8c28b78c
(Driver info: chromedriver=2.18.343845 (73dd713ba7fbfb73cbb514e62641d8c96a94682a),platform=Windows NT 10.0 x86_64)))
可以发现主要的错误应该是com.android.chrome is not installed on device
,这个看起来应该是chrome浏览器的手机端,我们可以尝试安装它。但是,我记得手机上一直都没有安装过这个,最后又检查了一下,发现原来是打开了Appium设置中的Browser,关闭此即可。
然而,除了这个原因有可能是别的原因,我们要具体分析错误输出,还可以做一些事情来来降低这种情况的发生:
- 在初始化的setUp()方法中调用ADB命令强制关闭被测应用一次;
- 添加–session-override选项,命令行中或者Appium界面中;
- 在tearDown()方法中,关闭Appium的session,清理环境。
8.2 Permission to start activity denied.
在使用start_activity()方法来启动另一个APP时,有时会遇到如下错误:
selenium.common.exceptions.WebDriverException: Message: Unable to launch the app: Error: Permission to start activity denied.
这时可以看到我们没有权限打开这一个Activity,通常是因为此Activity在清单文件里面没添加Android:exported=”true”,exported属性就是设置是否允许activity被其它程序调用的。所以我们需要从启动页Activity打开如下所示。这在一些情况下可能会有点麻烦。
app_package='com.gotokeep.keep'
app_activity='.activity.SplashActivity'
self.driver.start_activity(app_package,app_activity)
还有一种错误是找不到要打开的Activity:
elenium.common.exceptions.WebDriverException: Message: Unable to launch the app: Error: Activity used to start app doesn’t exist or cannot be launched! Make sure it exists and is a launchable activity
这时我们要检查Activity是否存在,并且路径是否填写正确。