Macaca 基于 uiRecorder 录制脚本的 自动化用例工程化模型 分享

孟德 · 2017年04月25日 · 最后由 chenhuipeng1 回复于 2018年08月28日 · 2463 次阅读

源码地址 :https://github.com/mengde0077/macaca_uiRecorder_demo.git

不太会写,就简单直接的描述一下!希望对初学者上手会有些帮助;
其中还有许多需要完善和补充后面会陆续进行改进提交, 希望有同学能参与 补充完善!

首先需要感谢,macaca 项目组的各位大神 提供的这一系列的 测试工具; 感谢!

模型简介:
1.可以录制;
2.可以回放;
3.可以固化用例,多层分离管理!

简单操作方式如下:
录制:
uirecorder start --mobile sample/test.IOS3.js

回放:
source run.sh sample/test.IOS3.js

执行固化用例:
source run.sh testsuite/test.B2C.3.0.4.js

固化用例采用 4 层分离模式:
第一层: 页面元素数据层;
第二层:页面操作层;
第三层:用例层,或耦合关键字层;
第四层:suite 层,业务场景层;

核心:
通用方法 js 模块;
commons.js

接入手机自动识别、app 启动、用例执行、用例截图 等方法,这个是从录制脚本中抽离出来的,只多加了 reuse 参数 可以配置要不要预装 app;
commBrowser.js
浏览器或手机操作方法;
commElem.js
被测元素对象公共操作方法;
commShould.js
公共断言方法;

分层结构如下图:

第一层:元素数据层
存放,元素对象的 xpath id 等;

如:

exports.btnLoginPar = {
        androidElem: '//*[@resource-id="com.ndol.sale.starter:id/btn_login"]',
        iosElem: '//*[@name="登录/注册"]',
        elementDesc: '登录/注册',
        waitTime: 30000
};

// 商品分类 ,商品列表中 商品的 名称
exports.goodsNamePar = {
        androidElem: '//*[@resource-id="com.ndol.sale.starter:id/projuctTitle"]',
        elementDesc: '商品名称'
};

第二层:页面操作层
存放,对页面元素的操作方法;

如:

exports.swipeGoodsName = function (type, index){commElem.waitElemAndSwipe(goodsPageUI.goodsNamePar, type, index, 5);};
exports.swipeGoodsNameLastToFrist = function (){
    commElem.waitElemAndSwipeLastToFrist(goodsPageUI.goodsNamePar);
};

第三层:用例层,或耦合关键字层

如:

exports.goodsListSwipeTest = function(){
    //适用版本  3.0.0--3.0.4 
    commBrowser.testCaseDoc("验证商品列表 向上滑动,隐藏 列表上方 tab 和 下方 tab ");
    unitPage.clickGoods();
    goodsPage.clickCategoryXXSP();
    // goodsPage.getGoodsNameSize();    //打印出 元素对象 的坐标信息
    goodsPage.swipeGoodsName('up', 1);   //将第2个商品向上滑动
    // commBrowser.swipeOnce(600,1000,600,400);   //滑屏
    commShould.elemCanNotFind('全部');    // 全部 等 上方 tab 被隐藏
    commShould.elemCanNotFind('分类');    // 分类 等 下方 tab 被隐藏
    goodsPage.swipeGoodsName('down', 1);   //将第2个商品向下滑动
    commShould.elemCanFind('全部');    // 全部 等 上方 tab 显示
    commShould.elemCanFind('分类');    // 分类 等 下方 tab 显示

};

exports.loginTest = function(){
    //适用版本  3.0.0--3.0.4 
    commBrowser.testCaseDoc("登录流程 ");
    unitPage.clickMine();   //点击 “我的”
    minePage.clickBtnLogin();   //点击注册登录
    minePage.login(userName, password);
    minePage.clickIvAvatar();   //点击 “我的资料”
    unitPage.clickArrowBack();      //返回 我的
};

第四层:suite 层,业务场景层
启动被测 app,运行测试前准备,执行用例 等;

var commons = require('../commons/commons');
var rootPath = commons.rootPath;
var appPath = rootPath + '/app/b2c_v3.0.4_112.apk';
function testcase(){
    var b2c_mine = require('../testcase/mineTest');
    var b2c_goods = require('../testcase/goodsTest');
    var b2c_order = require('../testcase/orderTest');
    var b2c_home = require('../testcase/homeTest');


    // app安装完成,开始执行用例***************************
    b2c_home.unloginDismissCouponTest();
    b2c_home.unloginStatusToLoginTest();
    b2c_order.unloginStatusToLoginTest();
    b2c_mine.unloginStatusToLoginTest();
    b2c_mine.userSettingTest();
    b2c_goods.goodsListSwipeTest();
    b2c_goods.findGoodsByName();
    b2c_goods.addCartTest();
    b2c_goods.addCartTest2();   
    // ************登录后用例****************************
    b2c_mine.loginTest();
    b2c_mine.getUserInfo();
    b2c_mine.loginStatusToLoginTest();
    b2c_order.allOrderListTest();

    // // ************退出登录****************************
    b2c_mine.loginOffTest();

    // 用例执行结束**************************************
}


