Appium

Appium是一个支持原生,混合和移动web apps的开源的跨平台测试框架工具

文档网站

支持平台

为什么选择appium ?

  1. 你不需要以任何方式重新编译或者修改你的app,就可以在所有的平台上使用标准的自动化APIs
  2. 你可以用你喜欢的开发工具使用任何 WebDriver 兼容的语言来编写测试用例.比如 Java, Objective-C, JavaScript with Node.js (in both callback and yield-based flavours), PHP, Python, Ruby, C#, Clojure, 或者 Perl 可以使用标准的Selenium WebDriver API和特定语言的客户端库.
  3. 你可以使用任何测试框架.

依托 WebDriver 意味着你可以押宝在一个已经成为事实上标准的独立,自由和开放的协议.而不会被限制在任何的专利中

如果在没有使用Appium的情况,你使用了Apple的UIAutomation库就只能通过Javascript,并且只能通过Instruments application插桩应用来运行你的测试. 同样的,在Google的UiAutomator体系下,你只能用Java写你的测试案例. Appium最终开启了跨平台原生移动自动化的可能.

依赖

你的环境需要配置好运行测试相关的特定移动平台,下面列出相关的依赖平台 如果你想通过npm install来运行appium, 为Appium研究和贡献力量.你需要node.js and npm 0.8 或者更高版本 (brew install node).

你可以使用 appium-doctor 来验证 Appium 的所有依赖。运行 appium-doctor,然后提供 --ios 或者 --android 参数来验证两个平台的依赖是否配置正确。如果从源代码运行,你可以使用 bin/appium-doctor.js 或者 node bin/appium-doctor.js

IOS依赖

android依赖

FirefoxOS Requirements

快速开始

启动Appium server,并运行用你喜欢的 WebDriver 兼容的语言编写的测试用例. 你可以用node.js或者下面的应用程序来运行Appium

使用Node.js

$ npm install -g appium $ appium &

使用app

为Appium编写测试

我们支持 Selenium WebDriver JSON Wire Protocol 的一个子集 首先还需要指定特定移动平台相关的 desired capabilities 来通过appium运行你的测试

你可以通过 WebDriver 的元素定位策略的一个子集来定位元素 更多信息请参考 finding elements

我们也对 JSON Wire Protocol for automating mobile gestures 做了一些扩展以支持像 tap, flick, 和 swipe 这样的动作(松开,按压,滑动等手机手势)

你也可以在混合模式下自动化你的用HTML5构建的Web页面 hybrid app guide 这个代码地址包含了 很多不同语言的测试例子!

想了解全部的Appium的文档页面,请访问 这个目录.

工作原理

Appium通过多种原生自动化框架来提供基于Selenium的 WebDriver JSON wire protocol api

Appium驱动Apple的UIAutomation库提供IOS支持. UIAutomation基于Dan Cuellar’s

Android支持上, 在新版本的Android使用了Uiautomator框架,老版本的android上使用了 Selendroid

FirefoxOS的支持依赖一个基于Gecko平台并且兼容WebDriver的自动化驱动Marionette,不过暂不翻译了.因为暂时用不到

如何贡献代码

可以看下我们的文档 contribution documentation 以了解如何从源代码中进行编译,测试和运行

其他项目的授权和灵感来源

Credits

邮件列表

声明和公告经常放到讨论组 Discussion Group, 需要注册

问题定位

我们增加了一个 问题定位指南. 如果你遇到一些问题,请看下这个问的那个.它包含了一些常见的错误说明,以及在无法解决的情况如何和社区联系

使用Robots扩展

可以使用appium的一些robots扩展.或者其他的robots.想了解更多可以看看 Appium Robots

翻译工作

文档翻译工作由testerhome在推动, 我们会不断补充更多的文档和测试用例. 欢迎对Appium感兴趣的同学加入我们, 为开源社区贡献中国人的力量.

贡献者

来自 testerhome 的:

Intel® 硬件加速器管理

如果你发现android虚拟机太慢, 并且你的系统运行在Intel® 的cpu上. 那么你可以尝试下HAXM, HAXM能够让你充分利用硬件虚拟化技术来加速android模拟器

Android Coverage

Android 覆盖率需要使用模拟器或者 root 过的机器。使用 instrument target 构建应用,比如 $ ant instrument

androidCoverage 传给你设备的 capabilities,并设置为你的 instrument 的 class。 比如

caps = { androidCoverage: 'com.example.pkg/com.example.pkg.instrumentation.MyInstrumentation' }

Appium 会用类似的命令启动应用:adb shell am instrument -e coverage true -w com.example.pkg/com.example.pkg.instrumentation.MyInstrumentation

当你的测试完成时,我们就可以结束覆盖率收集,然后将 coverage.ec 文件从设备里取出来。

mobile :endCoverage, intent: 'com.example.pkg.END_EMMA', path: '/mnt/sdcard/coverage.ec'

AndroidManifest.xml 里定义 instrumentation 和 broadcast 接收器。

    <instrumentation
        android:name="com.example.pkg.instrumentation.MyInstrumentation"
        android:targetPackage="com.example.pkg" >
    </instrumentation>

    <!-- adb shell am broadcast -a com.example.pkg.END_EMMA -->
    <receiver android:name="com.example.pkg.instrumentation.EndEmmaBroadcast" >
       <intent-filter>
           <action android:name="com.example.pkg.END_EMMA" />
       </intent-filter>
    </receiver>

再定义 EndEmmaBroadcast.java

package com.example.pkg.instrumentation;

import java.io.File;

import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Process;
import android.util.Log;

// adb shell am broadcast -a com.example.pkg.END_EMMA
@SuppressLint("SdCardPath")
public class EndEmmaBroadcast extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d("AppiumEmma", "EndEmmaBroadcast broadcast received!");
        // reflection is used so emma doesn't cause problems for other build targets
        // that do not include emma.
        try {
            Class.forName("com.vladium.emma.rt.RT")
                    .getMethod("dumpCoverageData", File.class, boolean.class, boolean.class)
                    .invoke(null, new File("/mnt/sdcard/coverage.ec"), false, false);
        } catch (Exception e) {
            Log.d("AppiumEmma", e.toString());
        }

        // once coverage is dumped, the processes is ended.
        Process.killProcess(Process.myPid());
    }
}

定义 MyInstrumentation.java

package com.example.pkg.instrumentation;

import android.app.Instrumentation;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;

public class MyInstrumentation extends Instrumentation {
    private Intent intent;

    @Override
    public void onCreate(Bundle arguments) {
        intent = getTargetContext().getPackageManager()
                .getLaunchIntentForPackage("com.example.pkg")
                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        start(); // creates new thread which invokes onStart
    }

    @Override
    public void onStart() {
        startActivitySync(intent);
        LocalBroadcastManager.getInstance(getTargetContext()).registerReceiver(
                new EndEmmaBroadcast(), new IntentFilter("com.example.pkg.END_EMMA"));
    }
}

报表

ant instrument 命令会生成一个 coverage.em 文件。你可以使用 mobile :endCoverage 命令来可以下载 某次运行的 coverage.ec 文件。 注意:你可以有很多个 coverage.ec 文件。你可以用下面的命令将他们合并起来:

java -cp /path/to/android-sdk-macosx/tools/lib/emma_device.jar emma report -r html -in coverage.em,coverage0.ec,coverage1.ec -sp /path/to/your-app/src

Appium 客户端库

Appium 有以下语言的库:

语言 代码
Ruby GitHub
Python GitHub
Java GitHub
JavaScript GitHub
PHP GitHub
C# GitHub

注意有些方法,比如 endTestCoverage()complexFind() 目前还没有什么用。 只有这个问题修复, 完整的覆盖率支持才会添加。 一旦这个问题修复,complexFind() 将被移除。 如果你一定想要用这些方法,请查看相应的文档。

锁定

锁定屏幕

# ruby
lock 5
# python
driver.lock(5)
// java
driver.lockScreen(3);
todo: javascript
todo: php
// csharp
driver.LockDevice(3);

将 app 置于后台

把当前应用放到后台去

# ruby
background_app 5
# python
driver.background_app(5)
// java
driver.runAppInBackground(5);
todo: javascript
todo: php
// csharp
driver.BackgroundApp(5);

收起键盘(iOS only)

在 iOS 上收起键盘

# ruby
hide_keyboard
# python
driver.hide_keyboard()
// java
driver.hideKeyboard();
todo: javascript
todo: php
// csharp
driver.HideKeyboard("Done");

是否已经安装

检查应用是否已经安装

# ruby
is_installed? "com.example.android.apis"
# python
driver.is_app_installed('com.example.android.apis')
// java
driver.isAppInstalled("com.example.android.apis")
todo: javascript
todo: php
// csharp
driver.IsAppInstalled("com.example.android.apis-");

安装应用

安装应用到设备中去

# ruby
install 'path/to/my.apk'
# python
driver.install_app('path/to/my.apk')
// java
driver.installApp("path/to/my.apk")
todo: javascript
todo: php
//csharp
driver.InstallApp("path/to/my.apk");

删除应用

从设备中删除一个应用

# ruby
remove 'com.example.android.apis'
# python
driver.remove_app('com.example.android.apis')
// java
driver.removeApp("com.example.android.apis")
todo: javascript
todo: php
// csharp
driver.RemoveApp("com.example.android.apis");

摇晃

