一、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 WebDriverAppium采用底层驱动商提供统一的WebDriver API,它和Selenium有着千丝万缕的联系,很多方法的使用都很相似,可以参考笔者之前写过的Selenium文章。 Selenium自动化测试-入门 Selenium自动化测试-unittest单元测试框架使用 二、Appium环境搭建2.1 安装Appium运行环境- Android运行环境
安装Android SDK后,并将其加入到系统环境变量中。 - 安装Python
- 安装Node.js
是为了用命令行的方式启动Appium。 - 安装Appium服务器
可以从此网站下载安装http:///
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:///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.结果验证
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
我们首先需要构造一个Desired Capabilities,设置一些参数,用它来连接到APP中,然后就是进行UI自动化操作的标准3步了。 - 获取页面控件
- 操作控件
- 控件信息验证
对这三步很熟悉了之后,我们再在这个基础上做一些封装,使得脚本更加健壮,可维护性更高。接下来按照以上几步来一步步做吧。 四、Desired Capabilities说明Desired Capabilities就是一组设置,这些设置可以让测试脚本控制Appium的运行行为。下面对这些设置做一个简单的说明。从其官方网站我们可以得到全面的信息,网址为:http:///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是否存在,并且路径是否填写正确。
|