Appium 的实践笔记

使用过的人都会吐槽这里面无数的坑,浪费了兄弟姐妹们大量的时间,但是总体来说能够达到自动化测试 UI 的目的,目前也是众多移动客户端测试最广泛流行的工具。当然现在 airtest,macaca 也非常流行,实际上他们的框架都差不多。

重要的参考文献:
Appium 的官方网站
TesterHome 的专栏
Github 地址
WebDriver 的地址

Appium 技术框架

Appium 是一个开源工具,用于自动化 iOS 手机 、 Android 手机和 Windows 桌面平台上的原生、移动 Web 和混合应用。“原生应用” 指那些用 iOS 、 Android 或者 Windows SDK 编写的应用。“移动 web 应用” 是用移动端浏览器访问的应用(Appium 支持 iOS 上的 Safari 、Chrome 和 Android 上的内置浏览器)。“混合应用” 带有一个 "webview" 的包装器——用来和 Web 内容交互的原生控件。类似 Phonegap 的项目,让用 Web 技术开发然后打包进原生包装器创建一个混合应用变得容易了。

重要的是,Appium 是跨平台的:它允许你用同样的 API 对多平台写测试,做到在 iOS 、Android 和 Windows 测试套件之间复用代码。

Appium 的理念

Appium 旨在满足移动端自动化需求的理念,概述为以下四个原则:

Appium 的设计

那么 Appium 项目的架构如何实现这一理念呢?为了实现第一点要求,我们其实使用了系统自带的自动化框架。这样,我们不需要把 Appium 特定的或者第三方的代码编译进你的应用。这意味着你测试使用的应用与最终发布的应用并无二致。我们使用以下系统自带的自动化框架:

客户端/服务器架构

Appium 的核心是暴露 REST API 的网络服务器。它接受来自客户端的连接,监听命令并在移动设备上执行,答复表示执行结果的 HTTP 响应。客户端/服务器架构实际给予了许多可能性:我们可以使用任何有 http 客户端 API 的语言编写我们的测试代码,不过选一个 Appium 客户端程序库 用更容易。我们可以把服务器放在另一台机器上,而不是执行测试的机器。我们可以编写测试代码,并依靠类似 Sauce Labs 的云服务接收和解释命令。
下面是我整理的 Appium 的组件:

会话 (session)

自动化始终在一个会话的上下文中执行,这些客户端程序库以各自的方式发起与服务器的会话,但都以发给服务器一个 POST /session 请求结束,请求中包含一个被称作 'desired capabilities' 的 JSON 对象(参见下一个章节)。这时服务器就会开启这个自动化会话,并返回一个用于发送后续命令的会话 ID。

Desired Capabilities

Appium 的 Desired Capabilities 是扩展了 webdriver 的 Desired Capabilities,他起到的作用就是一些发送给 Appium 服务器的键值对集合 (比如 map 或 hash),告诉服务器我们想要启动什么类型的自动化会话。也有各种可以在自动化运行时修改服务器行为的 capabilities。例如,我们可以把 platformName capability 设置为 iOS,告诉 Appium 我们想要 iOS 会话,而不是 Android 或者 Windows 会话。我们也可以设置 safariAllowPopups capability 为 true ,确保我们在 Safari 自动化会话中可以使用 javascript 打开新窗口。有关 Appium capabilities 的完整列表,请参阅 capabilities doc 。

下面的一些通用配置是需要指定的:
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。

了解WebDrive 协议

Appium 的安装

Appium 服务器
Appium 是用 Node.js 写的服务器。它可以从源码构建安装或者从 NPM 直接安装:

$ npm install -g appium
#查看版本
$ appium -v
#检查appium安装是否完整,各个依赖版本是否正确,可以用appim doctor
$ sudo npm install -g appium-doctor
$ appium-doctor

Appium 测试脚本开发和执行

首先确定你的 case 架构和整体设计,如果是针对业务的,是不是你的每个目录就是一个业务,下面每个文件名代表这个业务下面模块。

