为什么需要UI自动化测试

移动端APP是一个复杂的系统,不同功能之间耦合性很强,很难仅通过单元测试保障整体功能。UI测试是移动应用开发中重要的一环,但是执行速度较慢,有很多重复工作量,为了减少这些工作负担,提高工作效率,需要引入可持续集成的自动化测试方案。

为什么选择Appium

Appium(http://appium.io/docs/cn/about-appium/intro/)是一款开源测试工具,可以用来测试安卓/iOS/Windows端的原生应用和Web混合应用。

  1. 为了应对快速迭代的移动端应用功能,越来越多的App采用混合模式,即将部分功能交给应用内嵌的Web页面实现。Appium能方便的切换测试原生应用或App内嵌的web页面,对于Hybrid App有很好的支持。

  2. Appium使用各个平台自身提供的测试框架,因此无需引入第三方代码或重新打包应用。

    平台 测试框架
    Android 4.2+ UiAutomator/UiAutomator2(默认)
    Android 2.3+ Instrumentation(由Selendroid提供)
    iOS 9.3 以上 XCUITest
    iOS 9.3 以下 UIAutomation
  3. Appium在GitHub上开源,维护频率很高,社区也有相对较高的活跃度。在社区的不断努力下,Appium能始终保持兼容最新版本的手机操作系统和官方提供的测试框架,功能也越来越完善,包括基本的log收集、录屏、基于opencv的图像识别等,以及最近版本添加的iOS 13/Android 10支持等;

  4. Appium支持通过自定义插件寻找元素,GitHub上也有第三方在开发可用插件,例如基于人工智能的icon识别控件示例工程(https://github.com/testdotai/appium-classifier-plugin);也可以自定义插件,使用图像识别、OCR等方式查找页面元素。

使用Cucumber组织case

Appium支持多种编程语言,包括Java、Python等,但是直接使用代码维护case可阅读性较差,学习成本也比较高,引入Cucumber可以使用更接近自然语言的方式组织case。Cucumber是支持BDD(Behaviour-Driven Development,行为驱动开发)的工具,可以自定义语法规则模版,将文本描述的步骤转为使用代码执行的步骤。由于Cucumber和Java 8均兼容中文文本编码,因此可以自定义中文操作步骤,比起英文代码更易于理解。以定义一个最基本的点击操作为例,预期的语法规则为"当 点击 [元素名称]",则可以使用如下定义:

   // Cucumber使用正则表达式匹配引号中的内容作为type参数@当("^点击 \"([^\"]*)\"$")public void findElementAndClick(String type) throws Throwable {// driver为Appium对待测设备的抽象,所有测试步骤最终转为对driver对操作// type可以传入元素ID对应的字符串,By.id表示通过元素resource-id查找driver.findElement(By.id(type)).click();}

编写case时,使用UI自动化测试常用的Page Object设计模式,即为APP中需要测试的UI页面定义一个Page对象,该对象中包含页面上的可操作或可校验元素,并添加常用方法。

以花椒首页为例,可以新建一个名为"首页"的对象,该对象中包含"搜索"、"我的"、"开播"等元素对应的查找方式(例如搜索按钮,对应可用来查找元素的resource-id为com.huajiao:id/main_home_top_search)。由于在搜索页输入用户uid进行搜索是一个常用操作,可以为此定义一个"搜索"方法。所有测试用例、Page对象、元素、方法都使用测试后台网页进行保存和编辑,并且实现了基本关键词补全功能。

测试平台编辑页面

如上定义基本的点击、滑动、输入文本等操作,建立好适当的页面和方法后,一条用例就能转化为与自然相近的case描述(#开头行为注释行):

# "$首页.搜索"表示使用"首页"Page中的"搜索"元素
当 点击 $首页.搜索
# "$搜索.搜索()"表示调用搜索页面的搜索方法,括号内为搜索关键词参数
$搜索.搜索(43011080)
当 断言元素出现 $搜索.搜索结果

编写代码进行复杂的自定义操作

通过Cucumber定义常用操作,如点击、滑动、校验文本等,可以降低编写一条测试用例的工作量,提高测试用例可读性,但并非所有功能都可以使用常用操作的方式。尤其是因为Cucumber只支持一步一步顺序执行指令,无法进行分支或循环指令,因此复杂的操作逻辑需要在自定义步骤中编写代码完成操作。编写代码部分封装参考Android官方提供的Espresso工程,通过链式调用的方式进行"查找-操作-校验"的流程。

以Android客户端退出登陆为例,点击底部"首页-我的"元素,若当前为未登录状态,则会弹出登陆弹出,此时底部"首页-我的"元素不可见,说明已经是未登录状态。

我的元素不可见

由于Cucumber顺序执行,无法进行"我的"元素可见时退出登陆,不可见时关闭登陆弹窗,因此需要编写代码自定义退出登陆步骤:

    @当("^退出登录$")public void logout() throws Throwable {// 点击"首页-我的"onView(By.id("com.huajiao:id/bottom_tab_user")).perform(click());try {// 如果当前用户已登陆,不会弹窗提示登陆,"首页-我的"元素可见onView(By.id("com.huajiao:id/bottom_tab_user")).check(matches(isDisplayed()));// 调用退出登录的方法logOut();}// 未登录状态,"首页-我的"元素不存在,抛出NoSuchElementExceptioncatch (NoSuchElementException e) {// 点击系统back键关闭登陆弹窗onActions().pressKeyCode(AndroidKey.BACK, "1").perform();}}

使用Appium查找UI元素

  1. 基本查找方式

  • By.id: 通过元素的resource-id进行查找;

  • MobileBy.AndroidUIAutomator(String code): 通过UIAutomator2的代码文本查找。code为符合UIAutomator2规范的代码文本,Appium会解析文本后使用反射的方式调用UIAutomator2进行查找;如下为使用UiSelector查找文本包含text的元素: String code = "new UiSelector().textContains(\"" + text + "\");";

  • xpath查找元素
    xpath可以用来在XML文档中查找元素和属性。Appium和谷歌官方提供的uiautomatorviewer工具获取元素都是xml形式组织的,xpath可以精准定位仅靠By.idBy.className无法定位的元素:

    虽然xpath方式查找元素更精准,但是元素的路径可能受到布局改动的影响,且在iOS上性能不佳,因此推荐优先使用resource-id等方式组合定位元素

    • 文案是"TEXT"元素的兄弟元素,该兄弟元素的resource-id是"ID":
      xpath://*[@text='TEXT')]/../android.widget.TextView[@resource-id='ID']

    • resource-id是"ID"且选中状态元素的子元素,该子元素的attr属性为value: xpath://*[@resource-id='ID' and @selected='true']/*[@attr='value']

  • 图像识别查找元素
    Appium在By Selector级别支持按照图片查找By by = MobileBy.image(base64ImageString)。目前不支持多元素查找,只返回第一个查找到的元素。
    让Appium支持图片查找,需要一点前期准备工作:

    1. 安装NodeJS版本的OpenCV库:npm install -g opencv4nodejs

    2. Appium中配置相关参数(更多配置可参考Blog):

      // 设置图片识别阈值,默认0.4。需要尝试在找不到元素和找到不匹配元素间的平衡
      driver.setSetting(Setting.IMAGE_MATCH_THRESHOLD, 0.5);
      // 图片识别耗时较长,可以在操作元素对时候不再次查找图片,以节省时间
      driver.setSetting(Setting.CHECK_IMAGE_ELEMENT_STALENESS, false);
      

    StaleElementReferenceException: Appium查找到元素,之后尝试操作元素时,若元素已经不在当前页面DOM资源上时会抛出StaleElementReferenceException异常。Appium使用UIAutomator2查找元素时,会保留元素的缓存,对元素进行操作时,会直接把缓存的信息交给UIAutomator2进行点击、滑动等操作。

    • 实际测试过程中,可能出现步骤:A页面跳转B页面;在B页面点击元素el。而A、B两个页面都有与el相同ID的元素,在B页面上尝试操作元素el的时候,Appium直接使用了A页面的缓存,此时会出现StaleElementReferenceException

    • 由于Appium采用HTTP请求查找和操作元素,因此查找元素和操作元素实际流程是:POST查找元素->server缓存元素->POST操作缓存的元素,有时间间隔。在网络请求期间如果出现APP端弹窗等元素遮挡,也可能导致StaleElementReferenceException

    整体工作流程

    整体工作流程
    1. htest client客户端获取打包安卓打包服务器下载列表,从中筛选出最新的APK安装包版本。如果有高于手机端的最新版本,则覆盖安装手机端花椒APP,并自动触发BVT测试用例执行(执行单个case时直接从测试平台网页端触发);

    2. 测试平台选出Cucumber描述的BVT用例集,同时查找Page页面,转义用例步骤的元素和方法,替换为客户端可使用的元素定位符(id:开头表示通过resource-id查找,text:开头表示通过文本内容查找),通过HTTP请求返回给客户端(执行单个case时使用socket方式发送)。

    3. 执行测试用例过程中,可能在查找元素时恰好遇到手机端弹窗盖住花椒APP元素等情况,因此在执行测试用例过程中,会检测手机端可能出现的、非测试步骤中预期的弹窗,包括首充弹窗、开播礼物下载弹窗等,关闭弹窗后再次查找元素;

    4. htest client初始化Appium driver,以Appium作为代理连接手机,并在手机端执行测试用例中的基本操作;

    5. 如果执行测试用例失败,会尝试重新执行失败的用例,如果再次失败,会收集手机端日志、保存截图和录屏,并将失败日志返回保存到测试平台中, 执行单个case时使用socket发送执行结果, 结果通过htest Server回传给测试平台进行展示, 如果bvt时,则通过接口回传结果数据

    使用测试平台网页端单次执行测试用例:

    按模块划分,整个框架分为:

    1. 测试平台: 网页端,用于保存、编辑基于Cucumber的测试用例,管理Page页面,解析用例中的元素,将转义后的用例发送给客户端,展示客户端实际执行结果;

    2. htest server: Java中间件,使用的netty框架, 负责转发socket消息,即测试平台通知客户端执行用例消息,和客户端执行结果返回测试平台。使用:

    • 在htest中server端netty的启动com.htest.server.server.BaseServer

      @Overridepublic void run() {if (bossGroup == null) {bossGroup = new NioEventLoopGroup();model.setBossGroup(bossGroup);}if (workerGroup == null) {workerGroup = new NioEventLoopGroup();model.setWorkGroup(workerGroup);}ServerBootstrap b = new ServerBootstrap(); b.group(model.getBossGroup(),model.getWorkGroup()).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100).option(ChannelOption.SO_KEEPALIVE, true).handler(new LoggingHandler(LogLevel.INFO)).childHandler(getChildHandler());try {future = b.bind(SERVER_IP, getPort()).sync(); LOGGER.debug("服务启动成功 ip={},port={}",SERVER_IP, getPort());future.channel().closeFuture().sync();} catch (Exception e) {LOGGER.error("Exception{}", e);} finally {Runtime.getRuntime().addShutdownHook(new Thread() {@Override public void run() {shutdown();}});}
      }
      
    • HttpServer、JarServer、WebsocketServer都是相同的启动方式,区别在于他们监听的端口不同,处理数据的handler不同

    • HttpServer的处理器是com.htest.server.handler.ServerHttpHandler,处理消息是按照http协议处理的

      @Override
      protected void messageReceived(ChannelHandlerContext ctx, HttpRequest request) {try {this.request = request; headers = request.headers();if (request.method() == HttpMethod.GET) {QueryStringDecoder queryDecoder = new QueryStringDecoder(request.uri(), Charset.forName("utf-8")); Map<String, List<String>> uriAttributes = queryDecoder.parameters(); //此处仅打印请求参数(你可以根据业务需求自定义处理) for (Map.Entry<String, List<String>> attr : uriAttributes.entrySet()){for (String attrVal : attr.getValue()) {Logs.HTTP.debug(attr.getKey() + "=" + attrVal);}}}if (request.method() == HttpMethod.POST) {fullRequest = (FullHttpRequest) request;//根据不同的 Content_Type 处理 body 数据dealWithContentType();}keepAlive = HttpHeaderUtil.isKeepAlive(request);writeResponse(ctx.channel(), HttpResponseStatus.OK, "开始执行", keepAlive);} catch (Exception e) {writeResponse(ctx.channel(), HttpResponseStatus.INTERNAL_SERVER_ERROR, "启动失败", true);}
      }
      
    • JarServer的处理器是com.htest.server.handler.ServerHandler,处理消息是按照protobuf格式处理的

      @Override
      protected void handleData(ChannelHandlerContext ctx, MessageModel.Message msg) {Connection connection = server.getConnectionManager().get(ctx.channel());connection.updateLastReadTime();server.getMessageReceiver().onReceive(msg, connection);
      }
      
    • WebsocketServer的处理器是com.htest.server.handler.ServerChannelHandler,它也是按照protobuf格式处理消息的,跟HttpServer不同之处在于他们的ChannelInitializer不同

  • htest client: Java客户端,用于定义Cucumber步骤,更新手机APK,初始化Appium,执行测试用例;使用方式:在pc端命令行中执行java -jar htest-client.jar,pc端需要有Appium和nodejs opencv环境,通过yaml配置文件控制执行测试过程中端参数。具体工作方式如下:

    • 功能:该jar支持定时检查最新apk功能,默认是不开启的,通过yaml文件配置是否开启。如果发现有最新apk,会自动安装到手机,并给web服务器(管理自动化case的测试平台)发送一次请求,触发一次指定模块case集执行。

    • 下载策略:该系统默认只下载最新的apk,如果本地yaml配置文件中的apkVersion值比服务器上的apkVersion值。如果比服务器的小,则不下载。

    • 安装策略:下载完成后首先会比对手机中的apk的versionName(通过aapt解析出来的)与下载的apk的versionName大小,如果下载的apk新,则进行安装,否则不安装。也可以配置参数安装到指定的手机,如果只有一台手机则不用配置参数。

    • 安装完成后会自动更新apkVersion的值,用于下次的判断。

    • 安装完成后会向web服务器发送http请求,web服务器收到后会触发一次,派发给当前手机case集任务,具体case集模块由models参数配置,结果邮件接收人通过mails配置。

  • Appium: NodeJS客户/服务端,用于连接手机,通过UIAutomator2/XCUITest,在手机端执行获取元素/点击/滑动等基本操作;

  • 问题与改进

    1. 目前一个客户端只支持通过USB连接单台手机执行自动化case,加上case的分类粒度不够小,无法多手机并行执行完整的测试流程;改进方式为使用adb tcpip通过无线网络连接多台手机,按照模块并行执行case。

    2. 现有的用例执行失败后错误收集机制不够完善,由于使用adb方式进行视频录制,兼容性并不好,且最长只能录制3分钟的操作视频;改进方式为使用scrcpy对失败case的执行过程。

    3. 目前使用Appium官方提供的ID、文本等元素查找方式,对于标准控件成功率较高,但对于自定义控件等无法获取resource-id的UI元素,使用xpath方式查找效率较低,有时还会出现无法唯一定位等情况。而Appium自带的图片查找元素准确率一般,在特定情况下(例如Flutter编写的界面),仅靠图像识别难以定位元素;改进方式为使用自定义Appium插件的方式,通过图像识别、OCR等方式综合查找和定位元素。


    ----end----


    推荐阅读:
    浅谈如何自动化生成测试脚本...
    有没有好到简历模版可以参考一下
    

    一站式自动化测试平台以及解决方案

    微信扫描二维码,关注我的公众

基于Appium的移动端UI自动化测试相关推荐

  1. IM场景的移动端UI自动化测试平台实践

    在公司做了两三年IM平台开发,基本上把IM的所有能力都搭建齐全了:单聊.群聊.文本消息.语音消息.视频消息.卡片消息.音视频通话等,而且把整个聊天页面各个区域都开放了出去.整个IM系统的框架以及开发流 ...

  2. Python+Selenium.webdriver实现WEB端UI自动化测试(实例脚本)

    本篇记录基于Python+Selenium.webdriver实现WEB端UI自动化测试,其中测试用例使用excel维护.为了在实际项目种的扩展应用,建议学习webdriver的元素定位方法,欢迎在评 ...

  3. 关于Web端-UI自动化测试

    在手工测试阶段,针对项目输出了测试用例,如果这些测试用例需要在版本迭代的过程中,需要进行回归测试,通过手工重复地执行测试用例,将会耗费大量的人力. 为此应运而生就有了自动化测试,通过使用自动化工具,将 ...

  4. python 移动ui框架_Touch UI:基于vue的移动端UI框架

    Hi,我们做了一款高质量的.免费的移动端UI框架. 经过将两年多开发和项目实践,我们终于把Touch UI开放出来了.这是一套基于vue.js打造的移动端UI框架,包含近百种组件,几乎囊括了开发移动应 ...

  5. python ui自动化测试框架_基于python语言下的UI自动化测试框架搭建(一)

    最近在搭一个UI自动化测试框架,想把整个搭建过程分享出来,如果有不对的地方,希望大家能够指正,首先创建一个名称为,antomation_framework_demo的工程文件, pycharm中工程及 ...

  6. 基于python语言下的UI自动化测试框架搭建(四)

    testsuits:案例执行 创建baidu_search1.py文件,这里会展示两种执行方式,一种是直接调用base_page中封装好的常用操作方法,另外一种是先调用baidu_homepage.p ...

  7. pythonwebui自动化_python+selenium实现web端UI自动化测试

    代码示例:css #!/usr/bin/python # -*- coding: UTF-8 -*- # coding:utf8 import sys import os from selenium ...

  8. 基于图片识别的 UI 自动化测试工具 SikuliX

    简介 SikuliX 是一款开源的 GUI自动化测试工具,它的前身是 Sikuli(上帝之眼).SikuliX 在设计上使用到了图片识别技术,会通过不停的对桌面进行截图,然后与预期图片进行比较来判断交 ...

  9. docker+robot framework+selenium并发web应用UI自动化测试实践

    自己在日常测试中,会搭建UI自动化测试框架来进行web应用的回归测试,在这过程中遇到了许多问题,如测试脚本和执行机不分离,串行测试效率低下,环境搭建麻烦等问题.在这个过程中,自己也在网上看一些前辈的搭 ...

最新文章

  1. 哪些软件问题也可导致硬盘录像机死机
  2. python安装gz文件_python tar.gz怎么安装-问答-阿里云开发者社区-阿里云
  3. 信息学奥赛一本通(1102:与指定数字相同的数的个数)
  4. leetcode python3 简单题190. Reverse Bits
  5. JXLS 2.4.0学习
  6. 【历史上的今天】8 月 14 日:新浪微博开始内测;阿塔纳索夫完成论文;登上太空的计算机病毒
  7. vs2017调用目标发生了异常
  8. 计算机术语翻译在线,拼音翻译在线
  9. 页式存储系统的逻辑地址是由页号和页内地址两部分组成的
  10. idea 关闭检查更新_intellij idea怎么关闭自动更新
  11. 站在Stay老司机肩膀上分析Retrofit
  12. ts类中的private和protected
  13. 使用Navicat连接Oracle数据库教程
  14. 正面管教php_我就这样走进正面管教
  15. 耳鸣头晕是怎么回事?
  16. APISpace 的 星座配对API
  17. java中 SSL认证和keystore使用
  18. tinymce6.0+ 插件按钮翻译
  19. 致远OA A8V5 A6V5 V8.2 8.1 8.0 V7 V6 V5 V3补丁
  20. 基于Cisco OpenH264 的SIP 软电话

热门文章

  1. Mars3d从菜鸟到大神的超级捷径
  2. 使用OpenGL实现视频录制
  3. 2079 11 21
  4. 计算机软件添加信任,如何在腾讯计算机管理器中取消/添加受信任的文件或软件...
  5. 性能测试——Controller
  6. 三、Bootstrap之Navbar属性整理
  7. 【面经】联想大数据开发面经
  8. flow hive 新型蜂箱_【中蜂蜂箱】flow hive 自动采蜜蜂箱 6m自流蜜蜂箱巢框 塑料巢脾...
  9. Hive 知识点小结(精简版)
  10. 【R的网络提取】什么值得买批量提取商品信息和价格等数据