模拟设备摇晃

# ruby
shake
# python
driver.shake()
// java
driver.shake()
todo: javascript
todo: php
// csharp
driver.ShakeDevice();

关闭应用

关闭应用

# ruby
close_app
# python
driver.close_app();
// java
driver.closeApp()
todo: javascript
todo: php
// csharp
driver.CloseApp();

启动

启动应用

# ruby
launch
# python
driver.launch_app()
// java
driver.launchApp()
todo: javascript
todo: php
// csharp
driver.LaunchApp();

重置

应用重置

# ruby
reset
# python
driver.reset()
// java
driver.resetApp()
todo: javascript
todo: php
// csharp
driver.ResetApp();

可用上下文

列出所有的可用上下文

# ruby
context_array = available_contexts
# python
driver.contexts
// java
driver.getContextHandles()
todo: javascript
todo: php
// csharp
driver.GetContexts()

当前上下文

列出当前上下文

# ruby
context = current_context
# python
driver.current_context
// java
driver.getContext()
todo: javascript
todo: php
// csharp
driver.GetContext()

切换到默认的上下文

将上下文切换到默认上下文

# ruby
switch_to_default_context
# python
driver.switch_to.context(None)
// java
driver.context();
todo: javascript
todo: php
// csharp
driver.SetContext();

应用的字符串

iOS 里是 Localizable.strings Android 里是 strings.xml

# ruby
strings = app_strings
# python
driver.app_strings
// java
driver.getAppString();
todo: javascript
todo: php
// csharp
driver.GetAppStrings();

按键事件

发送一个按键事件给设备

# ruby
key_event 176
# python
driver.keyevent(176)
// java
driver.sendKeyEvent(AndroidKeyCode.HOME);
todo: javascript
todo: php
// csharp
driver.KeyEvent("176");

当前 Activity

Android only 得到当前 activity。

# ruby
current_activity
# python
driver.current_activity
// java
driver.currentActivity();
todo: javascript
todo: php
// csharp
driver.GetCurrentActivity();

触摸动作 / 多点触摸动作

An API for generating touch actions. This section of the documentation will be expanded upon soon.

生成触摸动作的接口。这部分文档很快将会补充更多的内容进来。

# ruby
touch_action = Appium::TouchAction.new
element  = find_element :name, 'Buttons, Various uses of UIButton'
touch_action.press(element: element, x: 10, y: 10).perform
# python
action = TouchAction(driver)
action.press(element=el, x=10, y=10).release().perform()
// java
TouchAction action = new TouchAction(driver)
.press(mapview, 10, 10)
.release().
perform();
todo: javascript
todo: php
// csharp
var touchAction1 = new TouchActions(this);
touchAction1.Down(10, 10).Up(10, 10);

var multiTouchAction = new MultiTouchAction(this);
multiTouchAction.Add(touchAction1);

PerformMultiTouchAction(multiTouchAction);

滑动

模拟用户滑动

# ruby
swipe start_x: 75, start_y: 500, end_x: 75, end_y: 0, duration: 0.8
# python
driver.swipe(75, 500, 75, 0, 0.8)
// java
driver.swipe(startx=75, starty=500, endx=75, endy=0, duration=800)
todo: javascript
todo: php
todo: c#

Pinch

Places two fingers at the edges of the screen and brings them together. 在 0% 到 100% 内双指缩放屏幕,

# ruby
pinch 75
# python
driver.pinch(element=el)
// java
driver.pinch(element);
todo: javascript
todo: php
// csharp
driver.Pinch(25, 25)

Zoom

放大屏幕 在 100% 以上放大屏幕

# ruby
zoom 200
# python
driver.zoom(element=el)
// java
driver.zoom(element);
todo: javascript
todo: php
# csharp
driver.Zoom(100, 200);

拉出文件

从设备中拉出文件

# ruby
pull_file 'Library/AddressBook/AddressBook.sqlitedb'
// python
driver.pull_file('Library/AddressBook/AddressBook.sqlitedb')
// java
driver.pullFile("Library/AddressBook/AddressBook.sqlitedb");
todo: javascript
todo: php
// csharp
driver.PullFile("Library/AddressBook/AddressBook.sqlitedb");

推送文件

推送文件到设备中去

# ruby
data = "some data for the file"
path = "/data/local/tmp/file.txt"
push_file path, data
# python
data = "some data for the file"
path = "/data/local/tmp/file.txt"
driver.push_file(path, data.encode('base64'))
// java
byte[] data = Base64.encodeBase64("some data for the file".getBytes());
String path = "/data/local/tmp/file.txt";
driver.pushFile(path, data)
todo: javascript
todo: php
// csharp
driver.PushFile("/data/local/tmp/file.txt", "some data for the file");

Appium 桌面应用

Appium 的桌面应用支持 OS X 和 Windows.

列出 Appium 服务器支持的客户端库

安装教程请参见各自库的文档。

Appium 服务关键字

关键字 描述 实例
app .ipa or .apk文件所在的本地绝对路径或者远程路径,也可以是一个包括两者的.zip. Appium会先尝试安装路径对应的应用在适当的真机或模拟器上.也可以是一个chrome或者chromium,这样就会在android系统中其中chrome或chromium,也可以是safari会启动ios上的safari. 针对Android系统,如果你指定app-packageapp-activity(具体见下面)的话,那么就可以不指定app. 比如/abs/path/to/my.apkhttp://myapp.com/app.ipa, Android上的chrome, chromium, iOS的safari
browserName 考虑到Selenium的兼容性,必须要使用'';要启动的浏览器的名称 比如chromesafari
device 要使用的模拟器或真机的类型名称 比如ios, selendroid, firefoxos, mock_ios, android
version Android API版本, iOS版本, Chrome/Safari版本 (Android)4.2/4.3(ios) 6.0/6.1/7.0
newCommandTimeout 设置在接受到有效命令之前结束进程并退出的时间 比如. 60
launch 在Appium已经安装的情况下可自动启动app。默认是true true, false

Android特有

关键字 描述 实例
app-activity 你要启动的Android 应用对应的Activity名称 比如MainActivity, .Settings
app-package 你想运行的Android应用的包名 比如com.example.android.myApp, com.android.settings
app-wait-activity 你想要等待启动的Android Activity名称 比如SplashActivity
device-ready-timeout 设置一个模拟器或真机准备就绪的时间 比如5

iOS特有

关键字 描述 实例
calendarFormat 为iOS的模拟器设置日历格式 比如. gregorian公历
deviceName iOS Simulator 的设备名 比如. iPhone Retina (3.5-inch)
language 为iOS的模拟器设置系统语言 比如. fr法语
launchTimeout 在Appium运行失败之前设置一个等待instruments的时间 比如. 20000毫秒
locale 为iOS模拟器进行区域设置 比如. fr_CA法语

名单

以下这些项目,鼓舞了我们,成就了 Appium。

元素定位与交互

Appium支持一个Webdriver元素定位方法的子集

标签名抽象映射

你可以使用UIAutomation的控件类型用于标签名, 或者使用简化的映射, 可以参考下如下例子 https://github.com/appium/appium/blob/master/lib/uiauto/lib/mechanic.js#L29

(官方文档外的补充) 对于Android下的元素对应, 可以参考 https://github.com/appium/appium/blob/master/lib/devices/android/bootstrap/src/io/appium/android/bootstrap/AndroidElementClassMap.java

例子

找到屏幕上所有的UIAButtons

WD.js:

driver.elementsByTagName('button', function(err, buttons) {
  // tap all the buttons
  var tapNextButton = function() {
    var button = buttons.shift();
    if (typeof button !== "undefined") {
      button.click(function(err) {
        tapNextButton();
      })
    } else {
      driver.quit();
    }
  }
  tapNextButton();
});

Ruby:

buttons = @driver.find_elements :tag_name, :button
buttons.each { |b| b.click }

Python:

[button.click() for button in driver.find_elements_by_tag_name('button')]

找到所有文本内容(或者accessibilityIdentifier)为"Go"的元素

WD.js:

driver.elementByName('Go', function(err, el) {
  el.tap(function(err) {
    driver.quit();
  });
});

Ruby:

@driver.find_element(:name, 'Go').click

Python:

driver.find_element_by_name('Go').click()

找到以“Hi, "开头的导航条元素

WD.js:

driver.elementByXpath('//navigationBar/text[contains(@value, "Hi, ")]', function(err, el) {
  el.text(function(err, text) {
    console.log(text);
    driver.quit();
  });
});

Ruby:

@driver.find_element :xpath, '//navigationBar/text[contains(@value, "Hi, ")]'

通过tagName查找元素

Java:

driver.findElement(By.tagName("button")).sendKeys("Hi");

WebELement element = findElement(By.tagName("button"));
element.sendKeys("Hi");

List<WebElement> elems = findElements(By.tagName("button"));
elems.get(0).sendKeys("Hi");

Python:

driver.find_elements_by_tag_name('tableCell')[5].click()

FindAndAct

你也可以通过一行命令来完成元素的查找和交互(只适用于IOS) 举个例子, 你可以通过一次调用来实现查找一个元素并点击它, 使用的命令是mobile: findAndAct

Python:

args = {'strategy': 'tag_name', 'selector': 'button', 'action': 'tap'}
driver.execute_script("mobile: findAndAct", args)

用一个滑动手势进行下拉刷新

Python:

