自动化工具 Appium UI 自动化框架之我见 (开源)

Justin · 2018年08月03日 · 最后由 Justin 回复于 2021年01月07日 · 1528 次阅读

APP UI Automation Framework

一个基于 Appium 1.8.1、TestNG,Page Object 模式开发的 UI 自动化测试框架

https://github.com/lgxqf/AppUIAutomation

介绍文档

结构图

基本功能

  • 每秒生成一次截图
  • 通过 xml 配置待执行的测试用例
  • 通过 yml 指定待执行测试的设备及 Appium 端口
  • 用例执行失败自动重试,且重试次数可配置
  • 用例执行失败时自动截图
  • 生成测试报告 (NGReport)
  • 支持自定义配置项


设计目标

  • 用一套代码执行 Android/iOS 测试用例
  • Test case 层的代码高度利用,只需要考虑业务逻辑,无需关心系统平台及如何查找元素

    以下代码在iOS和Android上均可运行
    
    //打开我的朋友圈
    public void showMyMoment(){
        //打开微信主页面,点击"我"
        WeiXinMainPage.verify()
                .clickMeButton();
    
        //校验"我"页面,打开"朋友圈"
        WeiXinMePage.verify()
                .clickMoment();
    
        //校验"朋友圈页面",下划一段距离,然后打开带图片的朋友圈
        WeiXinMomentPage.verify()
                .scroll()
                .clickMyMoment();
    
        Driver.sleep(10);
    }
    

设计理念

  • 应用 Page Object 模式提高 UI 页面操作代码的复用度
  • 用 Driver 类封装所有用到的 Appium API, 框架中其它类只通过 Driver 调用 Appium 的方法,这种作法会有以下两点好处:
  • 一、屏蔽对 Appium API 的依赖,如果 Appium 的某个 API 官方废弃了,只需修改 Driver 类封装的相应方法即可
  • 二、如果将 Appium 换成 Macaca 或其它框架,除了改动 Driver 类 其它类无需改动
  • 在 Driver 中用 findElementById 等封装对 iOS 和 Android 的元素查找,提高代码的复用,尽可能的避免 iOS 与 Android 因查找元素方式不同而写相似的代码
  • 该框架适用于同一个 APP, Android 和 iOS UI 结构基本一致的情况

一些原则

  • Page 类的构造函数用 Verify 代替
  • Page 类的构造函数用过 findElementByID 等来 检查当前页面是不否为期望的 Page
  • 依照 SRP 原则,Page 类内的函数 只返回当前类实例(this) 或 void, 不返回其它页面的对象,确保每个 Page 与依赖于任何其它 Page,提高 Page 类的复用度
//朋友圈的Page类
public class WeiXinMomentPage extends BasePage {

    //能过静态方法返回页面实例
    public static WeiXinMomentPage verify(){
        if( !Util.isAndroid() ) {
            //默认情况下写的Page类是Android的UI
            //若Android与iOS UI上有差异,需继承Android的Page类再写个iOS Page
            return new WeiXinMomentPageiOS();
        }

        return new WeiXinMomentPage();
    }

    //不允许调用构造函数
    protected WeiXinMomentPage(){
        Driver.findElementByText(getRes("MOMENT_PAGE_ME_TEXT"));
    }

    //所有成员函数只返回this或void,确保每个Page类的独立性,不依赖于任何其它Page类
    public WeiXinMomentPage scroll(){
        Driver.scrollUp();

        return this;
    }

    public WeiXinMomentPage clickMyMoment(){
        MobileElement elem = Driver.findElemByIdWithoutException (getRes("MY_POST_PAGE_MOMENT_PIC_ID"));

        if(elem == null){
            elem = Driver.findElementById(getRes("MY_POST_PAGE_MOMENT_ARTICLE_ID"));
        }

        elem.click();

        return this;
    }
}

  • Driver : 封装所有用到的 Appium 方法。作用屏幕对 Appium 的依赖、提供更方便的函数。
  • BasePage : 所有 Page 类的基类
  • BaseTest : 所有 Test 类的基类
  • ConfigUtil : 读取工程配置文件
  • ResourceUtil : 读取资源配置文件
  • Util : 工具类,提供一些能用方法
  • PageUtil : 封装进入某个页面的方法,方便复杂 test case 的编写
  • TestListener : 监听测试结果,用例执行失败时截图

