Appium Appium 测试开发实践笔记 - ios 部分待续

wolfgao · 2019年01月18日 · 2178 次阅读

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 特定的或者第三方的代码编译进你的应用。这意味着你测试使用的应用与最终发布的应用并无二致。我们使用以下系统自带的自动化框架:

  • iOS 9.3 及以上:苹果的 XCUITest
  • iOS 9.3 及以下:苹果的 UIAutomation
  • Android 4.2+: 谷歌的 UiAutomator
  • Android 2.3+: 谷歌的 Instrumentation(通过绑定另外的项目—— Selendroid 提供 Instrumentation 的支持)
  • Windows: 微软的 WinAppDriver 为了实现第二点要求,我们把这些(系统本身的)供应商提供的框架包装进一套 API —— WebDriver API 中。WebDriver(也叫 "Selenium WebDriver")规定了一个客户端 - 服务器协议(称为 JSON Wire Protocol),按照这种客户端 - 服务器架构,可以使用任何语言编写的客户端向服务器发送适当的 HTTP 请求。

客户端/服务器架构

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 #或者通过覆盖session $ appium -a ip_address -p port_number --session-override # --session-override 是指覆盖之前的session; 还有一种就是下载一个服务器的应用,通过图形化的方式来进行配置和启动。目前我大部分都是通过这种方式来调试的,但是如果要进行 CI,必须使用命令行的方式来完成。

Appium 测试脚本开发和执行

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

具体 API 的调用:

  • finding elements:这部分是整个工作量最多的地方:
    Appium 支持 WebDriver 定位策略的子集:可以通过 "class" 查找 (例如: UI 组件的类型),也可以通过 "xpath" 查找 (例如: 一个元素的路径以抽象的方式去表达,具有一定的约束)

  • ios uiautomation: 该字符串相当于使用 UIAutomation 库 去递归地搜索元素(仅支持 iOS 9.3 及以下的版本)

  • android uiautomator: 该字符串相当于使用 UiAutomator Api 去递归地搜索元素(仅支持 Android)

  • accessibility id: 该字符串相当于利用原生的可访问性(Accessibility)选项,使用 Id/Name 去递归地搜索元素。

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

  • 几个重要的 API examples:

# 等主页面 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 文件。

客户端代理程序:

  • Bootstrap + UiAutomator V1(应该是 appium 早期版本,比如 1.6 之前的版本),现在最新的版本已经 1.9,因此可以跳过这部分,直接看下面。
  • UiAutomator2:UiAutomator2(也可称为 UiAutomator V2 原理同之前的 UiAutomator+Bootstrap 有很大的不同:不但 UiAutomator V2 修复了 UiAutomator V1 中遇到的大多数问题,而且最重要的是实现了与 Android 系统更新的分离 英文基础较好的同学可以去看看 https://github.com/appium/appium-uiautomator2-server/wiki

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

  • 比如在获取到 toast 信息的时候需要将 automationName 设置成 UiAutomator2,就是因为在 UiAutomator2 上新增了 AccessibilityService 服务,可以实现 Toast 的获取。
  • 比如有时候无论什么方法就是拿不到控件,我试过 find_element_by_id, by_uiautomator('new UiSelector().xxx("")'),就是怎么搞总是提示 not found,后来在 caps 设置成 UiAutomator2 就可以了。 只要在 desired capabilities 加上 'uiautomator2',即可立即使用 UiAutomator2。 desired_caps['automationName'] = 'uiautomator2'

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

  • UiAutomator2 drivr apk: 驱动模块,负责创建会话,安装 UiAutomator2-server.apk 到设备上,开启 Netty(这是一个通信框架,相当于 Bootstrap 使用的 SocketServer 升级版本)
  • UiAutomator2 server apk: 当驱动模块创初始化完毕,服务器就会监听 PC 端 Appium 发送过来的请求,将请求发送给真正底层的 UiAutomator2。

iOS 测试 (待续。。。)

在线文档打开

  • Python 的在线文档服务打开 python3 -m pydoc -b
共收到 0 条回复 时间 点赞
子非鱼 行业流行测试框架对比 中提及了此贴 03月18日 21:59
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册