基于Appium的UI自动化测试

news2024/11/27 21:37:54

为什么需要UI自动化测试

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

为什么选择Appium

Appium是一款开源测试工具,可以用来测试安卓/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 以上XCUITestiOS 9.3 以下UIAutomation
  3. Appium在GitHub上开源,维护频率很高,社区也有相对较高的活跃度。在社区的不断努力下,Appium能始终保持兼容最新版本的手机操作系统和官方提供的测试框架,功能也越来越完善,包括基本的log收集、录屏、基于opencv的图像识别等,以及最近版本添加的iOS 13/Android 10支持等;
  4. Appium支持通过自定义插件寻找元素,GitHub上也有第三方在开发可用插件,例如基于人工智能的icon识别控件示例工程;也可以自定义插件,使用图像识别、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();
        }
        // 未登录状态,"首页-我的"元素不存在,抛出NoSuchElementException
        catch (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无法定位的元素:
  • 文案是"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']

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

  1. 图像识别查找元素
    Appium在By Selector级别支持按照图片查找By by = MobileBy.image(base64ImageString)。目前不支持多元素查找,只返回第一个查找到的元素。
    让Appium支持图片查找,需要一点前期准备工作:
    1. 安装NodeJS版本的OpenCV库:npm install -g opencv4nodejs
    2. Appium中配置相关参数:
      // 设置图片识别阈值,默认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元素等情况,因此在执行测试用例过程中,会检测手机端可能出现的、非测试步骤中预期的弹窗,包括首充弹窗、开播礼物下载弹窗等,关闭弹窗后再次查找元素;

 

  1. htest client初始化Appium driver,以Appium作为代理连接手机,并在手机端执行测试用例中的基本操作;
  2. 如果执行测试用例失败,会尝试重新执行失败的用例,如果再次失败,会收集手机端日志、保存截图和录屏,并将失败日志返回保存到测试平台中, 执行单个case时使用socket发送执行结果, 结果通过htest Server回传给测试平台进行展示, 如果bvt时,则通过接口回传结果数据

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

如果对软件测试、接口测试、自动化测试、面试经验交流。感兴趣可以加软件测试交流:1085991341,还会有同行一起技术交流。

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

  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不同

3.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配置。

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

最后:下面是配套学习资料,对于做【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!

软件测试面试小程序

被百万人刷爆的软件测试题库!!!谁用谁知道!!!全网最全面试刷题小程序,手机就可以刷题,地铁上公交上,卷起来!

涵盖以下这些面试题板块:

1、软件测试基础理论 ,2、web,app,接口功能测试 ,3、网络 ,4、数据库 ,5、linux

6、web,app,接口自动化 ,7、性能测试 ,8、编程基础,9、hr面试题 ,10、开放性测试题,11、安全测试,12、计算机基础

  全套资料获取方式:点击下方小卡片自行领取即可

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1023206.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

八大排序(二)--------冒泡排序

本专栏内容为&#xff1a;八大排序汇总 通过本专栏的深入学习&#xff0c;你可以了解并掌握八大排序以及相关的排序算法。 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;八大排序汇总 &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库…

高端知识竞赛中用到的软件和硬件有哪些

现在单位搞知识竞赛&#xff0c;已不满足于用PPT放题&#xff0c;找几个简单的抢答器、计分牌弄一下了&#xff0c;而是对现场效果和科技感要求更高了。大屏要分主屏侧屏&#xff0c;显示内容要求丰富炫酷&#xff1b;选手和评委也要用到平板等设备&#xff1b;计分要大气些&am…

QT在安装后添加新组件【QT基础入门 环境搭建】

一、Qt的安装目录下找到MaintenanceTool工具 二、双击该exe文件运行该工具(界面可能不相同但功能一样) 登录账号,进入以下界面,点击下一步 选择更新组件,出现以下提示 三、此时需要手动添加储存库 1.进入下面网站,选择一个国内镜像 Qt Downloads 点击后面的HTTP可进入…

java CAS详解(深入源码剖析)

CAS是什么 CAS是compare and swap的缩写&#xff0c;即我们所说的比较交换。该操作的作用就是保证数据一致性、操作原子性。 cas是一种基于锁的操作&#xff0c;而且是乐观锁。在java中锁分为乐观锁和悲观锁。悲观锁是将资源锁住&#xff0c;等之前获得锁的线程释放锁之后&am…

如何才能实现批量抠图?一键批量抠图的办法

批量抠图是在处理大量图片时非常实用的技术&#xff0c;它可以帮助您快速准确地去除图片的背景或选择特定的对象。在编辑图片和设计时是一项必不可少的技能。 今天分享一款简单方便的抠图工具&#xff0c;智能识别并抠取图片中的各种物体、人物、动物等&#xff0c;效果不错&a…

简单聊聊G1垃圾回收算法整个流程 --- 理论篇 -- 下

简单聊聊G1垃圾回收算法整个流程 --- 理论篇 -- 下 软实时性预测转移时间预测可信度GC 暂停处理的调度并发标记中的暂停处理 分代 G1 GC 模式不同点新生代区域分代对象转移具体转移流程分代选择回收集合设置最大新生代区域数 GC的切换GC执行的时机 总结 上一篇 文章我们简单看了…

JDK新特性-Stream流

Stream流是用来操作集合或者数组中的数据的&#xff0c;Stream流提供了一种更加强大的&#xff0c;更加简单的方式来操作集合或者数组中的数据&#xff0c;代码更加简洁&#xff0c;可读性更好。下面是一个简单的例子&#xff1a; public class S1 {public static void main(S…

12万条中法常见词语对照ACCESS数据库

法文是全球通用的语言之一&#xff0c;要拥有国际视野&#xff0c;学懂全球通用语言是您的必然选择。走向成功之门&#xff0c;尽快学会法文吧。《12万条中法常见词语对照ACCESS数据库》收集了汉语常用词语的法文对照翻译。 本数据库是由 Microsoft Access 2000 创建的 MDB 数据…

算法训练 第三周

一、买卖股票的最佳时机 本题给了我们一个数组&#xff0c;这个数组的第i个元素表示股票第i天的价格&#xff0c;要求我们选择一天买入股票&#xff0c;并在这天之后的某一天卖出&#xff0c;问我们何时能获得最大利润。 1.循环嵌套 我们可以循环遍历所有买入的价格&#xff…

Al中秋节由来

文章目录 简介中秋节的庆祝活动有哪些&#xff1f;有没有其他与中秋节相关的传说或故事&#xff1f; 今天的话题是&#xff0c;扯犊子 简介 中秋节是中国传统的重要节日之一&#xff0c;通常在农历八月十五这一天庆祝。中秋节的由来有多种传说和故事。 其中最有名的传说是关于…

什么是HTTP状态码?常见的HTTP状态码有哪些?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 什么是HTTP状态码&#xff1f;⭐ 1xx - 信息性状态码⭐ 2xx - 成功状态码⭐ 3xx - 重定向状态码⭐ 4xx - 客户端错误状态码⭐ 5xx - 服务器错误状态码⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前…

pcl--第五节 点云表面法线估算

估算点云表面法线 * 表面法线是几何表面的重要属性&#xff0c;在许多领域&#xff08;例如计算机图形应用程序&#xff09;中大量使用&#xff0c;以应用正确的光源以产生阴影和其他视觉效果。 给定一个几何表面&#xff0c;通常很难将表面某个点的法线方向推断为垂直于该点…

Qt(day3)

思维导图 小练习 second.h #ifndef SECOND_H #define SECOND_H#include <QWidget> #include<QLabel> #include<QLineEdit> #include<QPushButton> #include<QTimerEvent> #include<QTime> #include<QTextEdit> #include<QTextT…

北京小程序开发-微信小程序开发时间总结

一、双线程模型 渲染线程和逻辑线程 小程序的双线程指的就是渲染线程和逻辑线程&#xff0c;这两个线程分别承担UI的渲染和执行 JavaScript 代码的工作 渲染线程使用 Webview 进行 UI 的渲染呈现。Webview 是一个完整的类浏览器运行环境&#xff0c;本身具备运行 JavaScript…

Shiro 框架基本使用

文章目录 Shiro框架介绍Shiro 基本使用SimpleAccountRealmIniRealmJdbcRealmCustomRealm&#xff08;自定义Realm&#xff09; Shiro框架介绍 Apache Shiro是一个强大且易用的Java安全框架&#xff0c;它执行身份验证、授权、密码和会话管理。Shiro框架通过其三个核心组件&…

网络爬虫-----爬虫的分类及原理

目录 爬虫的分类 1.通用网络爬虫&#xff1a;搜索引擎的爬虫 2.聚焦网络爬虫&#xff1a;针对特定网页的爬虫 3.增量式网络爬虫 4.深层网络爬虫 通用爬虫与聚焦爬虫的原理 通用爬虫&#xff1a; 聚焦爬虫&#xff1a; 爬虫的分类 网络爬虫按照系统结构和实现技术&#…

白鲸开源 X SelectDB 金融大数据联合解决方案公布!从源头解决大数据开发挑战

业务挑战与痛点 随着互联网技术的发展、云计算技术的成熟、人工智能技术的兴起和数字化经济的崛起&#xff0c;数据已成为企业的核心资产。在金融行业中&#xff0c;数字化已成为了支撑各类业务场景的核心力量&#xff0c;包括个人理财、企业融资、股票交易、保险理赔、贷款服…

【Java 基础篇】Java 文件及文件夹操作详解

在Java编程中&#xff0c;文件和文件夹操作是常见的任务之一。你可能需要读取、写入、创建、删除文件或文件夹&#xff0c;以及遍历文件系统中的内容。本文将详细介绍Java中如何执行这些常见的文件和文件夹操作&#xff0c;适用于初学者和基础用户。 1. 文件操作 读取文件内容…

如何显示并管理Python应用的数据?Kendo UI for Angular有妙招!

Angular是Python应用中进行数据管理和显示的一个很好的选择&#xff0c;如果能使用Kendo UI for Angular则可以更进一步。 PS&#xff1a;给大家推荐一个实用组件~Kendo UI for Angular是专业级的Angular UI组件库&#xff0c;不仅是将其他供应商提供的现有组件封装起来&#…

关于IDEA没有显示日志输出?IDEA控制台没有显示Tomcat Localhost Log和Catalina Log 怎么办?

问题描述&#xff1a; 原因是;CATALINA_BASE里面没有相关的文件配置。而之前学习IDEA的时候&#xff0c;把这个文件的位置改变了。导致&#xff0c;最后输出IDEA的时候&#xff0c;不会把日志也打印出来。 检查IDEA配置; D:\work_soft\tomcat_user\Tomcat10.0\bin 在此目录下&…