配置文件

  • Config.yml 运行测试时的一些配置项 如包名,重试次数等等。 详见 Config.ym 内的注释

资源文件(具体使用方法见 demo)

  • 为每个元素新建一个便于辨识的名字,用这个名字统一 Android/iOS 待查找元素, 然后将不同系统找中该名字的元素对应的值写入相应的 RES.yml 中
  • AndroidRES.yml 写入 Android 元素查找时需要用到的值
  • IOSRES.yml 写入 iOS 元素查找时需要用到的值
AndroidRES.yml

MAIN_PAGE_WEIXIN_TEXT: '微信'
MAIN_PAGE_CONTACT_TEXT: '通讯录'
MAIN_PAGE_DISCOVER_TEXT: '发现'
MAIN_PAGE_ME_TEXT: '我'

ME_PAGE_MY_POST_TEXT: '相册'
MOMENT_PAGE_ME_TEXT: '我的相册'

MY_POST_PAGE_MOMENT_PIC_ID: 'com.tencent.mm:id/dep'
MY_POST_PAGE_MOMENT_ARTICLE_ID: 'com.tencent.mm:id/yk'
IOSRES.yml

MAIN_PAGE_WEIXIN_TEXT: '微信'
MAIN_PAGE_CONTACT_TEXT: '通讯录'
MAIN_PAGE_DISCOVER_TEXT: '发现'
MAIN_PAGE_ME_TEXT: '我'

ME_PAGE_MY_POST_TEXT: '相册'
MOMENT_PAGE_ME_TEXT: '我'

MY_POST_PAGE_MOMENT_PIC_ID: 'visible == true AND type == "XCUIElementTypeStaticText" AND name CONTAINS "月"'

测试用例集

  • 框架通过读取 task 目录下的 xml 运行指定的测试用例
在任务的xml中有四个值需要配置
1. port : Appium 端口   
2. udid : 设备ID
3. wdaPort : iOS设备运行的时的WDA port
4. class : 待运行的测试类

测试执行时输入的xml样例

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">

<suite name="TestSuite">
    <listeners>
        <listener class-name="framework.TestListener" />
    </listeners>

    <test name="Performance">
        <parameter name = "port" value = "4723"/>     
        <parameter name = "udid" value = "SJE0217B29005225"/>
        <parameter name = "wdaPort" value = "8001"/>
        <classes>
            <class name="testcase.weclass.WCPerformanceTestCPU"/>
            <class name="testcase.weclass.WCPerformanceTestBattery"/>
        </classes>
    </test>
</suite>

如何运行 demo

  • demo 实现的功能:打开微信 (若未登录微信,请先手动登录),然后打开朋友圈,查看第一个朋友圈 (带图片的)
  • 启动 Appium,然后运行以下命令
  • 方式一 : 将工程打成 Jar 包,然后运行命令 java -jar UIAutomation-1.0-fat-tests ./task/demo.xml
  • 方式 2 : IDEA 中 右键单击 demo.xml ,选择运行。见下图

参考文档

共收到 11 条回复 时间 点赞

欢迎拍砖,共同探讨。

支持啊,有时间试用下😀

雨夜狂奔 回复

你可以先运行一下 demo 在 ios 和 android 上都可运行

有源码不,可否下载下来试试

有啊 第一行就是源码地址

Justin 测试开发之路--UI 自动化设计军规 中提及了此贴 08月13日 10:18

有时间试试,不过目前的工作重心是 WEB 自动化

@seveniruby 这个能申请加精吗😀

java 不会。。。Python 小白路过

你好,马哥,我有向您请教的问题:
1、我观察到您的 PO 封装完,需要 return 一个 page 回去,我采用的是 return this,而您是 return new page。。请问您是故意不用 this 吗,是否有特殊的原因呢?
2、我观察了您开源的微信公众号的 UI 自动化测试框架,您的 driver 是定义了 static 变量,供后续的 findElement 等等操作,但是这样在多线程下安全吗?线程 1 设置了 driver,被线程 2 修改,,请问这里是故意这么设计的吗?

Justin #12 · 2021年01月07日 Author
julystone 回复

当时没考虑多线程 我用的是多进程 所以不受影响

“需要 return 一个 page 回去,我采用的是 return this,而您是 return new page” 对呀 构造一个新对象 所以返回 new page

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册