Name Last Update
_test Loading commit data...
config Loading commit data...
examples Loading commit data...
libs Loading commit data...
proxy-server Loading commit data...
scene-types Loading commit data...
schemes Loading commit data...
test Loading commit data...
.babelrc Loading commit data...
.gitignore Loading commit data...
README.md Loading commit data...
config.json Loading commit data...
package.json Loading commit data...
startup.js Loading commit data...
yarn.lock Loading commit data...

有货app埋点自动化测试

环境安装

  • nodejs 8+
  • 安装Appium Desktop

  • Ios [doc]

    • brew install Carthage
    • brew install libimobiledevice
    • 在appium的安装目录中找到WebDriverAgent项目,目录:/Applications/Appium.app/Contents/Resources/app/node_modules/appium/node_modules/appium-xcuitest-driver/WebDriverAgent,执行目录下的./Scripts/bootstrap.sh
    • 用xcode打开WebDriverAgent,修改bundleId,使用开发者账号自动签发验签文件
    • 使用test模式在真机上运行,把WebDriverAgentRunner安装到手机
    • 在描述文件管理中信任开发者
  • Android

    • android和java开发环境
    • 设置ANDROID_HONME、JAVA_HOME环境变量
    • 已经安装adb
    • 真机连接处理调试模式
  • app开发模式中关闭https和请求代理,并设置网络代理到部署appium的机器

ps: 可以通过npm 安装appium-doctor来检测ios和android环境

开始开发

1. 定义测试场景

/scene-types/scene-types.js中定义测试场景,使用常量来定义的名称。

我们把埋点测试拆分为多个业务场景,比如商品详情页,那么这个场景就是针对从首页进入商品详情页的过程中上报的埋点数据进行匹配测试。

module.exports.IOS_PRODUCT_DETAIL = 'IOS_PRODUCT_DETAIL';
module.exports.IOS_UFO_PRODUCT_DETAIL = 'IOS_UFO_PRODUCT_DETAIL';

module.exports.ANDROID_PRODUCT_DETAIL = 'ANDROID_PRODUCT_DETAIL';
module.exports.ANDROID_UFO_PRODUCT_DETAIL = 'ANDROID_UFO_PRODUCT_DETAIL';

2. 定义埋点匹配规则

在目录/schemes/[ios|android]/中创建测试场景的埋点规则文件并与之前定义的测试场景常量关联,在这个规则文件中定义的规则会测试这个场景中所有埋点上报数据。

/schemes/android/product-detail.js:

const {
  ANDROID_PRODUCT_DETAIL
} = require('../../scene-types/scene-types');

module.exports = {
  [ANDROID_PRODUCT_DETAIL]: [
    {op: 'YB_LAUNCH_APP', name: '启动app', single: true, priority: 12},
    {op: 'YB_PAGE_ENTER', name: '第一次打开页面', equals: { PAGE_ID: 'sy' }},
    {op: 'YB_SHOW_CATEGORY', name: 'YB_SHOW_CATEGORY1', types: {YH_AppChannelBlk: Array}},
    {op: 'YB_PAGE_EXIT', validate: (log) => {
      return log.param.PAGE_ID === 'pl';
    }}
  ]
}

匹配规则支持五种匹配模式并可以自定义匹配优先级:

  • op模式,基础模式,规则必须定义op字段,op字段的值会和上报数据中的op值匹配
  • equals模式,可选,规则定义的equals对象的键值会和上报数据中的param对象中的键值做相等匹配
  • types模式,可选,规则定义的types对象的键值会和上报数据中的param对象中的键的类型做匹配,例如:上面示例中上报数据param.YH_AppChannelBlk是一个Array
  • validate模式,可选,规则定义的validate自定义函数,会在匹配时以参数的形式把上报数据传给这个函数做自定义逻辑的匹配
  • single模式,可选,设置规则single:true,则该规则在一次测试环境上报的数据中只能唯一匹配一次,超过一次则规则匹配失败(用于测试重复上报的场景)

以上匹配模式中:equals、types、validate、single的默认的优先级都是1,也就是说如果定义了这几个模式,在匹配时这些规则的优先级高于未定义的规则。另外可以手动设置priority的方式来定义规则的优先级,数值越高优先级越高。

3. 编写驱动脚本

驱动脚本的主要用途是利用wd这个库创建的webdriver对象来操作android或者ios的app,在操作的过程中app的上报被服务拦截并匹配。

const {describe} = require('../../libs/driver');
const {IOS_PRODUCT_DETAIL} = require('../../scene-types/scene-types');

describe(IOS_PRODUCT_DETAIL, '商品详情页埋点测试', async(driver) => {
  let categoryTab = await driver.waitForElementByXPath('//xxx/xxx');

  await categoryTab.click();
});

具体流程就是通过driver对象来查找app中的元素并点击,触发app的跳转或者交互操作。

常用到的查询api:

  • elementByAccessibilityId('SomeAccessibilityID') 通过ios的AccessibilityId来更快速的查找元素
  • elementById('id')通过android的元素id来更快速的查找元素
  • elementByName('name')通过元素的name来查找元素
  • elementByXPath('xpath')通过xpath来查找元素

更多api可以查看 wd

当app出现一些过渡页面或者数据加载比较慢时,通过以上api找不到元素,可以通过waitFor的方式让查询操作在一个超时时间内不断查找直到找到为止。 所有api都支持waitFor 例如:

waitForElementByXPath('//xxx/xxx', 5000)

waitFor默认超时时间是1秒,可以手动设置。

更多waitFor api可以查看 wd

如果没有配置AccessibilityID或者Id,可以通过Appium Destop的Inspector来查找元素。更多https://www.cnblogs.com/yjlch1016/p/8536380.html

常用到的交互api:

  • click() 点击元素
  • sendKeys('内容') 如果是输入框则自动填入内容

4. 开始测试

android执行:

yarn android

ios执行:

yarn ios

默认会执行所有测试场景,如果需要执行指定某个或者某几个场景则可以通过在命令后加上文件名的方式指定:

yarn android|ios d.test.js d.test.1.js

在执行完成之后服务会自动打开测试报告。

5. 场景编排

默认每个场景都是从app启动开始,到app关闭结束。我们把一次app启动到app关闭的测试过程称为一个case。有些连续的复杂场景我们可以把它拆解成多个子场景,在一个case中可以执行多个场景,在子场景中可以接着同一个case中的上一个场景的操作状态继续执行。

比如:场景1:商品详情页、场景2:添加购物车、场景3:下单

以上三个场景我们可以把它们编排成一个case,在场景1执行完成后app停留在商品详情页,场景2接着上一个场景把这个商品添加到购物车,场景3去购物车结算。

具体如何编排,根目录下config.json:

"cases": {
  "ios": [{
    "name": "商品详情页",
    "scenes": ["IOS_UFO_PRODUCT_DETAIL"]
  }, {
    "name": "购物车",
    "scenes": ["IOS_PRODUCT_DETAIL", "IOS_PRODUCT_CART"]
  }],
  "android": [{
    "name": "商品详情页",
    "scenes": ["ANDROID_UFO_PRODUCT_DETAIL"]
  }, {
    "name": "购物车",
    "scenes": ["ANDROID_PRODUCT_DETAIL", "ANDROID_PRODUCT_CART"]
  }]
}

cases对象中区分ios、android。在数组中定义case并给case命名一个name,在scenes的数组中就是一次case需要执行的场景名称。如果场景没有定义在这个文件则默认这个场景在一个独立的case中执行。