appium的核心其实是一个暴露了一系列REST API的server。
这个server的功能其实很简单:监听一个端口,然后接收由client发送来的command。翻译这些command,把这些command转成移动设备可以理解的形式发送给移动设备,然后移动设备执行完这些command后把执行结果返回给appium server,appium server再把执行结果返回给client。
在这里client其实就是发起command的设备,一般来说就是我们代码执行的机器,执行appium测试代码的机器。狭义点理解,可以把client理解成是代码,这些代码可以是java/ruby/python/js的,只要它实现了webdriver标准协议就可以。
这样的设计思想带来了一些好处:
- 1,可以带来多语言的支持;
- 2,可以把server放在任意机器上,哪怕是云服务器都可以;(是的,appium和webdriver天生适合云测试)
Session
session就是一个会话,在webdriver/appium,你的所有工作永远都是在session start后才可以进行的。一般来说,通过POST /session这个URL,然后传入Desired Capabilities就可以开启session了。
开启session后,会返回一个全局唯一的session id,以后几乎所有的请求都必须带上这个session id,因为这个seesion id代表了你所打开的浏览器或者是移动设备的模拟器。
进一步思考一下,由于session id是全局唯一,那么在同一台机器上启动多个session就变成了可能,这也就是selenium gird所依赖的具体理论根据。
本文版权归乙醇所有,欢迎转载,但请注明作者与出处,严禁用于任何商业用途
Desired Capabilities
Desired Capabilities携带了一些配置信息。从本质上讲,这个东东是key-value形式的对象。你可以理解成是java里的map,python里的字典,ruby里的hash以及js里的json对象。实际上Desired Capabilities在传输时就是json对象。
Desired Capabilities最重要的作用是告诉server本次测试的上下文。这次是要进行浏览器测试还是移动端测试?如果是移动端测试的话是测试android还是ios,如果测试android的话那么我们要测试哪个app? server的这些疑问Desired Capabilities都必须给予解答,否则server不买账,自然就无法完成移动app或者是浏览器的启动。
具体例子如下:
For example, we might set the
platformName
capability toiOS
to tell Appium that we want an iOS session, rather than an Android one. Or we might set thesafariAllowPopups
capability totrue
in order to ensure that, during a Safari automation session, we’re allowed to use JavaScript to open up new windows. See the capabilities doc for the complete list of capabilities available for Appium
Appium Server
这就是每次我们在命令行用appium命令打开的东西。
Appium Clients
由于原生的webdriver api是为web端设计的,因此在移动端用起来会有点不伦不类。appium官方提供了一套appium client,涵盖多种语言ruby/java/python,在我看来ruby client是实现最好的。在测试的时候,一般要使用这些client库去替换原生的webdriver库。这实际上不是替换,算是client对原生webdriver进行了一些移动端的扩展,加入了一些方便的方法,比如swipe之类,appium client让我们可以更方便的写出可读性更好的测试用例。
Appium.app, Appium.exe
appium server的GUI版本,前者用在osx上,后者是windows上。可视化、不需要装node,可以看app的UI结构是这个东东的卖点。
appium简明教程(3)——appium的安装windows版
appium的哲学里有一条就是不重新发明轮子。同样,官方已经有明确的安装步骤了,因此在这里纯属搬砖。
感谢testerhome的辛勤翻译。
本文版权归乙醇所有,欢迎转载,但请注明作者与出处,严禁用于任何商业用途
限制
如果你在windows上安装appium,你没法使用预编译专用于OS X的.app文件,你也将不能测试IOS apps,因为appium依赖OS X专用的库来支持IOS测试。这意味着你只能通过在mac上来运行IOS的app测试。这点限制挺大。
开始安装
- 安装nodejs 0.8版本及以上, 通过官方的安装程序来安装。
- 安装android的sdk包,(http://developer.android.com/sdk/index.html), 运行依赖sdk中的’android’工具。并确保你安装了Level17或以上的版本api。设置
ANDROID_HOME
系统变量为你的Android SDK路径,并把tools platform-tools两个目录加入到系统的Path路径里。因为这里面包含有一些执行命令 - 安装java的JDK,并设置
JAVA_HOME
变量为你的JDK目录。 - 安装Apache Ant 或者直接使用Android Windows SDK自带的ant,地址在eclipseplugins目录,你需要把这个目录加到你的系统PATH变量中
- 安装Apache Maven. 并且设置M2HOME和M2环境变量,把M2环境变量添加到你的系统PATH变量中。
- 安装Git. 确保你安装了windows下的Git,以便可以运行常用的command命令
现在,你已经下载安装了所有的依赖,开始运行 reset.bat
运行Appium
要在windows上运行测试用例,你需要先启动Android模拟器或者连接上一个API Level17以上的android真机。 然后在命令行运行appium node .
备注
- 你必须带上–no-reset和–full-reset标记,以用于windows上的android
- 有一个硬件加速模拟器用于android,但是它有自己的一些限制,如果你想了解更多,请参考页面
- 确保在你的AVD的
config.ini
中有一个配置项为hw.battery=yes
最简略的安装方式
出于对官方文档的尊重,我按照原文翻译,如下介绍我的安装心得。官方提到的一些工具,其实并不需要安装。 下面介绍我已经测试过的安装和使用过程
安装appium
- 安装nodejs
2、使用npm安装appium,npm install -g appium
注意:在某些情况下,appium安装的时候并不会把appium的路径放进系统的PATH里,这时候需要手工去加一下。
运行appium
启动appium,直接运行appium 即可。
更新appium
通过npm install -g appium
来更新appium即可
appium简明教程(4)——appium client的安装
appium client是对webdriver原生api的一些扩展和封装。它可以帮助我们更容易的写出用例,写出更好懂的用例。
appium client是配合原生的webdriver来使用的,因此二者必须配合使用缺一不可。
从本节开始,教程的内容将涵盖3个语言,ruby/python/java。
本文版权归乙醇所有,欢迎转载,但请注明作者与出处,严禁用于任何商业用途
安装appium client
ruby篇(一定要在线安装)
ruby的appium client叫做appium lib,为什么是这样就不解释了,总之是历史原因。
首先update rubygem和bundler(说老实话,真的不需要,但官方文档上这么写)
gem update –system ;
gem update bundler
然后使用gem安装
gem uninstall -aIx appium_lib ;(这个也不是必须的)
gem install –no-rdoc –no-ri appium_lib
python篇(尽量在线安装)
推荐使用pip安装
pip install Appium-Python-Client
当然了也可以在Pipy上下载源码安装
tar -xvf Appium-Python-Client-X.X.tar.gz(windows上用7zip可以解压)
cd Appium-Python-Client-X.X
python setup.py install
最后,也可以通过github安装(要git客户端)
git clone git@github.com:appium/python-client.git
cd python-client
python setup.py install
java篇(在线安装)
java的话用maven安装就可以了
io.appium
java-client
1.3.0
当然了,也可以自己下载jar包,请自行选择最新版本。
appium简明教程(5)——appium client方法一览
appium client扩展了原生的webdriver client方法
下面以java代码为例,简单过一下appium client提供的适合移动端使用的新方法
- resetApp()
- getAppString()
- sendKeyEvent()
- currentActivity()
- pullFile()
- pushFile()
- pullFolder()
- hideKeyboard()
- runAppInBackground()
- performTouchAction()
- performMultiTouchAction()
- tap()
- swipe()
- pinch()
- zoom()
- getNamedTextField()
- isAppInstalled()
- installApp()
- removeApp()
- launchApp()
- closeApp()
- endTestCoverage()
- lockScreen()
- shake()
- complexFind()
- scrollTo()
- scrollToExact()
- openNotifications()
-
Context Switching: .context(), .getContextHandles(), getContext())
新增的locator -
findElementByAccessibilityId()
- findElementsByAccessibilityId()
- findElementByIosUIAutomation()
- findElementsByIosUIAutomation()
- findElementByAndroidUIAutomator()
-
findElementsByAndroidUIAutomator()
这些方法主要覆盖了3大类: -
driver扩展:比如增加了resetApp等操作app的方法
- action扩展:增加一些移动端的特有的action(怎么描述呢,相当于是移动端 特有的操作),比如swipe,shake(嗯,有了这个方法就可以让代码帮你摇一摇了)等;
- locator扩展:增加了一些移动端专属的定位策略
本文版权归乙醇所有,欢迎转载,但请注明作者与出处,严禁用于任何商业用途
下一节我们开始介绍使用appium启动android模拟器
appium简明教程(6)——启动appium及android模拟器
一般情况下,我们都从命令行启动appium。
windows下,dos命令窗口输入
appium
如果该命令报错,那么请重装appium
npm install -g appium
如果安装出错,请自行更换npm源。
npm -g –registry http://registry.cnpmjs.org install appium
然后请打开android的模拟器,如果没有请新建一个虚拟设备。请自行解除设备锁定(手动把屏幕解锁了),以防万一。
下面的代码以启动android原生的计算器程序为例
ruby篇
1
2
3
4
5
|
require 'appium_lib'
caps = {
caps:{ platformName: 'Android', appActivity: '.Calculator', appPackage: 'com.android.calculator2' },
appium_lib: { sauce_username: nil, sauce_access_key: nil } }
driver = Appium::Driver.new(caps).start_driver
|
讨论:可以看出ruby lib里面的Appium::Driver类实际上就是原生的webdriver类的子类,当然了,由于ruby语法灵活,也可以使用monkey patch来实现类似功能。
python篇
1
2
3
4
5
6
7
8
9
|
from appium import webdriver
desired_caps = {}
desired_caps[
'platformName'] = 'Android'
desired_caps[
'platformVersion'] = '4.2'
desired_caps[
'deviceName'] = 'Android Emulator'
desired_caps[
'appPackage'] = 'com.android.calculator2'
desired_caps[
'appActivity'] = '.Calculator'
driver = webdriver.Remote(
'http://localhost:4723/wd/hub', desired_caps)
|
讨论:webdriver.Remote实际上就是原生webdriver的子类,另外Remote()构造函数的第一个参数中需要显示指定appium server监听的端口
java篇
新建java项目时候,请注意将selenium-webdriver以及appium client的jar包导入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import io.appium.java_client.AppiumDriver;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
DesiredCapabilities capabilities =
new DesiredCapabilities();
capabilities.setCapability(CapabilityType.BROWSER_NAME,
"");//这句不是必须的
capabilities.setCapability(
"deviceName","Android Emulator");
capabilities.setCapability(
"platformVersion", "4.4");
capabilities.setCapability(
"platformName","Android");
capabilities.setCapability(
"appPackage", "com.android.calculator2");
capabilities.setCapability(
"appActivity", ".Calculator");
AppiumDriver driver =
new AppiumDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities);
|
讨论:AppiumDrvier是原生webdriver的子类。
在这里我们可以看到,新建driver的时候必须要指定一个DesiredCapabilities 对象,该对象究竟是何方神圣,我们下一节会仔细讲解。
appium简明教程(7)——Desired Capabilities详解
Desired Capabilities在启动session的时候是必须提供的。
Desired Capabilities本质上是key value的对象,它告诉appium server这样一些事情:
- 本次测试是启动浏览器还是启动移动设备?
- 是启动andorid还是启动ios?
- 启动android时,app的package是什么?
- 启动android时,app的activity是什么?
本文版权归乙醇所有,欢迎转载,但请注明作者与出处,严禁用于任何商业用途
Appium的Desired Capabilities是扩展了webdriver的Desired Capabilities的,下面的一些通用配置是需要指定的:
- automationName:使用哪种自动化引擎。appium(默认)还是Selendroid?
- platformName:使用哪种移动平台。
iOS
,Android
, orFirefoxOS?
- deviceName:启动哪种设备,是真机还是模拟器?
iPhone Simulator
,iPad Simulator
,iPhone Retina 4-inch
,Android Emulator
,Galaxy S4
, etc… - app:应用的绝对路径,注意一定是绝对路径。如果指定了appPackage和appActivity的话,这个属性是可以不设置的。另外这个属性和browserName属性是冲突的。
- browserName:移动浏览器的名称。比如Safari’ for iOS and ‘Chrome’, ‘Chromium’, or ‘Browser’ for Android;与app属性互斥。
- udid:物理机的id。比如1ae203187fc012g。
下面这些属性是android平台特定的:
- appActivity:待测试的app的Activity名字。比如MainActivity, .Settings。注意,原生app的话要在activity前加个”.”。
- appPackage:待测试的app的java package。比如com.example.android.myApp, com.android.settings。
本文主要讨论android平台的appium测试方法和技巧,因此在这里就不列出ios设备特定的属性了。
更多信息请参考官方文档
在这里我们发现,我们经常要获取app的package和activity名字,那么有什么工具可以让我们方便的获取到这些信息呢?下一节讲回答这个问题。
appium简明教程(8)——那些工具
monitor.bat(hierarchyviewer.bat已经不赞成继续使用了)
该文件位于your_andriod_sdk_pathtools下面。以乙醇的机器为例,其位于E:adt-bundle-windows-x86-20131030sdktools下。
该工具可以帮我们找到android控件的content-description,为以后的find_element_by_accessibility_id
定位方法做参数使用。
关于什么是content-description,可以参考官方文档。
本文版权归乙醇所有,欢迎转载,但请注明作者与出处,严禁用于任何商业用途
好,露个脸。
uiautomatorviewer.bat
该文件位于your_andriod_sdk_pathtools下面。以乙醇的机器为例,其位于E:adt-bundle-windows-x86-20131030sdktools下。
该工具主要用来查看控件的属性,比如resource id,class name等。
该工具也可查看被测app的appPackage(Desired Capabilities中使用)。
爆照。
好了,是不是感觉还缺了点什么呢?
确实如此,被测app的appActivity怎么获取呢?
下一讲我们详细讲解如何获取被测app的appActivity。
appium简明教程(9)——如何获取android app的Activity
有时候在appium的Desired Capabilities中需要指定被测app的appActivity,下面的方法可能会对你有所帮助。
方法一
如有你有待测项目的源码,那么直接查看源码就好。如果没有,那么请联系有源码的同学,这是推荐方法。
本文版权归乙醇所有,欢迎转载,但请注明作者与出处,严禁用于任何商业用途
方法二
如果你没有代码,那么可以反编译该app。
这里将用到2个工具,分别是dex2jar和jd-gui。你可以在这里下载目前为止的最新版本以及示例apk。
我们以工具包里的ContactManager.apk为例,简单介绍一下反编译的流程。
- 1,重命名ContactManager.apk为ContactManager.zip并解压得到文件classes.dex;
- 2,解压dex2jar-0.0.9.15.zip,并从命令行进入该文件夹;
-
3,运行命令
d2j-dex2jar.bat path_toclasses.dex
在当前文件夹下得到classes-dex2jar.jar; -
4,解压jd-gui-0.3.6.windows.zip得到文件jd-gui.exe;
- 5,使用jd-gui.exe打开classes-dex2jar.jar;
嗯,好了,可以尽情欣赏了。上图。
上图所示的ContactManager就是待测app的main activity。
方法三
参考testerhome的这个帖子
使用log查看大法(嗯,windows上没grep不幸福,好在有powershell的Select-String,可以拿来勉强一用),直接搬砖。
a、启动待测apk
b、开启日志输出:adb logcat>D:/log.txt
c、关闭日志输出:ctrl+cd、查看日志
找寻:
Displayed com.mm.android.hsy/.ui.LoginActivity: +3s859ms
appPackage = com.mm.android.hsy
appActivity = .ui.LoginActivity
好了,准备活动做的差不多了。下一节乙醇带大家进行控件定位之旅。
appium简明教程(10)——控件定位基础
狭义上讲,UI级的自动化测试就是让机器代替人去点来点去的过程。
但机器去点什么(点上面还是点左边),怎么点(是长按还是轻触),这些东西是必须由代码的编写者所指示清楚的。
控件定位就是解决机器点什么的问题的。
一般说来,我们可以这样告诉机器:去点登陆按钮。
机器很笨,它并不知道什么是登陆按钮。因为登陆按钮是自然语言的描述。
如果你让一个人去点登陆按钮,那么他其实也是要经过一系列的脑补以后才可以做这件事的。
这个脑补的过程还原如下:
这个一定是个按钮
这个按钮一定在被测的应用上
这个按钮大概上面有登陆这个文字信息
嗯,还真有一个,那么点吧。
这就是人探索性测试的一个简单过程。一般来说,如果你给出的信息不太充分,人类还是可以通过一系列的探索性思维去理解你的描述的。这个属于心理学的问题,不展开解释。
但是机器并不是人,如果你给出的描述不精确的话,机器是不会自发性的进行探索和脑补的。
因此控件定位就是精确的描述控件特征并告诉机器的过程。
本文版权归乙醇所有,欢迎转载,但请注明作者与出处,严禁用于任何商业用途
控件的特征就是控件的属性,我们可以通过上一讲中的uiautomatorviewer去获取。
在appium的定位世界里,下面这些方法是可以为我们使用的。也就是说,我们通过下面几个约定好的方式,按照webdriver和appium的DSL(自行搜索并理解)进行控件特征的描述和定位。
继承自webdriver的方法,也就是通过这3个特征可以定位控件
- find by “class” (i.e., ui component type,andorid上可以是android.widget.TextView)
- find by “xpath” (i.e., an abstract representation of a path to an element, with certain constraints,由于appium的xpath库不完备的原因,这个不太推荐)
-
find by “id”(android上是控件的resource id)
由Mobile JSON Wire Protocol 协议中定义的方法,更适合移动设备上的控件定位 -
-ios uiautomation
: a string corresponding to a recursive element search using the UIAutomation library (iOS-only) -android uiautomator
: a string corresponding to a recursive element search using the UiAutomator Api (Android-only)accessibility id
: a string corresponding to a recursive element search using the Id/Name that the native Accessibility options utilize.
在appium 的client对Mobile JSON Wire Protocol中定义的方法进行了封装,使其调用起来更加方便
ruby篇
1
2
3
4
|
find_element
:accessibility_id, 'Animation'
find_elements
:accessibility_id, 'Animation'
find_element
:uiautomator, 'new UiSelector().clickable(true)'
find_elements
:uiautomator, 'new UiSelector().clickable(true)'
|
当然了,你也可以使用原生的webdriver方法
find_element id: ‘resource_id’
另外,ruby lib里提供了一些非常好用的简便方法来进行控件的定位,好写,好读。
- text(value_or_index) :Find the first TextView that contains value or by index. If int then the TextView at that index is returned.
- button(value_or_index):Find the first button that contains value or by index. If int then the button at that index is returned
更多请看这里
python篇
1
2
3
4
5
6
7
8
9
|
el = self.driver.find_element_by_android_uiautomator(
'new UiSelector().description("Animation")')
self.assertIsNotNone(el)
els = self.driver.find_elements_by_android_uiautomator(
'new UiSelector().clickable(true)')
self.assertIsInstance(els, list)
el = self.driver.find_element_by_accessibility_id(
'Animation')
self.assertIsNotNone(el)
els = self.driver.find_elements_by_accessibility_id(
'Animation')
self.assertIsInstance(els, list)
|
总的来说就是在driver里增加了
- find_element_by_accessibility_id
- find_elements_by_accessibility_id
- find_element_by_android_uiautomator
- find_element_by_android_uiautomator
等方法
java篇
前面也讲过了,新增了这些方法
1
2
3
4
5
6
|
findElementByAccessibilityId()
findElementsByAccessibilityId()
findElementByIosUIAutomation()
findElementsByIosUIAutomation()
findElementByAndroidUIAutomator()
findElementsByAndroidUIAutomator()
|
讨论:从上面可以看出来,python 和 java client对移动端控件定位的封装是比较初级的。ruby lib中封装了很多方便和简洁的方法,因此可以看出,使用ruby lib是优于python和java的选择。当然,如果忽略性能的话。
下一节我们开始具体看下如何用resource id去定位控件。
appium简明教程(11)——使用resource id定位
上一节乙醇带大家了解了appium的定位策略。实际上appium的控件定位方式是完全遵守webdriver的mobile扩展协议的。
这一节将分享一下如何使用resource id来定位android策略。
什么是resource id,这个不属于本文的范畴,大家可以点这里了解。
我们可以有两种方式来使用resource id进行定位:
- 使用 findElement(By.id(“resourceId”)) 的方式。这也是原生的webdriver定义的方法,不过竟然在appium的官方文档里没有提及,属于隐藏技;
- 使用 find_elements_by_android_uiautomator(‘new UiSelector().resourceId(“the_id”)’) 的方式;关于uiautomator定位后面的教程会展开讲解;
从上面的代码片段可以看到,使用 find_element_by_id 的方式进行定位是最简便的。
那么怎么获取控件的resource id呢,使用uiautomatorviewer就可以了。具体方法如下图所示。
现在就以上图所示的android原生计算器程序为例,看一下每种语言是如何实现点击【9】这个按钮的。
目的
点击计算器上的【9】这个按钮。该按钮的id是com.android.calculator2:id/digit6
。先甜后苦,从ruby开始。
本文版权归乙醇所有,欢迎转载,但请注明作者与出处,严禁用于任何商业用途
Ruby篇
1
2
3
4
5
|
require 'appium_lib'
caps = {
caps:{ platformName: 'Android', appActivity: '.Calculator', appPackage: 'com.android.calculator2' },
appium_lib: { sauce_username: nil, sauce_access_key: nil, debug: true} }
dr = Appium::Driver.new(caps).start_driver
dr.find_element(
id: 'com.android.calculator2:id/digit9').click
|
Python篇
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#coding:utf-8
from appium import webdriver
from time import sleep
desired_caps = {}
desired_caps[
'platformName'] = 'Android'
desired_caps[
'platformVersion'] = '4.4'
desired_caps[
'deviceName'] = 'Android Emulator'
desired_caps[
'app'] = 'Calculator.apk'
desired_caps[
'appPackage'] = 'com.android.calculator2'
desired_caps[
'appActivity'] = '.Calculator'
dr = webdriver.Remote(
'http://localhost:4723/wd/hub', desired_caps)
sleep(
3)
dr.find_element_by_id(
'com.android.calculator2:id/digit9').click()
|
Java篇
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
//新建一个FindById类位于info.itest.www package下面
package info.itest.www;
import io.appium.java_client.AppiumDriver;
import java.net.MalformedURLException;
import java.net.URL;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
public class FindById {
public static void main(String args[]) throws MalformedURLException {
DesiredCapabilities cap =
new DesiredCapabilities();
cap.setCapability(CapabilityType.BROWSER_NAME,
"");
cap.setCapability(
"platformName", "Android");
cap.setCapability(
"deviceName", "Android Emulator");
cap.setCapability(
"platformVersion", "4.4");
cap.setCapability(
"appPackage", "com.android.calculator2");
cap.setCapability(
"appActivity", ".Calculator");
AppiumDriver dr =
new AppiumDriver(new URL("http://127.0.0.1:4723/wd/hub"), cap);
dr.findElement(By.id(
"com.android.calculator2:id/digit9")).click();
}
}
|
如果读者对webdriver很熟悉的话,那么掌握这个方法是非常简单的。如果对webdriver不熟悉,那么可以参考乙醇的webdriver实用指南,先学习一下webdriver的基础知识。
这一节我们写了一些脚本去进行控件定位,在实际的项目中,这些没有任何断言的脚本是基本上无法完成测试用例的功能的。
先卖个关子,下下一节乙醇将会带大家写第一个appium的测试用例。
那么下一节我们将学习如何使用class name进行定位。