if(module.parent && /mocha\.js/.test(module.parent.id)){
    commons.runThisSpec(appPath, testcase);
}

function callSpec(name){
    try{
        require(rootPath + '/' + name)();
    }
    catch(e){
        console.log(e)
        process.exit(1);
    }
}


通用操作方法
如:

/*
 *等待 对象的出现 ,向指定 方向 拖拽
 * @param androidElem 安卓对应的元素对象的获取方式 及 值 
 * @param iosElem ios对应的元素对象的获取方式 及 值 
 * @param elementDesc 控件描述文案
 * @param waitTime 等待元素对象刷新的 等待时间
 * @param type 滑动的方向 'up'、down'、'right'、'left'
 * @param index 元素的位标 从 0 开始
 * @param times 移动距离(宽或高)的倍数,默认1
 */
exports.waitElemAndSwipe = function (parameter,type,index,times){
    var androidElem = parameter.androidElem;
    var iosElem = parameter.iosElem?parameter.iosElem:parameter.androidElem; //如果没有传ios的默认使用Android的
    var elementDesc = parameter.elementDesc;
    var waitTime = parameter.waitTime?parameter.waitTime:5000;
    var index = index || 0;
    var times = times || 1;
    var type = type || 'up';
    it('拖拽: 将第 ' + index + '' + elementDesc + ' 元素,向' + type + '拖拽' + times + '个自身高度或宽度', function (){
        if(platformName === 'Android'){
            return driver.wait(androidElem, waitTime).get(index)
                        .rect()
                        .then(_get_element_rect)
                        .then(function(par){
                            // console.log(par);
                            return _swipeElemByType(par, type, times);
                        });
        }else{
            return driver.wait(iosElem, waitTime).get(index)
                        .rect()
                        .then(_get_element_rect)
                        .then(function(par){
                            // console.log(par);
                            return _swipeElemByType(par, type, times);
                        });
        };
    });
};


/*
 * 在当前页面 对 textAffirm 进行 未显示 断言
 * @param androidElem 安卓对应的元素对象的获取方式 及 值 
 * @param iosElem ios对应的元素对象的获取方式 及 值 
 * @param elementDesc 控件描述文案
 * @param waitTime 等待元素对象刷新的 等待时间
 * @param textAffirm 断言
 */
exports.elemCanNotFind = function (textAffirm){
    it('不存在断言:当前页面不存在文本元素对象 "' + textAffirm + '"', function (){
        if(platformName === 'Android'){
            return driver.find('//*[@text="' + textAffirm + '"]')
                .should.is.empty;    //判断当前页面没有找到该元素对象
        }else{
            return driver.find('//*[@name="' + textAffirm + '"]')
                .should.is.empty;
        };
    });
};

在后边说一些题外话!可以忽略!
开始使用 uiRecorder 录制,是为了 方便学习,后来发现录制完之后,用例显得特别臃肿庞大,
而且大量冗余,不方便工程化,就试着将录制的脚本进行固化、工程化,然后慢慢的就有了下面这些东西;

使用过程中 发现 对 IOS 的录制和回放 相对 Android 要慢太多,希望各位大神 持续改进,好跟着喝汤!

所以模型中的东西,虽然是针对 Android 和 IOS 做方法统一,但实际上大部分只能 Android 跑通;

当然 我 用这一套的目的,也并不是要用来做 严谨的 业务逻辑 UI 自动化验证(这一块主要通过接口自动化实现,已大量覆盖),
主要的目的是做一些:
日常 功能及功能页面巡检(每天定时跑个几次);
兼容性,就是接上不同的机型 各跑几次;
验证 一些 前端处理的简单逻辑 ;
等等

项目中 附带被测 app(发现有 BUG 请告诉我 嘿嘿)

共收到 7 条回复 时间 点赞

大佬能分享一下 ios 是怎么跑起这个的吗?我总是报 socket hang up 的错误

@yxys01 不好意思 我已经由一段时间没有研究 macaca 了 ,我们项目中是用的 appium , 不过看你报的这个错 ,是网络连接方面的问题,我建议你 把详细的错误日志 抓出来,去问一下 开发者!

最近用 uirecorder 学做 ui 自动化测试 学习了 谢谢

@bingxueDU 很高兴 能给你带来帮助! 不过我的代码有些过时了,思路还是可以借鉴!

孟德 回复

现在还有在用 uirecorder 吗?

@bingxueDU 我其实也就是体验性质的使用 , 公司暂时的项目情况还用不起来! 而且 之前 升级 2.0 的时候 uirecorder 本身变化也很大 。 现在暂时没有用, 你可以使用录制来获取 元素对象的 xpath 还是很方便,uirecorder 抽取的很不错 !

你好,我想问下关于 uirecorder 使用的问题,请问 1.安卓录制怎么直接打开应用为不是每次都安装呢 2.run 用例的时候遇到 error 和断言 fail 就会停止,不继续执行,但是 macha 回放脚本就会全部执行,但是回放脚本不生成报告,请问可以设置吗

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