Appium 在 Android UI 测试中的应用

MiriamColli 8年前
   <h3><strong>Android 测试工具与 Appium 简介</strong></h3>    <p>Appium 是一个 C/S 架构的,支持 Android/iOS Native, Hybrid 和 Mobile Web Apps 的测试框架,与测试程序通过 Selenum Webdriver 协议通讯。Webdriver 的好处是通过 HTTP RPC 的方式调用 Server 上的过程,编写测试脚本不受语言的限制,无论是 Python, Java, NodeJS 均可以方便的编写测试。本文中将使用 Python 进行编程。</p>    <p>起因是因为市场部的同事抛来如下需求:批量添加一些微信好友。直接抓取请求进行重放的方法是不靠谱的,微信与服务端的通讯均加密,Pass。考虑使用 xposed 等框架 hook 相关函数进行操作。但是 xposed 需要越狱,且开发复杂,Pass。后来想到了使用 UI 测试工具进行模拟操作,开发较为简单。</p>    <p>Android UI 测试工具有很多种,如 Monkey, UIAutomator, Selendroid, Robotium 等。其中 UIAutomator, Monkey, Selendroid 均为非侵入式的 UI 测试,也就是不需要修改源代码,只要安装了目标程序就可以进行测试。Robotium 需要与源码一同编译测试。Appium 实际上就是一个测试工具的统一调度软件,将不同的非侵入式测试工具整合在一起,对外提供统一的 API。在 Android 2.3 以前的版本,Appium 会调用 Selendroid ,之后的版本会直接使用 UIAutomator,iOS 下使用 UIAutomation。Appium 还支持 FirefoxOS 的 UI 测试。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/7ae2adc36848ae7e786c762930b950cf.gif"></p>    <h3><strong>安装 Appium</strong></h3>    <p>官网给出了命令行下的安装方法。但实际上 Appium 有 GUI 版本,更适合在 Windows/MacOS 下使用。Windows 下需要安装 .NET Framework。</p>    <pre>  <code class="language-python">> brew install node      # get node.js  > npm install -g appium  # get appium  > npm install wd         # get appium client  > appium &               # start appium  > node your-appium-test.js</code></pre>    <p>Appium 需要依赖 Android SDK 编译在手机端运行的两个插件,因此需要首先安装相应的 Android SDK 版本。这里直接使用了 Android Studio 中自带的 SDK Manager。在 SDK Manager 中选择和测试机相对应的 SDK Platform 和较新的 Build-tools,如果需要使用模拟器测试还要装对应的 ARM/x86 System Image,以及 Intel HAXM Installer,用于加速 x86 虚拟机。Appium 使用 adb 来与目标机器通讯,因此对于真机和模拟器操作几乎都是相同的,如何建立模拟器在此不再赘述。</p>    <p>安装完成后需要在 Appium GUI 中配置 Android SDK 目录,随后选择 Android,点击 Launch 就可以启动 Appium Server。</p>    <p><img src="https://simg.open-open.com/show/7148394b349714b418c34ec71993e946.png"></p>    <p><img src="https://simg.open-open.com/show/7a4a35b3c2faea9fcaf92c7356f01809.png"></p>    <p>Appium Server 默认会监听 <a href="/misc/goto?guid=4959714399790976736" rel="nofollow,noindex">http://localhost:4723</a> ,用于 RPC 通讯。下面我们就可以打开熟悉的编程环境,编写 UI 测试用例了。这里使用 Python 进行编写,需要先安装 Appium 的 Python Client ,然后再 python 中使用 appium.webclient 就可以连接 Appium server了。</p>    <pre>  <code class="language-python">pip install Appium-Python-Client</code></pre>    <h3><strong>使用 Appium 进行 UI 控制</strong></h3>    <p>根据注释修改相应属性后即可运行测试。手机需要打开 ADB 调试,执行完以下代码后,Appium 会在手机上安装 Appium Settings 和 Unlock 两个程序,随后微信会被启动。</p>    <pre>  <code class="language-python">from appium import webdriver     desired_caps = {}  desired_caps['platformName'] = 'Android'  #测试平台  desired_caps['platformVersion'] = '5.1'   #平台版本  desired_caps['deviceName'] = 'm3_note'    #设备名称,多设备时需区分  desired_caps['appPackage'] = 'com.tencent.mm'  #app package名  desired_caps['appActivity'] = '.ui.LauncherUI' #app默认Activity  dr = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) #启动Remote RPC</code></pre>    <p>Selenum Webdriver 使用了一种类似于 JS 中的 DOM 模型的方法来选择页面中的元素。dr 为当前正在活动的 activity 对象,可以使用 findElementByXXX 的方法来获取 Activity 中的元素。所有 Element 后带 s 的函数,均获得所有匹配的元素,不带 s 的函数获得第一个匹配的元素。</p>    <p><strong>查询函数</strong></p>    <p><strong>1. findElement(s)ByName</strong></p>    <p>在 Android 中基本没用。Android UI 没有 Name 这个属性。有说可以使用 text 值获取。但我并没有成功</p>    <p><strong>2. findElement(s)ByClassName</strong></p>    <p>通过类名来获取元素,用法如下:</p>    <pre>  <code class="language-python">item_list = dr.find_elements_by_class_name("android.widget.LinearLayout")  item_list[2].click()</code></pre>    <p><strong>3. findElementById</strong></p>    <p>通过 resource_id 来获取元素,每个 Activity 中都是唯一的,用法如下</p>    <pre>  <code class="language-python">t = dr.find_element_by_id("com.tencent.mm:id/f7")  t.send_keys(wechatId)</code></pre>    <p><strong>4. findElement(s)ByAccessbiltiyId</strong></p>    <p>在 Android 上 AccessbilityID 实际就是 contentDescription 。这个属性是为了方便视力受损人士使用手机所设置。开启 TTS 后系统会朗读相关控件的 contentDescription。</p>    <p><strong>5. findElement(s)ByXPath</strong></p>    <p>通过 XML Path 描述来寻找元素。我没有成功的获取到,可能是 XPath 写的有问题。</p>    <pre>  <code class="language-python">s = dr.find_element_by_xpath("//android.widget.TextView[contains(@text,'搜索')]")  s.click()</code></pre>    <p><strong>6. findElementByAndroidUIAutomator</strong></p>    <p>通过 UIAutomator 的选择器来获取元素。因为 Appium 在 Android 上实际是调用的 UIAutomator,所以可以通过 UIAutomator 的选择器来选择元素。</p>    <pre>  <code class="language-python">el = dr.find_element_by_android_ui_automator("new UiSelector().text(\"搜索\")")  el.click()</code></pre>    <p><strong>操作函数</strong></p>    <p>操作函数用于操作选定的元素,有很多,以下仅列举几个,更多的请查阅手册。</p>    <ol>     <li>click</li>     <li>send_keys</li>     <li>clear</li>    </ol>    <p>查询函数返回的元素对象可以像 JS 中的 dom 元素一样,继续使用查询函数来选定其子元素。用例如下。</p>    <pre>  <code class="language-python">search = dr.find_element_by_id("com.tencent.mm:id/aqw").find_element_by_class_name("android.widget.RelativeLayout")  search.click()</code></pre>    <h3><strong>如何确定查询规则</strong></h3>    <p>了解了相关的函数后,下面就应对 UI 进行定位了。如果是自己团队开发的程序,推荐让开发同学在所有的空间上都添加 resource_id 进行绝对定位。如果碰到没有谈价 resource_id 的元素,那就要使用别的办法进行定位了。</p>    <p><strong>1. UI Automator Viewe</strong>r</p>    <p>UI Automator Viewer 是 Android 官方的 UI 定位工具,位于 sdk/tools 下。运行后会打开 viewer 界面。点击获取按钮即可获取当前正在运行的 Activity 的 UI 结构。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d43d2e5b004a886cb1d08d00d89c9489.png"></p>    <p><strong>2. AppiumDriver getPageSource</strong></p>    <p>AppiumDriver(Client) 可以很方便的获得当前正在运行的 Activity 的 UI 描述,随后可根据返回的 XML 文档来寻找元素。</p>    <pre>  <code class="language-python">print dr.page_source</code></pre>    <p><img src="https://simg.open-open.com/show/eb517d4ed5d36cd240f957b48f5826a6.jpg"></p>    <p>确定元素位置后,即可根据前述的 Find 方法来查找/选择元素</p>    <h3><strong>编写完整的测试代码</strong></h3>    <p>正确的获取元素之后便可以获取元素相关的信息,随后使用各语言常用的测试框架编写测试即可,如 Java 的 JUnit,Nodejs 的 Mocha 等。</p>    <p>这里我使用 Appium 主要是为了模拟用户点击添加微信好友,所以完整的程序并没有使用到测试框架。相关的 UI 元素获取/操作方法供大家参考。</p>    <pre>  <code class="language-python"># coding:utf-8  from appium import webdriver  from time import sleep      def addFriend(dr, id, dryRun=False):      succ = False      wechatId = str(id)      dr.find_element_by_accessibility_id(r"更多功能按钮").click()      item_list = dr.find_elements_by_class_name("android.widget.LinearLayout")      try:          item_list[2].click()      except:          print "Error! in item list len"          return succ      el = dr.find_element_by_class_name("android.widget.ListView")      item_list = el.find_elements_by_class_name("android.widget.LinearLayout")      try:          item_list[1].click()      except:          print "Error! in item list len"          return succ      t = dr.find_element_by_id("com.tencent.mm:id/f7")      t.send_keys(wechatId)      search = dr.find_element_by_id("com.tencent.mm:id/aqw").find_element_by_class_name("android.widget.RelativeLayout")      search.click()      try:          freq = dr.find_element_by_id('com.tencent.mm:id/aqq')          assert freq.text == u"操作过于频繁,请稍后再试。"          print "Frequency too high! Sleep 300s"          sleep(60)          return succ      except:          pass        try:          dr.find_element_by_id('com.tencent.mm:id/a8x').click()          addBtn = dr.find_element_by_id('com.tencent.mm:id/eu')          if not dryRun:              addBtn.click()              succ = True          print "Success Send Requests:" + wechatId      except:          print "No Such User Or Already a Friend:" + wechatId        while True:          try:              dr.find_element_by_id('com.tencent.mm:id/fb').click()          except:              try:                  dr.find_element_by_id('com.tencent.mm:id/f4').click()              except:                  break      return True    def resetActivity(dr, desired_caps):      dr.start_activity(desired_caps['appPackage'], desired_caps['appActivity'])    desired_caps = {}  desired_caps['platformName'] = 'Android'  desired_caps['platformVersion'] = '5.1'  desired_caps['deviceName'] = 'm3_note'  desired_caps['appPackage'] = 'com.tencent.mm'  desired_caps['appActivity'] = '.ui.LauncherUI'  print "Trying connect to phone..."  dr = {}  try:      dr = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)  except Exception, e:      print "Cannot Connect to phone :", e      exit()  print "Successfully connect to phone."  print "Reading friend list..."  friendList = []  fp = open("friends.txt")  line = fp.readline().strip()  while line:      friendList.append(line)      line = fp.readline().strip()  print "Finish reading friends. Total: " + str(len(friendList))  print "Wait for Wechat's splash screen...."  for i in range(0, 10):      print 10 - i      sleep(1)  succ_list = []  fail_list = []  for i in friendList:      try:          succ = addFriend(dr, i, dryRun=False)          if succ:              succ_list.append(i)          else:              fail_list.append(i)      except:          fail_list.append(i)          resetActivity(dr, desired_caps)    print "Succeed List:"  print "\n".join(succ_list)  print "Failed List:"  print "\n".join(fail_list)    dr.close()</code></pre>    <p> </p>    <p>来自:https://blog.coding.net/blog/Appium-Android UI</p>    <p> </p>