具体 API 的调用:

关于 Appium API 的文档,可以直接看官方的英文文档,也可以参考这里 https://testerhome.com/topics/3711

# 等主页面 activity 出现
driver.wait_activity(".base.ui.MainActivity", 10)

通过 resource id

driver.find_element_by_id("com.baidu.yuedu:id/negativeUpgrade").click()

# 获得属性值:

# 获得 id
t5 = driver.find_element_by_id("com.baidu.yuedu:id/lefttitle").get_attribute("resourceId")
# 获得 class
t6 = driver.find_element_by_id("com.baidu.yuedu:id/lefttitle").get_attribute("className")
# 获得 text
t7 = driver.find_element_by_id("com.baidu.yuedu:id/lefttitle").get_attribute("text")
# 获得 size
t10 = driver.find_element_by_id("com.baidu.yuedu:id/lefttitle").size
# 获得 location
t11 = driver.find_element_by_id("com.baidu.yuedu:id/lefttitle").location

# 重点说一下 find_element_by_xpath():

 #by text: //*[@text=’text文本属性’]: 
driver.find_element_by_xpath("//*[@text='扫一扫']").click()
#by id:  //*[@resource-id=’id属性’]
driver.find_element_by_xpath("//*[@resource-id='com.taobao.taobao:id/tv_scan_text']").click()
#by text and id:
driver.find_element_by_xpth("//*[@resource-id='com.taobao.taobao:id/tv_scan_text'][@text='扫一扫']").click()
#by class: 
driver.find_element_by_xpath("//android.widget.EditText").click()
#or
driver.find_element_by_xpath("//*[@class='android.widget.EditText']").click()
#by content-desc //*[@content-desc=’desc的文本’]:
driver.find_element_by_xpath("//*[@content-desc='帮助']").click()
#by contains  //[contains(@content-desc, ‘帮助’)]
driver.find_element_by_xpath('//*[contains(@text, "注册/登录")]').click()

分享一个方法也比较管用,有时候你发现不了或者找不到一个控件,就会抛出一个异常,我们可以写一个公共方法来判断一下是否存在,再获取这个空间。

def is_element_exist(driver, element_id, timeout=5):
      '''
      前面我们讲了隐式等待和强制等待,下面我们看看显示等待,同样的先看代码:
      WebDriverWait(driver, timeout, poll_frequency=0.5, ignored_exceptions=None)
      首先我们来弄明白这个方法里面几个参数的含义:
      1、driver:是我们操作的driver。
      2、timeout:超时时间,也就是我们找这个元素要找多久
      3、poll_frequency:间隔时间,怎么理解?就是说在超时时间内每多少秒去查询一次,默认情况是0.5秒一次
      4、ignored_exceptions:异常,就是没有找到程序抛出什么异常。在默认情况是跑出:NoSuchElementException
      '''
      try:
          WebDriverWait(driver, timeout,1).until(lambda driver:driver.find_element_by_id(element_id))
          return True
      except Exception as e:
          return False

更详细的说明,大家可以百度搜索一下,有大量的线程文档,也可以直接参考 Appium.io 的在线文档。关键就是多多调试,多多看别人代码,在整个编写脚本的过程中会遇到各种各样的坑,或者困难,我们要有信心战胜困难,解决问题,继续前行!

Android 测试执行和调试

编写完脚本后,如何执行和调试,这部分主要看你用的什么语言,我以 Python 为例吧。Python 有很好的 unittest 框架,安装这个框架写就没有问题,执行的时候可以用 pytest 的框架,配置文件用 yaml 文件。

客户端代理程序:

笔者经过实践,UiAutomator2 确实要比 UiAutomator 好很多,解决了很多问题:

大家也可以看一下原理图
具体来说就是安装安装两个 apk,一个驱动模块,一个服务模块,他们一起合作来完成命令的接受和执行:

iOS 测试 (待续。。。)

在线文档打开


↙↙↙阅读原文可查看相关链接,并与作者交流