js_snippet = "mobile: swipe"
args = {'startX':0.5, 'startY':0.2, 'startX':0.5, 'startY':0.95, 'tapCount':1, 'duration':10}
driver.execute_script(js_snippet, args)

备注: driver.execute_script() 可以在 Automating Mobile Gestures: Alternative access method) 找到说明

使用Appium Inspector来定位元素

(翻译备注: 这个工具目前只有Mac版本, 如果你使用的是windows, 可以使用android自带的traceview工具来获得元素的位置)

Appium提供了一个灵活的工具Appium Inspector, 允许你在app运行的时候, 直接定位你正在关注的元素. 通过Appium Inspector(靠近start test按钮的i标示文本), 你可以通过点击预览窗口上的控件来获得它的name属性, 或者直接在UI导航窗口中定位

概述

Appium Inspector有一个简单的布局, 全部由如下窗口组成. UI导航器, 预览, 录制与刷新按钮, 和交互工具

Step 1

例子

启动Appium Inspector后, (通过点击app右上的小"i"按钮), 你可以定位任何预览窗口中的元素. 作为测试, 我正在查找id为"show alert"的按钮

Step 1

要找到这个按钮的id, 在定位预览窗口中我点击了"show alert"按钮, Appium Inspector在UI导航窗口中高亮显示了这个元素, 然后展示了刚被点击按钮的id和元素类型

Step 1

Selenium Grid

使用 ”–nodeconfig" 服务器参数,你可以在本地 selenium grid 里注册你的 appium 服务器。

> node . -V --nodeconfig /path/to/nodeconfig.json

在 node 的配置文件里,你需要定义 “browserName”“version”“platform”。 基于这些参数,selenium grid 会将你的测试定向到正确的设备上去。你还需要配置你的 host 详细信息和 selenium grid 的详细信息。你可以在 这里 找到详细的参数列表和描述信息。

一旦你启动了 appium 服务器并且在 grid 里注册了信息,你会在 grid 控制台发现你的设备:

“http://<grid-ip-adress>:<grid-port>/grid/console”

Grid 配置文件例子

{
  "capabilities":
      [
        {
          "browserName": "<e.g._iPhone5_or_iPad4>",
          "version":"<version_of_iOS_e.g._6.1>",
          "maxInstances": 1,
          "platform":"<platform_e.g._MAC_or_ANDROID>"
        }
      ],
  "configuration":
  {
    "cleanUpCycle":2000,
    "timeout":30000,
    "proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
    "url":"http://<host_name_appium_server_or_ip-address_appium_server>:<appium_port>/wd/hub",
    "maxSession": 1,
    "register": true,
    "registerCycle": 5000,
    "hubPort": <grid_port>,
    "hubHost": "<Grid_host_name_or_grid_ip-address>"
  }
}

可以在 这里查看有效的 platform 参数。

Appium grunt 命令

Grunt 是 Node.js 的 Make! 我们用它来自动化所有的 appium 开发任务。 下面就是你能做的:

任务 描述
grunt lint 运行 JSLint
grunt test 运行所有的测试
grunt functional 运行整个功能测试集
grunt ios 运行 iOS 功能测试集
grunt android 运行 Android 功能测试集
grunt selendroid 运行 selendroid 功能测试集
grunt firefoxos 运行 firefoxos 功能测试集
grunt unit 运行所有的单元测试
grunt buildApp:<AppName>:<SDK> 构建一个用于 iPhone 模拟器的 iOS 应用。 我们预计这个应用的路径是 sample-code/apps/<AppName>/build/Release-iphonesimulator/<AppName>.app. 默认的 SDK 是 ‘iphonesimulator6.0’
grunt signApp:<certName> 使用开发证书的绝对路径,签名测试应用。
grunt authorize 授权模拟器,使它不需要弹框请求权限。
grunt log 打印 appium.log (运行测试的时候很有用)
grunt configAndroidBootstrap 配置使用 ant 构建 Android 的 bootstrap.jar
grunt buildAndroidBootstrap 使用 ant 构建 bootstrap.jar
grunt buildSelendroidServer 构建 selendroid 服务器
grunt configAndroidApp:<AppName> 配置使用 ant 构建 android 测试应用。 我们期待有一个 sample-code/apps/<AppName> 的 Android 项目
grunt buildAndroidApp:<AppName> 使用 ant 构建项目. 会在 sample-code/apps/<AppName> 下生成应用。
grunt installAndroidApp:<AppName> 将安卓应用安装到模拟器和设备中去
grunt docs 生成文档
grunt generateAppiumIo 将 README.md 转成 appium.io 的 getting-started.html
grunt setConfigVer:<device> 将 package.json 里面 appium 的版本号和对应设备写入 .appiumconfig.json 文件

其他

grunt buildApp 默认使用 iPhone 6.1 模拟器的 SDK 来构建应用。你可以传其他的 SDK 给 grunt 命令。 (用 xcodebuild -showsdks 找出你所有的 sdk):

> grunt buildApp:UICatalog:iphonesimulator6.0

如何写文档

# 用于第一级标题。每个文档必须以第一级标题开头。 不要使用 === 底线方法来创建标题。

第二级标题

## 用于第二级标题。 不要使用 --- 底线方法来创建第二级标题标题。

普通标题

### 用于不会出现在目录中的标题。 不要使用第四级标题 ####, 第五级标题 #####, 或者第六级标题 ######

分隔线

不要使用分隔线例如 -- 或者 ---。 这会使 Slate 混乱.

链接

链接到 readme:

[readme](#README.md)

链接到 contributing:

[contributing](#CONTRIBUTING.md)

链接到其他文档:

[link text](#filename.md)

链接到文档的内部, 使用 # 来标记 Slate 链接.

[go direct to json](#json-wire-protocol-server-extensions)

需要注意的是当标题改变时,哈希链接会损坏。所以链接到文档的开头是最好的( other.md 替换 other.md#something )。

自动化混合应用

Appium 其中一个理念就是你不能为了测试应用而修改应用。为了符合这个方法学,我们可以使用 Selenium 测试传统 web 应用的方法来测试混合 web 应用 (比如,iOS 应用里的元素 “UIWebView” ),这是有可能的。这里会有一些技术性的复杂,Appium 需要知道你是想测试原生部分呢还是web部分。幸运的是,我们还能遵守 WebDriver 的协议。

自动化混合 iOS 应用

在你的 Appium 测试里,你需要以下几步来和 web 页面交涉:

  1. 前往到应用里 web 视图激活的部分。
  2. 调用 GET session/:sessionId/window_handles
  3. 这会返回一个我们能访问的 web 视图的 id 的列表。
  4. 使用你想访问的这个 web 视图的 id 作为参数,调用 POST session/:sessionId/window
  5. (这会将你的 Appium session 放入一个模式, 在这个模式下,所有的命令都会被解释成自动化web视图而不是原生的部分。比如,当你运行 getElementByTagName,它会在 web 视图的 DOM 上操作,而不是返回 UIAElements。当然,一个 Webdriver 的方法只能在一个上下文中有意义,所以如果在错误的上下文,你会收到错误信息。)
  6. 如果你想停止 web 视图的自动化,回到原生部分,你可以简单地使用 execute_script 调用 "mobile: leaveWebView" 方法来离开 web 层。

在 iOS 真机上运行

appium 使用一个远程调试器建立连接来实现和 web 视图的交互。当在模拟器上执行下面例子的时候,我们可以直接建立连接,因为模拟器和 appium 服务器在同一台机器上。

当在真机上运行用例时,appium 无法直接访问 web 视图,所以我们需要通过 USB 线缆来建立连接。我们使用 ios-webkit-debugger-proxy建立连接。

使用 brew 安装最新的 ios-webkit-debug-proxy。在终端运行一下命令:

# 如果你没有安装 brew 的话,先安装 brew。
> ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go/install)"
> brew update
> brew install ios-webkit-debug-proxy

你也可以通过 git 克隆项目来自己安装最新版本:

# Please be aware that this will install the proxy with the latest code (and not a tagged version).
> git clone https://github.com/google/ios-webkit-debug-proxy.git
> cd ios-webkit-debug-proxy
> ./autogen.sh
> ./configure
> make
> sudo make install

一旦安装好了, 你就可以启动代理:

# 将udid替换成你的设备的udid。确保端口 27753 没有被占用
# remote-debugger 将会使用这个端口。
> ios_webkit_debug_proxy -c 0e4b2f612b65e98c1d07d22ee08678130d345429:27753 -d

注意: 这个 ios-webkit-debug-proxy 需要 “web inspector” 打开着以便建立连接。在 settings > safari > advanced 里打开它。请注意 web inspector 在 iOS6 时候加入的 以前的版本没有。

Wd.js Code example

  // 假设我们已经有一个初始化好了的 `driver` 对象。
  driver.elementByName('Web, Use of UIWebView', function(err, el) { // 找到按钮,打开 web 视图
    el.click(function(err) { // 引导到 UIWebView
      driver.windowHandles(function(err, handles) { // 得到能访问的视图列表。
        driver.window(handles[0], function(err) { // 因为只有一个,所以选择第一个。
          driver.elementsByCss('.some-class', function(err, els) { // 通过 css 拿到元素。
            els.length.should.be.above(0); // 肯定有元素。
            els[0].text(function(elText) { // 得到第一个元素的文本。
              elText.should.eql("My very own text"); // 比较匹配文本。
              driver.execute("mobile: leaveWebView", function(err) { // 离开web视图上下文。
                // 如果你想的话,做一些原生应用的操作。
                driver.quit(); // 退出。
              });
            });
          });
        });
      });
    });
  });

Wd.java 代码例子

  //配置 webdriver 并启动 webview 应用。
  DesiredCapabilities desiredCapabilities = new DesiredCapabilities();
  desiredCapabilities.setCapability("device", "iPhone Simulator");
  desiredCapabilities.setCapability("app", "http://appium.s3.amazonaws.com/WebViewApp6.0.app.zip");  
  URL url = new URL("http://127.0.0.1:4723/wd/hub");
  RemoteWebDriver remoteWebDriver = new RemoteWebDriver(url, desiredCapabilities);

  // 切换到最新的web视图
  for(String winHandle : remoteWebDriver.getWindowHandles()){
    remoteWebDriver.switchTo().window(winHandle);
  }

  //在 guinea-pig 页面用 id 和 元素交互。
  WebElement div = remoteWebDriver.findElement(By.id("i_am_an_id"));
  Assert.assertEquals("I am a div", div.getText()); //验证得到的文本是否正确。
  remoteWebDriver.findElement(By.id("comments")).sendKeys("My comment"); //填写评论。

  //离开 webview,回到原生应用。
  remoteWebDriver.executeScript("mobile: leaveWebView");

  //关闭应用。
  remoteWebDriver.quit();

Wd.rb cucumber 的例子

TEST_NAME = "Example Ruby Test"
SERVER_URL = "http://127.0.0.1:4723/wd/hub"
APP_PATH = "https://dl.dropboxusercontent.com/s/123456789101112/ts_ios.zip"
capabilities =
    {
      'browserName' => 'iOS 6.0',
      'platform' => 'Mac 10.8',
      'device' => 'iPhone Simulator',
      'app' => APP_PATH,
      'name' => TEST_NAME
    }
@driver = Selenium::WebDriver.for(:remote, :desired_capabilities => capabilities, :url => SERVER_URL)

## 这里切换到最近一个窗口是因为在我们的例子里这个窗口一直是 webview。其他的用例里,你需要自己指定。
## 运行 @driver.window_handles,查看 appium 的日志,找出到底哪个窗口是你要的,然后找出相关的数字。
## 然后用 @driver.switch_to_window(number),切换过去。

Given(/^I switch to webview$/) do
  webview = @driver.window_handles.last
  @driver.switch_to.window(webview)
end

Given(/^I switch out of webview$/) do
  @driver.execute_script("mobile: leaveWebView")
end

# 你可以使用 CSS 选择器在你的 webview 里来选择元素

And(/^I click a webview button $/) do
  @driver.find_element(:css, ".green_button").click
end

用 ruby 调试 web 视图:

我在我的帮助类里创建了一个快速方法来定位web元素,无论它在哪一个窗口视图。 (这非常有帮助,特别是你的 webview 的 id 变化或者你用同一份代码来测试 Android 和 iOS。) https://gist.github.com/feelobot/7309729

自动化混合 Android 应用

Appium 通过 Chromedriver 内建混合应用支持。Appium 也可以使用 Selendroid 做为 4.4 之前的设备对 webview 支持的背部引擎。(你需要在 desired capability 里指定 "device": "selendroid")。然后:

  1. 前往你应用里 web 视图激活的部分。
  2. 用 “WEBVIEW” 做窗口句柄调用 POST session/:sessionId/window , 比如 driver.window("WEBVIEW")
  3. (这会将你的 Appium session 放入一个模式, 在这个模式下,所有的命令都会被解释成自动化web视图而不是原生的部分。比如,当你运行 getElementByTagName,它会在 web 视图的 DOM 上操作,而不是返回 UIAElements。当然,一个 Webdriver 的方法只能在一个上下文中有意义,所以如果在错误的上下文,你会收到错误信息。)
  4. 如果要停止web上下文里的自动化,回到原生部分的自动化,简单地使用 “NATIVE_APP” 调用 window 方法。比如 driver.window("NATIVE_APP")

注意:我们可以像上面说的,使用同样的策略。然而,Selendroid 使用 WEBVIEW/NATIVE_APP 窗口设置策略。 Appium 常规的混合支持也使用这种策略。

Wd.js 代码例子

// 假设我们已经初始化了一个 `driver` 实例。
driver.window("WEBVIEW", function(err) { // 选择唯一的 WebView
  driver.elementsByCss('.some-class', function(err, els) { // 通过 CSS 取得元素
    els.length.should.be.above(0); // 验证元素存在
    els[0].text(function(elText) { // 得到第一个元素的文本
      elText.should.eql("My very own text"); // 验证文本内容
      driver.window("NATIVE_APP", function(err) { // 离开 webview 上下文
        // 可以做些原生应用的测试
        driver.quit(); // 关闭 webdriver
      });
    });
  });
});

Wd.java 代码例子

  //配置 webdriver 并启动 webview 应用。
  DesiredCapabilities desiredCapabilities = new DesiredCapabilities();
  desiredCapabilities.setCapability("device", "Selendroid");
  desiredCapabilities.setCapability("app", "/path/to/some.apk");  
  URL url = new URL("http://127.0.0.1:4723/wd/hub");
  RemoteWebDriver remoteWebDriver = new RemoteWebDriver(url, desiredCapabilities);

  // 切换到最新的web视图
  remoteWebDriver.switchTo().window("WEBVIEW");

  //在 guinea-pig 页面用 id 和 元素交互。
  WebElement div = remoteWebDriver.findElement(By.id("i_am_an_id"));
  Assert.assertEquals("I am a div", div.getText()); //验证得到的文本是否正确。
  remoteWebDriver.findElement(By.id("comments")).sendKeys("My comment"); //填写评论。

  //离开 webview,回到原生应用。
  remoteWebDriver.switchTo().window("NATIVE_APP");

  //关闭应用。
  remoteWebDriver.quit();

Appium 介绍

Appium 是一个自动化测试开源工具,支持 iOS 平台和 Android 平台上的原生应用,web 应用和混合应用。

所谓的“移动原生应用”是指那些用 iOS 或者 Android SDK 写的应用。 所谓的“移动 web 应用”是指使用移动浏览器访问的应用(Appium 支持 iOS 上的 Safari 和 Android 上的 Chrome)。 所谓的“混合应用”是指原生代码封装网页视图 —— 原生代码和 web 内容交互。 比如,像 Phonegap,可以帮助开发者使用网页技术写应用,然后用原生代码封装,这些就是混合应用。

重要的是,Appium 是一个跨平台的工具:它允许测试人员使用同样的接口,基于不同的平台(iOS, Android)写自动化测试脚本。 这样大大增加了 iOS 和 Android 测试套件间代码的复用性。

想知道 Appium 如何支持平台,版本和自动化形态的详细信息,请参见platform support doc.

Appium 的理念

为了满足移动自动化需求, Appium 遵循着某种理念。这种理念重点体现于以下 4 个需求:

  1. 你无需为了自动化,而重新编译或者修改你的应用。
  2. 你不必局限于某种语言或者框架来写和运行测试脚本。
  3. 一个移动自动化的框架不应该在接口上重复造轮子。(移动自动化的接口应该统一)
  4. 无论是精神上,还是名义上,都必须开源。

Appium 设计

那么 Appium 架构是如何实现这个哲学呢?为了满足第一条,Appium 真正的工作引擎其实是第三方自动化框架。 这样,我们就不需在你的应用里植入 Appium 特定或者第三方的代码。这就意味着你在测试你将发布的应用。我们 使用以下的第三方框架:

为了满足第二点,我们把这些第三方框架封装成一套 API, WebDriver API. WebDriver (也就是 “Selenium WebDriver") 指定了客户端到服务端的协议。 (参见 JSON Wire Protocol)。 使用这种客户端-服务端的架构,我们可以使用任何语言来编写客户端,向服务端发送恰当的 HTTP 请求。 而且目前已经有大多数流行语言版本的客户端实现了。这也意味着你可以使用任何测试套件或者测试框架。客户端库就是简单的 HTTP 客户,可以以任何你喜欢的方式潜入你的代码。换句话说,Appium 和 WebDriver 客户端不是技术意义上的“测试框架”, 而是“自动化库”。你可以在你的测试环境中随意使用这些自动化库!

事实上 WebDriver 已经成为 web 浏览器自动化的标准,也成了 W3C 的标准 —— W3C Working Draft。 我们又何必为移动做一个完全不同的呢? 所以我们扩充了WebDriver 的协议, 在原有的基础上添加移动自动化相关的 API 方法,这也也满足了第三条理念。

第四条就不用说了,Appium 是开源的

Appium 概念

C/S 架构
Appium 的核心是一个 web 服务器,它提供了一套 REST 的接口。它收到客户端的连接,监听到命令, 接着在移动设备上执行这些命令,然后将执行结果放在 HTTP 响应中返还给客户端。事实上,这种客户端/服务端的 架构打开了许多可能性:比如我们可以使用任何实现了客户端的语言来写我们的测试代码。比如我们可以把服务端放在不同 的机器上。比如我们可以只写测试代码,然后使用像 Sauce Labs 这样的云服务来解释命令。

Session
自动化总是在一个 session 的上下文中运行。客户端初始化一个和服务端交互的 session。不同的语言有不同的实现方式, 但是最终都是发送一个附有 “desired capabilities” 的 JSON 对象参数的 POST 请求 “/session” 给服务器。 这时候,服务端就会开始一个自动化的 session,然后返回一个 session ID。 客户端拿到这个 ID,之后就用这个 ID 发送后续的命令。

Desired Capabilities
Desired capabilities 是一些键值对的集合 (比如,一个 map 或者 hash)。 客户端讲这些键值对发给服务端,告诉服务端我们想要启动怎样的自动化session。 根据不同的 capabilities 参数,服务端会有不同的行为。比如,我们可以把 platformName capability 设置为 iOS,告诉 Appium 服务端,我们想要一个 iOS 的 session,而不是一个 Android 的。我们也可以设置 safariAllowPopups capability 为 true, 确保在 Safari 自动化 session 中,我们可以使用 javascript 来打开新窗口。 参见 capabilities 文档,查看完整的 capabilities 列表。

Appium Server
Appium server 是用 nodejs 写的。我们可以用源码编译或者从 NPM 直接安装。

Appium 服务端

Appium 服务端有很多语言库 Java, Ruby, Python, PHP, JavaScript 和 C#,这些库都实现了 Appium 对 WebDriver 协议的扩展。当使用 Appium 的时候,你只需使用这些库代替常规的 WebDriver 库就可以了。 你可以从这里看到所有的库的列表。

Appium.app, Appium.exe

我们提供了 GUI 封装的 Appium server 下载。它封装了运行 Appium server 的所有依赖。 所以你需要担心 Node。而且这个封装包含了一个 Inspector 工具,可以让使用者检查应用的界面层级。 这样写测试用例的时候就非常方便了。

Getting Started

恭喜!你现在有足够的知识来使用 Appium 了。 来我们回到 getting started doc 继续了解更加 细节的需求和指南。

部署ios app 到手机上

准备在真机上执行appium测试, 需要如下准备:

  1. 用特殊的设备参数来构建app
  2. 使用 fruitstrap, 一个第三方程序,来部署你构建的app到手机上

Xcodebuild 命令的参数:

新的参数运行指定设置. 参考 developer.apple.com:

xcodebuild [-project projectname] [-target targetname ...]
             [-configuration configurationname] [-sdk [sdkfullpath | sdkname]]
             [buildaction ...] [setting=value ...] [-userdefault=value ...]

这有一个资料来参考可用的设置

CODE_SIGN_IDENTITY (Code Signing Identity)
    介绍: 标识符,指定一个签名.
    例如: iPhone Developer

PROVISIONING_PROFILE 已经从可用的的命令中消失了,但还是有必要设置的。

在xcodebuild命令中设置 "CODE_SIGN_IDENTITY” & “PROVISIONING_PROFILE”:

xcodebuild -sdk <iphoneos> -target <target_name> -configuration <Debug> CODE_SIGN_IDENTITY="iPhone Developer: Mister Smith" PROVISIONING_PROFILE="XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX"

成功的话, app会构建到如下目录 <app_dir>/build/<configuration>-iphoneos/<app_name>.app

用Fruitstrap进行部署

clone一个fruitstrap的fork版本在ghughes version ,这个已经不再维护. 已确认该fork可用unprompted fork, 但是其它的据说也可用.

clone成功的话, 执行 make fruitstrap 然后, 然后复制生成的 fruitstrap到app的所在的目录或上级目录下。

运行fruitstrap 通过输入以下命令 (命令是否可用依赖于你fork的 fruitstrap):

./fruitstrap -d -b <PATH_TO_APP> -i <Device_UDID>

如果是为了持续集成,你可以发现很有用的方法来记录fruitstrap命令行和日志文件中的记录, 像这样:

./fruitstrap -d -b <PATH_TO_APP> -i <Device_UDID> 2>&1 | tee fruit.out

在node服务启动前fruitstrap进行需要被结束, 一个方法是扫描fruitstrap的输出来得知app完成启动。 有一个有效的方法是通过一个Rakefile 和一个 go_device.sh 脚本:

bundle exec rake ci:fruit_deploy_app | while read line ; do 
   echo "$line" | grep "text to identify successful launch" 
   if [ $? = 0 ] 
   then 
   # Actions 
       echo "App finished launching: $line" 
       sleep 5 
       kill -9 `ps -aef | grep fruitstrap | grep -v grep | awk '{print $2}'` 
   fi
 done

一旦fruitstrap的进程被结束, node 服务就可以启动并且appium测试可以被执行!

下一步: 在真机上运行appium

iOS Predicate

I’d like to share my experience in using ’-ios uiautomation’ search strategy with Predicates. After using Instruments/UIAutomation tool for a long time I paid attention on the following methods in UIAutomation JavaScript API:

(UIAElement) UIAElementArray.firstWithPredicate(PredicateString predicateString)
(UIAElementArray) UIAElementArray.withPredicate(PredicateString predicateString)

And it turned out that native JS search strategy (powered by Apple) provides much more flexibility than I was thinking before, it is almost just like Xpath. Predicates can be used to restrict an elements set to select only those ones for which some condition is true.

For example:

appiumDriver.findElementsByIosUIAutomation("collectionViews()[0].cells().withPredicate(\"ANY staticTexts.isVisible == TRUE\")")

- will select only those UIACollectionCell elements that have visible UIAStaticText child elements, and themselves are childs of 1st UIACollectionView element that should be located under the main app window. Here staticTexts() and isVisible() are methods available in UIAElementArray and UIAElement classes respectively. Note that UIAElementArray numbering begins with 0 unlike Xpath where indexes counting starts from 1

Here’s a list of available Predicates (mostly taken from Predicates Programming Guide)

Basic Comparisons

= , == - The left-hand expression is equal to the right-hand expression: “`javascript tableViews()1.cells().firstWithPredicate("label == 'Olivia’ ”)

same in Xpath: /UIATableView2/UIATableCell@label = 'Olivia’ “`

>= , => - The left-hand expression is greater than or equal to the right-hand expression.

<= , =< - The left-hand expression is less than or equal to the right-hand expression.

> - The left-hand expression is greater than the right-hand expression.

< - The left-hand expression is less than the right-hand expression.

!= , <> - The left-hand expression is not equal to the right-hand expression.

BETWEEN - The left-hand expression is between, or equal to either of, the values specified in the right-hand side. The right-hand side is a two value array (an array is required to specify order) giving upper and lower bounds. For example, 1 BETWEEN { 0 , 33 }, or $INPUT BETWEEN { $LOWER, $UPPER }. In Objective-C, you could create a BETWEEN predicate as shown in the following example:

NSPredicate *betweenPredicate =
    [NSPredicate predicateWithFormat: @"attributeName BETWEEN %@", @[@1, @10]];

This creates a predicate that matches ( ( 1 <= attributeValue ) && ( attributeValue <= 10 ) )

Boolean Value Predicates

TRUEPREDICATE - A predicate that always evaluates to TRUE .

FALSEPREDICATE - A predicate that always evaluates to FALSE.

Basic Compound Predicates

AND , && - Logical AND.

OR , || - Logical OR.

NOT , ! - Logical NOT.

String Comparisons

String comparisons are by default case and diacritic sensitive. You can modify an operator using the key characters c and d within square braces to specify case and diacritic insensitivity respectively, for example firstName BEGINSWITH[cd] $FIRST_NAME

BEGINSWITH - The left-hand expression begins with the right-hand expression.

scrollViews()[3].buttons().firstWithPredicate("name BEGINSWITH 'results toggle' ")

same in Xpath: /UIAScrollView[4]/UIAButton[starts-with(@name, 'results toggle')][1]

CONTAINS - The left-hand expression contains the right-hand expression.

tableViews()[1].cells().withPredicate("ANY collectionViews[0].buttons.name CONTAINS 'opera'")

same in Xpath: /UIATableView[2]/UIATableCell[UIACollectionView[1]/UIAButton[contains(@name, 'opera')]]

ENDSWITH - The left-hand expression ends with the right-hand expression.

LIKE - The left hand expression equals the right-hand expression: ? and * are allowed as wildcard characters, where ? matches 1 character and * matches 0 or more characters. In Mac OS X v10.4, wildcard characters do not match newline characters.

tableViews()[0].cells().firstWithPredicate("name LIKE '*Total: $*' ")

same in Xpath: /UIATableView[1]/UIATableCell[matches(@name, '.*Total: \$.*')][1]

MATCHES - The left hand expression equals the right hand expression using a regex -style comparison according to ICU v3 (for more details see the ICU User Guide for Regular Expressions).

tableViews().firstWithPredicate("value MATCHES '.*of 7' ")

same in Xpath: /UIATableView[matches(@value, '.*of 7')][1]

Aggregate Operations

ANY , SOME - Specifies any of the elements in the following expression. For example ANY children.age < 18 .

tableViews()[0].cells().firstWithPredicate("SOME staticTexts.name = 'red'").staticTexts().withName('red')

same in Xpath: /UIATableView[1]/UIATableCell[UIAStaticText/@name = 'red'][1]/UIAStaticText[@name = 'red']

ALL - Specifies all of the elements in the following expression. For example ALL children.age < 18 .

NONE - Specifies none of the elements in the following expression. For example, NONE children.age < 18 . This is logically equivalent to NOT (ANY ...) .

IN - Equivalent to an SQL IN operation, the left-hand side must appear in the collection specified by the right-hand side. For example, name IN { 'Ben', 'Melissa', 'Matthew' } . The collection may be an array, a set, or a dictionary—in the case of a dictionary, its values are used.

array[index] - Specifies the element at the specified index in the array.

array[FIRST] - Specifies the first element in the array.

array[LAST] - Specifies the last element in the array.

array[SIZE] - Specifies the size of the array ”`javascript elements()[0].tableViews()[0].cells().withPredicate(“staticTexts[SIZE] > 2”)

same in Xpath: /*1/UIATableView1/UIATableCell[count(UIAStaticText) > 2] “`

Identifiers

C style identifier - Any C style identifier that is not a reserved word.

#symbol - Used to escape a reserved word into a user identifier.

[\]{octaldigit}{3} - Used to escape an octal number ( \ followed by 3 octal digits).

[\][xX]{hexdigit}{2} - Used to escape a hex number ( \x or \X followed by 2 hex digits).

[\][uU]{hexdigit}{4} - Used to escape a Unicode number ( \u or \U followed by 4 hex digits).

Literals

Single and double quotes produce the same result, but they do not terminate each other. For example, "abc" and 'abc' are identical, whereas "a'b'c" is equivalent to a space-separated concatenation of a, 'b', c.

FALSE , NO - Logical false.

TRUE , YES - Logical true.

NULL , NIL - A null value.

SELF - Represents the object being evaluated.

"text” - A character string.

'text’ - A character string.

Comma-separated literal array - For example, { 'comma', 'separated', 'literal', 'array' } .

Standard integer and fixed-point notations - For example, 1 , 27 , 2.71828 , 19.75 .

Floating-point notation with exponentiation - For example, 9.2e-5 .

0x - Prefix used to denote a hexadecimal digit sequence.

0o - Prefix used to denote an octal digit sequence.

0b - Prefix used to denote a binary digit sequence.

Reserved Words

The following words are reserved:

AND, OR, IN, NOT, ALL, ANY, SOME, NONE, LIKE, CASEINSENSITIVE, CI, MATCHES, CONTAINS, BEGINSWITH, ENDSWITH, BETWEEN, NULL, NIL, SELF, TRUE, YES, FALSE, NO, FIRST, LAST, SIZE, ANYKEY, SUBQUERY, CAST, TRUEPREDICATE, FALSEPREDICATE

Appium predicate helpers

Appium has helpers for predicate search in app.js:

Here’s a Ruby example:

# Ruby example
text = 'Various uses'
predicate = "name contains[c] '#{text}' || label contains[c] '#{text}' || value contains[c] '#{text}'"
element = execute_script(%Q(au.mainApp().getFirstWithPredicate("#{predicate}");))

puts element.name # Buttons, Various uses of UIButton

把appium 0.18.x上的测试用例集迁移到appium1.x上

Appium 1.0 已经从先前的版本中移除了一部分过时的特性, 这个指导文档会帮助你了解使用Appium 1.0需要做的具体改变.

新的客户端库

你需要关注的最大的改变是利用新的appium的client libraries来替换原生的WebDriver ciients. 访问Appium client list 去寻找符合你自己编程语言的客户端库吧. 在每个客户端的网站上都可以找到用于集成到你代码中的依赖库相关介绍和下载

基本上, 你需要做如下的改变(以Python作为例子)

from appium import webdriver

替换原来的:

from selenium import webdriver

新的适配参数

下面的适配参数将不再使用 * device * version

取而代之的是利用下面的配置参数

app 配置参数保持不变, 但是特指非浏览器的app, 如果你想使用类似Safari或者Chrome这样的浏览器, 你需要设置browserName. 这代表appbrowserName是互斥的.

我们把appium的配置参数都规范为驼峰拼写法(camelCase), 这代表着原来的app-package或者app-wait-activity现在会变成appPackageappWaitActivity. 当然目前android的app package和activity都已经是自动探测了, 大部分情况下你可以省略这两个配置项.

新的定位方式

我们已经移除了下面的定位方式

我们增加了accessibility_id定位方法去做过去name做的事情. 具体的细节还得跟你使用的Appium客户端库有关.

tag name已经被替换为class name. 所以想通过UI的类型来定位某个元素, 你需要使用class name定位方式

关于class namexpath的定位方式: 现在需要使用完整的全类名, 这意味着如果你有一个如下的定位用的xpath

//table/cell/button

现在需要改成

//UIATableView/UIATableCell/UIAButton

如果是android的话, button需要改变成android.widget.Button

我们也增加了如下的定位方式

根据你使用的客户端去相应的使用新的定位方式

使用xml, 不再是json了

App source方法先前返回JSON, 现在修改成返回XML. 所以如果你有代码是依赖解析app source的, 那么就需要更新

通过context支持混合应用, 不再是window了

以前混合app的切换支持是通过"windows"

现在Appium支持"context" 概念了, 要获得当前环境下所有的上下文(contexts), 或者特定的context, 你可以用

driver.contexts
current = driver.context

在这些context之间切换, 可以使用

driver.switch_to.context("WEBVIEW")

没有了execute_script("mobile: xxx")

所有的mobile:方法都已经被移除, 并且被替换为appium client libraries的原生方法. 这意味着如果一个方法调用原来的方式是 driver.execute("mobile: lock", [5]) 现在需要更新为 driver.lock(5) 在这个地方lock已经变成了原生的客户端方法. 当然具体的调用细节在不同的客户端库中的实现可能会有所差别.

特别需要注意的是, 手势(gesture)方法已经被替换为TouchAction / MultiAction API, 它允许更强大通用的组合手势的自动化. 可以参考你的客户端库的具体用法.

这就是全部啦, 祝迁移愉快

(文档由testerhome.com翻译, 欢迎更多热爱技术的同学加入到翻译中来, We Love Appium)

自动化手机网页应用

如果你正对于如何在iOS的Safari或Android上的Chrome做网页应用的自动化,那么Appium能够帮助你。你可以写一个最普通的WebDriver测试代码,就像使用Selenium服务一样使用Appium来满足需求。

iOS模拟器上的Safari浏览器

首先,我们需要先确认在你的Safari浏览器的设置中开启了开发者模式,这样Safari的远程调试端口也会被同时打开。

如果你打算在模拟器或真机上使用Appium的话,你必须先开发Safari。

然后设置如下显示的这些信息以便于在设备中的Safari执行测试:

{
  app: 'safari'
  , device: 'iPhone Simulator'
  , version: '6.1'
}

iOS真机上的Safari浏览器

为了能够在真机上的Safari执行测试,我们使用了SafariLauncher App来启动Safari。使用ios-webkit-webkit-proxy来自动启动Safari的远程调试功能。

提示: 目前针对iOS7版本的上,ios-webkit-debug-proxy有一个问题。a bug

前期设置

当你要在真机上的Safari中执行你的测试脚本之前你需要先注意以下几点: 安装并正常运行ios-webkit-debug-proxy(具体可以参考(shybrid docs) *打开iOS真机中的web inspector,可以在iOS6.0或更高版本中的设置 > safari > 高级找到。 *创建一个provisioning profile 能够帮助你配置safariLauncher. * 你可以前往Apple Developers Member Center 创建一个launcher profile: * 第一步: 创建一个新的App Id 同时设置WildCard App ID这个选项置为"“ * 第二步: 为步骤1的App Id创建一个new Development Profile . * 第三步: 选择你的certificate(s) and device(s) 并选择下一步. * 第四步: 设置profile的名称以及generate the profile. * 第五步: 下载profile并使用文本编辑器打开. * 第六步: 寻找并牢记你的UUID

现在你有了自己的profile文件,可以在终端中输入如下的命令:

$ git clone https://github.com/appium/appium.git
$ cd appium

# 选项1:你可以不设置任何的参数就可以设置ios开发者证书
$ ./reset.sh --ios --real-safari

# 选项2:你需要定义code signing identity并且允许xcode可选择profile identity code
$ ./reset.sh --ios --real-safari --code-sign '<code signing idendity>' 

#选项3:你需要设置<code signing idendity>和<profile identity code>
$ ./reset.sh --ios --real-safari --code-sign '<code signing idendity>' --profile '<retrieved profile identity code>'

#设置成功之后,就可以像往常一样启动服务
$ node /lib/server/main.js -U <UDID>

执行你的测试

如果要在safari下的运行你的测试, 只需要简单的配置app为safari即可

Java 举例

//setup the web driver and launch the webview app.
DesiredCapabilities desiredCapabilities = new DesiredCapabilities();
desiredCapabilities.setCapability("app", "safari");  
URL url = new URL("http://127.0.0.1:4723/wd/hub");
RemoteWebDriver remoteWebDriver = new RemoteWebDriver(url, desiredCapabilities);

// Navigate to the page and interact with the elements on the guinea-pig page using id.
remoteWebDriver.get("http://saucelabs.com/test/guinea-pig");
WebElement div = remoteWebDriver.findElement(By.id("i_am_an_id"));
Assert.assertEquals("I am a div", div.getText()); //check the text retrieved matches expected value
remoteWebDriver.findElement(By.id("comments")).sendKeys("My comment"); //populate the comments field by id.

//close the app.
remoteWebDriver.quit();

在真机或模拟器上的Chrome执行测试

需要做的准备:

接着,像这样设置就可以在Chrome上执行测试了:

{
  app: 'chrome'
  , device: 'Android'
};

Appium 支持的平台

Appium 支持很多的运行平台和测试方式(包括原生、混合应用、内嵌 浏览器、真机、模拟器等)。这篇文档主要用来让大家明确在使用 Appimu时候支持的平台版本和上述测试方式的必备条件。

iOS 平台支持

请移步到 Running on OS X: iOS 这里介绍了在iOS系统下使用Appium的必备条件和安装说明。

Android 平台

请移步至 Running on OS X: Android, Running on Windows, 或者 Running on Linux 获得在不同操作系统下android平台对appium的支 持和安装配置文档。

Appium在真机上

Appium已经初步支持真机测试。

如果要在真机上执行测试,你将要做如下准备:

1.一个苹果的开发者ID和有效的开发者对应的配置文件和签名文件

2.一台iPad或者iPhone

  1. 你要测试的应用的源码

  2. 一台安装了XCode和XCode Command Line Developer Tools的Mac机器

Provisioning Profile

要在真机上测试就需要一个有效的iOS开发者的Distribution Certificate and Provisioning Profile。你可以在这个上面找到配置这些的相关信息Apple documentation

同样的,你还需要对你的应用签名,更多的信息可以查看sign your app.

你必须使用Xcode的执行按钮来安装你的应用

使用Appium运行你的测试

一旦你的设备和应用设置好了之后,你就能够用如下的命令在你的机器上执行测试:

node . -U <UDID> --app <bundle_id>

这将会启动Appium并且开始在真机上测试应用。

疑问解答思路

  1. 确认UDID已经正确的在xcode organizar或itunes中设置了。很长的字符串(20多个字符串) 0.确认你测试代码中的测试对象设备的设置
  2. 再次确认你从instruments启动你的自动化测试
  3. 确认instruments已经关闭

在 Linux 上运行 Appium

限制

如果你在 Linux 上使用 Appium, 那么你没法使用已经构建好的 ’.app',那是为 OS X 准备的。 另外由于 Appium 在测试 iOS 应用时 依赖 OS X 特有的库, 所以你也没有办法测试在 Linux 上测试 iOS 应用。

配置

首先,安装版本高于或等于 0.8 的 nodejs。可以根据 instructions for your flavor of linux 进行安装。

安装好了 node.js 之后,安装 Android SDK。 你会需要运行 android adb 等工具,这些工具都在 SDK 里包含了, 你要做的是配置环境变量。当然你要确保你的 API level 大于等于 17。 你也需要使用 Ant 来构建 bootstrap jar 以便 Appium 使用它来测试 Android 应用。

最后, 设置 $ANDROID_HOME 为你的 Android SDK 的路径。比如, 你将 Android SDK 解压在 /usr/local/adt/, 那你就要将如下添加到你的 .bashrc.zshrc.bash_profile 等 shell 配置文件中去:

export ANDROID_HOME=”/usr/local/adt/sdk

现在你可以运行 Appium 了, 在你 checkout 出来的 Appium 目录里, 运行 .reset.sh --android, 它会帮助你安装好所有的依赖。

运行 Appium

运行测试前, 你需要启动一个 API Level 大于等于 17 的 Android 模拟器或者连接一个系统是 4.1 以上的 Android 真机。然后在 Appium 目录运行

node .

你可以在 server documentation 找到所有的命令行参数。

备注

在 Mac OS X 上使用 Appium

在 OS X 上, Appium 支持 iOS 和 Android 测试

系统配置 (iOS)

使用多种 iOS SDK 进行测试

Appium 使用苹果提供的 instruments 来启动 iOS 模拟器,默认它会使用当前安装的 Xcode 和该 Xcode 下安装好的最高版本的 iOS SDK。这就意味着如果你想测试 iOS 6.1, 但是你安装了 iOS 7.0, 那么 Appium 会强制使用 7.0 的模拟器。 唯一的方法就是安装多个Xcode,然后在安装不同的 SDK。然后在启动 Appium 前,切换到你要测试的特定的版本。

另外,我们发现 Xcode 5 上的 iOS 6.1 测试,会很慢而且不稳定。所以我们推荐,如果要在 6.1 及 6.1 以下版本的 iOS 上进行测试,请使用 Xcode 4.6.3。如果要在 iOS 7.0 上测试,请使用 Xcode 5。假设我们的 Xcode 5 在 /Applications/Xcode.app, Xcode 4.6 在 /Applications/Xcode-4.6.app,我们就可以用下面的命令来切换到 Xcode 4.6 来为 iOS 6.1 测试做准备。

sudo xcode-select -switch /Applications/Xcode-4.6.app/Contents/Developer/

如果要回到 Xcode 5 的话,我们再运行一次:

sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer/

系统配置 (Android)

在windows上运行appium

限制

如果你在windows上安装appium,你没法使用预编译专用于OS X的.app文件,你也将不能测试IOS apps,因为appium依赖OS X专用的库来支持IOS测试。这意味着你只能通过在mac上来运行IOS的app测试。这点限制挺大。

开始安装

  1. 安装nodejs 0.8版本及以上, 通过官方的安装程序来安装。

  2. 安装android的sdk包,(http://developer.android.com/sdk/index.html), 运行依赖sdk中的'android'工具。并确保你安装了Level17或以上的版本api。设置ANDROID_HOME系统变量为你的Android SDK路径,并把tools platform-tools两个目录加入到系统的Path路径里。因为这里面包含有一些执行命令

  3. 安装java的JDK,并设置JAVA_HOME 变量为你的JDK目录。

  4. 安装Apache Ant 或者直接使用Android Windows SDK自带的ant,地址在eclipse\plugins目录,你需要把这个目录加到你的系统PATH变量中

  5. 安装Apache Maven. 并且设置M2HOME和M2环境变量,把M2环境变量添加到你的系统PATH变量中。

  6. 安装Git. 确保你安装了windows下的Git,以便可以运行常用的command命令

现在,你已经下载安装了所有的依赖,开始运行 reset.bat

运行Appium

要在windows上运行测试用例,你需要先启动Android模拟器或者连接上一个API Level17以上的android真机。 然后在命令行运行appium node .

备注

最简略的安装方式

出于对官方文档的尊重,我按照原文翻译,如下介绍我的安装心得。官方提到的一些工具,其实并不需要安装。 下面介绍我已经测试过的安装和使用过程

安装appium

  1. 安装nodejs

2、使用npm安装appium,npm install -g appium

运行appium

启动appium,直接运行appium 即可。

更新appium

通过npm install -g appium 来更新appium即可

如果有任何疑问,欢迎到testerhome.com来交流

執行測試

測試前的準備 (iOS)

在模擬器上測試apps必須要用模擬器專用的編譯器,例如說使用下列的命令來編譯 Xcode 項目:

> xcodebuild -sdk iphonesimulator6.0

這行指令在Xcode項目底下創建了一個build/Release-iphonesimulator目錄,並且生成一個可以透過 Appium 服務器來交流的的.app封包。

如果需要,你可以把.app 目錄壓縮成一個zip壓縮檔!Appium 會自行解壓縮。讓你能方便在非本地運行Appium。

測試前的準備 (Android)

用Appium去執行你的.apk檔其實沒什麼特別需要注意的事項。如果需要,你可以把它壓縮成zip壓縮檔。

用Appium測試你的app (iOS)

目前最好的測試方式請參照測試範例:

Node.js | Python | PHP | Ruby | Java

基本上來說,首先先確定你啟動了Appium:

node . -V

然後執行你的WebDriver測試腳本,腳本必須包含下列的環境參數:

{
    device: 'iPhone Simulator',
    browserName: '',
    version: '6.1',
    app: myApp
}

在這個腳本集裡,myApp必須是下列其中之一:

在你選擇的WebDriver庫裡,設定remote session使用上述的環境參數然後使用端口 4723來連接本地服務器 (或著是使用你在Appium啟動時所設定的任意端口)。現在你已經設置完成了!

用Appium測試你的app (Android)

首先,先確定你有一個而且必須是只能一個Android模擬器或著設備連接著。如果你輸入adb devices,你應該只看到一個設備連接著。這將是Appium所用來測試的設備。當然,要連接一個設備,你需要準備好一個Android AVD (參考 系統設置 以了解更多). 如果Android SDK工具在你的路徑下,你可以簡單的執行:

emulator -avd <我的Avd名稱>

然後等android模擬器啟動。有時候,因為某些原因,adb會卡住。如果它沒有顯示任何的設備或其他故障,你可以使用下列指令來重啟:

adb kill-server && adb devices

現在,確認Appium已經啟動:

node .

然後執行你的WebDriver測試腳本,腳本必須包含下列的環境參數:

{
    device: 'Android',
    browserName: '',
    version: '4.2',
    app: myApp,
    'app-package': myAppPackage,
    'app-activity': myAppActivity
}

在這個腳本集裡,myApp必須是下列其中之一:

myAppPackage 必須是你的應用的java package,例如, com.example.android.myApp.

myAppActivity 必須是你的希望測試的Android activity, 例如, MainActivity.

在你選擇的WebDriver庫裡,設定remote session使用上述的環境參數然後使用端口 4723來連接本地服務器 (或著是使用你在Appium啟動時所設定的任意端口)。現在你已經設置完成了!

用Appium測試你的app (Android 設備 < 4.2, 以及混合app測試)

低於4.2版本的Android設備 (API Level 17) 沒有安裝Google 的用戶界面自動化框架/UiAutomator framework.下面的範例是早期Appium在這些設備上的測試方法。對於早期的設備以及使用混合模式(webview-based)製作的apps, Appium 包含了另一種自動化測試後端Selendroid.

要使用Selendroid, 只需要在之前提到的環境參數上稍作修改即可,把'Android’ 換成 'Selendroid’:

{
    device: 'Selendroid',
    browserName: '',
    version: '2.3',
    app: myApp,
    'app-package': myAppPackage,
    'app-activity': myAppActivity
}

這樣Appium就會使用 Selendroid 取代預設的測試會話。使用 Selendroid 的缺點是有時候它的API跟 Appium 非常不同。所以我們建議你在為你的舊設備或著混合app寫測試腳本之前先仔細的閱讀Selendroid 的說明文檔

Appium 服务器参数

使用方法: node . [标志]

所有的标志都是可选的,但是有一些标志需要组合在一起才能生效。

标志 默认值 描述 例子
--shell null 进入 REPL 模式
--app null iOS: 基于模拟器编译的 app 的绝对路径或者设备目标的 bundle_id; Android: apk 文件的绝对路径 --app /abs/path/to/my.app
--ipa null (IOS-only) .ipa 文件的绝对路径 --ipa /abs/path/to/my.ipa
-q, --quiet false 不输出具体日志
-U, --udid null 连接的物理实体机的 udid --udid 1adsf-sdfas-asdf-123sdf
-a, --address 0.0.0.0 监听的 ip 地址 --address 0.0.0.0
-p, --port 4723 监听的端口 --port 4723
-dp, --device-port 4724 (Android-only) 连接设备的端口号 --device-port 4724
-k, --keep-artifacts false (IOS-only) 保留 Instruments trace 目录
-r, --backend-retries 3 (iOS-only) 遇到 crash 或者 超时,Instrument 重新参试启动的次数。 --backend-retries 3
--session-override false 允许 session 覆盖 (冲突的话)
--full-reset false (iOS) 删除整个模拟器目录。 (Android) 通过卸载应用(而不是清楚数据)重置应用状态。在 Android 上,session 完成后也会删除应用。
--no-reset false session 之间不充值应用状态 (IOS: 不删除应用的 plist 文件; Android: 在创建一个新的 session 前不删除应用。)
-l, --pre-launch false 在第一个 session 前,预启动应用 (iOS 需要 –app 参数,Android 需要 –app-pkg 和 –app-activity)
-lt, --launch-timeout 90000 (iOS-only) 等待 Instruments 启动的时间
-g, --log null 将日志输出到指定文件 --log /path/to/appium.log
--log-timestamp false 在终端输出里显示时间戳
--log-no-colors false 不在终端输出中显示颜色
-G, --webhook null 同时发送日志到 HTTP 监听器 --webhook localhost:9876
--native-instruments-lib false (IOS-only) iOS 内建了一个怪异的不可能避免的延迟。我们在 Appium 里修复了它。如果你想用原来的,你可以使用这个参数。
--merciful, -m false 不运行强制关闭没有响应的 instruments 的监视进程
--app-pkg null (Android-only) 你要运行的apk的java 包。 (例如, com.example.android.myApp) --app-pkg com.example.android.myApp
--app-activity null (Android-only) 打开应用时,启动的 Activity 的名字(比如, MainActivity) --app-activity MainActivity
--app-wait-package false (Android-only) 你想等待的 Activity 的 包名。(比如, com.example.android.myApp) --app-wait-package com.example.android.myApp
--app-wait-activity false (Android-only) 你想等待的 Activity 名字(比如, SplashActivity) --app-wait-activity SplashActivity
--android-coverage false (Android-only) 完全符合条件的 instrumentation 类. 作为命令 adb shell am instrument -e coverage true -w 的 -w 的参数 --android-coverage com.my.Pkg/com.my.Pkg.instrumentation.MyInstrumentation
--avd null 要启动的 avd 的名字 --avd @default
--device-ready-timeout 5 (Android-only) 等待设备准备好的时间,以秒为单位 --device-ready-timeout 5
--safari false (IOS-Only) 使用 Safari 应用
--device-name null (IOS-Simulator-only) 待使用的 iOS 设备名字 --device-name iPhone Retina (4-inch)
--default-device, -dd false (IOS-Simulator-only) instruments 启动时使用默认的模拟器
--force-iphone false (IOS-only) 无论应用要用什么模拟器,强制使用 iPhone 模拟器
--force-ipad false (IOS-only) 无论应用要用什么模拟器,强制使用 iPad 模拟器
--language null (IOS-only) iOS 模拟器的语言 --language en
--locale null (IOS-only) iOS simulator 的区域 --locale en_US
--calendar-format null (IOS-only) iOS 模拟器的日历格式 --calendar-format gregorian
--orientation null (IOS-only) 初始化请求时,使用 LANDSCAPE 或者 PORTRAIT --orientation LANDSCAPE
--tracetemplate null (IOS-only) 指定 Instruments 使用的 .tracetemplate 文件 --tracetemplate /Users/me/Automation.tracetemplate
--show-sim-log false (IOS-only) 如果设置了, iOS 模拟器的日志会写到终端上来
--nodeconfig null 指定 JSON 格式的配置文件 ,用来在 selenium grid 里注册 appium --nodeconfig /abs/path/to/nodeconfig.json
-ra, --robot-address 0.0.0.0 robot 的 ip 地址 --robot-address 0.0.0.0
-rp, --robot-port -1 robot 的端口地址 --robot-port 4242
--selendroid-port 8080 用来和 Selendroid 交互的本地端口 --selendroid-port 8080
--chromedriver-port 9515 ChromeDriver运行的端口 --chromedriver-port 9515
--use-keystore false (Android-only) 设置签名 apk 的 keystore
--keystore-path /Users/user/.android/debug.keystore (Android-only) keystore 的路径
--keystore-password android (Android-only) keystore 的密码
--key-alias androiddebugkey (Android-only) Key 的别名
--key-password android (Android-only) Key 的密码
--show-config false 打印 Appium 服务器的配置信息,然后退出
--keep-keychains false (iOS) 当 Appium 启动或者关闭的时候,是否保有 keychains (Library/Keychains)

贡献者的代码风格指南

感谢你们对 Appium 的贡献! 这些是我们书写 javascript 代码时使用的基本原则。 请遵守这些,避免风格的来回修改,以便我们可以合并你的 pull 请求。 基本原则就是:让你的代码看起来和周围的代码一致

衍合(Rebasing)

每个 pull 请求中的提交(commits)必须包括 逻辑的变化。 如果有多个作者,确认每个作者有自己的提交。最好不要修改作者信息。 合并(merge)提交必须从 pull 请求中 rebase 。

检错(Linting)

所有的代码 (除了 bootstrap.js 的代码,它使用了 Apple 的私有方法) 必须通过 JSLint。 为了检查你的代码,你可以在 Appium 存储目录下,简单地运行 grunt lint。 如果你已创建一个新的 .js 文件,请确认它在 grunt.js 中被通配符覆盖,或者被专门添加。

边输入边检错你的代码是容易实现的,使得整个进程更加顺利。 我们喜欢 jshint, 因为它有与许多源代码编辑器的集成。 文件 .jshintrc 加入到仓库中,它的内容是:

{
  "laxcomma": true,
  "strict": true,
  "undef": true,
  "unused": true,
  "trailing": true,
  "node": true,
  "es5": true,
  "white": true,
  "indent": 2
}

在编辑代码时,这些定义了我们想要看到的警告类型。 浏览 网页 ,查看编辑器和平台列表,找到使你的编辑器自动化检错的设置方法。

风格注意点

试验风格:

在代码语义通顺和长度许可下,可以保持在同一行:

样例:

  driver.elementByTagName('el1').should.become("123")
    .nodeify(done);

  driver
    .elementsByTagName('el1').should.eventually.have.length(0)
    .nodeify(done);

或者使用缩进来提高代码的可读性:

h.driver
  .elementById('comments')
    .clear()
    .click()
    .keys("hello world")
    .getValue()
    .should.become("hello world")
  .elementById('comments')
    .getValue().should.become("hello world")
  .nodeify(done);

h.driver
  .execute("'nan'--")
    .should.be.rejectedWith("status: 13")
  .nodeify(done);        

Appium 故障调试

如果你遇到问题,先不要提交 ticket 到 github 或者发信去 appium-disscuss 邮件列表求教,这里教你如何处理。

一般问题

如果你从 Appium.app 运行的话 (通过 dmg 安装的)

如果你从源代码运行 Appium 的话

Android

IOS

Webview/Hybrid/Safari app 支持

FirefoxOS

告诉社区

如果你通过以上方法都不能解决问题,你可以做:

如果你可以确认你发现的是个bug,来给我们报bug吧。issue tracker

如果你的 Appium 无法正常工作,然后错误信息不够清晰,欢迎加入mailing list。 给大家发邮件询问,但是请包含以下信息:

Known Issues

Specific Errors

Action Error Resolution
Running reset.sh xcodebuild: error: SDK “iphonesimulator6.1” cannot be located 安装 iPhone 6.1 SDK 或者 使用单独的 SDK 构建 待测应用 比如: grunt buildApp:UICatalog:iphonesimulator5.1