uni-app提供了一批API,这些API可以操控uni-app应用,
包括运行、跳转页面、触发点击等,
并可以获取页面元素状态、进行截图,
从而实现对uni-app项目进行自动化测试的目的。

本功能使用到了业内常见的测试库如jest(MIT协议)。

特性

开发者可以利用API做以下事情:

  • 控制跳转到指定页面
  • 获取页面数据
  • 获取页面元素状态
  • 触发元素绑定事件
  • 调用 uni 对象上任意接口

    平台差异说明

    | App | H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 字节跳动小程序 | QQ小程序 | 快应用 | | :—-: | :—-: | :—-: | :—-: | :—-: | :—-: | :—-: | :—-: | | √(ios仅支持模拟器) | √ | √ | x | x | x | x | x |

目前仅 cli 工程支持。有利于持续集成。
**
推荐使用方式:研发提交源码到版本库后,持续集成系统自动拉取源码,自动运行自动化测试。

暂不支持百度,先忽略百度相关测试代码

创建 cli 工程

  1. # 全局安装vue-cli
  2. $ npm install -g @vue/cli
  3. $ cd ... // 切换到工程保存目录
  4. $ vue create -p dcloudio/uni-preset-vue#alpha my-project

如果之前是HBuilderX工程,则把HBuilderX工程内的文件(除 unpackage、node_modules 目录)拷贝至 vue-cli 工程的 src 目录。
在 vue-cli 工程内重新安装 npm 依赖(如果之前使用了 npm 依赖的话)

cli创建项目时若选择hello uni-app模板,可看到其中已经自带部分测试例。

已有 cli 工程

  1. 更新依赖包 @dcloudio/* >= 2.0.0-alpha-27920200613002
  2. 安装依赖包 @dcloudio/uni-automator

    1. npm install @dcloudio/uni-automator --save-dev
  3. package.json script节点新增命令

    1. "test:h5": "cross-env UNI_PLATFORM=h5 jest -i",
    2. "test:android": "cross-env UNI_PLATFORM=app-plus UNI_OS_NAME=android jest -i",
    3. "test:ios": "cross-env UNI_PLATFORM=app-plus UNI_OS_NAME=ios jest -i",
    4. "test:mp-weixin": "cross-env UNI_PLATFORM=mp-weixin jest -i",
    5. "test:mp-baidu": "cross-env UNI_PLATFORM=mp-baidu jest -i"

    H5平台测试流程

    进入工程目录,安装依赖

  1. npm install puppeteer --save-dev
  1. 注意: 从v3.0.0开始,Puppeteer 开始依赖于Node 10.18.1+
  2. 根据API编写测试的js代码,参考测试用例 API文档见:https://uniapp.dcloud.io/collocation/auto/api 测试文件目录配置见 jest.config.js
  3. 运行测试
  1. npm run test:h5
  1. 测试结果

    1. >> cross-env UNI_PLATFORM=h5 jest -i
    2. ...
    3. Test Suites: 1 passed, 1 total
    4. Tests: 4 passed, 4 total
    5. Snapshots: 0 total
    6. Time: 14.995s, estimated 16s

    更多配置参考 jest.config.js

    App-Android测试流程

  2. 配置全局 adb 环境变量

  3. 配置 Hbuilder 调试基座/自定义基座 android_base.apk 目录,参考 jest.config.js
  4. 创建 cli 工程/现有 cli 工程 切换到工程目录,安装依赖包 adbkit

    1. npm install adbkit --save-dev
  5. 编写测试代码,参考测试用例

  6. 运行测试

    1. npm run test:android

    App-iOS测试流程

    目前仅支持 iOS 模拟器(需要mac电脑安装xcode)

  7. 安装依赖 node-simctl

    1. npm install node-simctl --save-dev
  8. 配置模拟器id,参考 jest.config.js

  9. 配置 Hbuilder 调试基座/自定义基座 Pandora_simulator.app 目录,参考 jest.config.js
  10. 编写测试代码,参考测试用例
  11. 运行测试

    1. npm run test:ios

    微信小程序测试流程

  12. 创建cli项目,同H5平台 (必须配置微信小程序 appid, manifest.json -> mp-weixin -> appid)

  13. 运行测试(如果微信开发者工具无法成功打开项目,请手动打开)

    1. npm run test:mp-weixin
  14. 测试结果

    1. > cross-env NODE_ENV=development UNI_PLATFORM=mp-weixin vue-cli-service uni-build --watch "--auto-port" "9520"
    2. Test Suites: 1 passed, 1 total
    3. Tests: 4 passed, 4 total
    4. Snapshots: 0 total
    5. Time: 14.995s, estimated 16s

    测试示例

    使用 hello uni-app 工程测试 H5 平台

  15. 创建 cli 项目,选择 hello uni-app

    1. $ vue create -p dcloudio/uni-preset-vue#alpha my-hello-uni-app
    2. # 进入项目目录
    3. $ cd my-hello-uni-app
  16. 安装 puppeteer

    1. npm install puppeteer
  17. 创建测试文件 src/pages/tabBar/component/component.test.js,复制下面代码

    1. describe('pages/tabBar/component/component.nvue', () => {
    2. let page
    3. beforeAll(async () => {
    4. // 重新reLaunch至首页,并获取首页page对象(其中 program 是uni-automator自动注入的全局对象)
    5. page = await program.reLaunch('/pages/tabBar/component/component')
    6. await page.waitFor(1000)
    7. })
    8. it('u-link', async () => {
    9. // 检测首页u-link的文本内容
    10. expect(await (await page.$('.hello-link')).text()).toBe('https://uniapp.dcloud.io/component/')
    11. })
    12. it('视图容器', async () => {
    13. // 检测首个 panel 是视图容器
    14. expect(await (await page.$('.uni-panel-text')).text()).toBe('视图容器')
    15. // 检测首个 panel 切换展开
    16. const panelH = await page.$('.uni-panel-h');
    17. // 不能做完全匹配,百度小程序会生成额外的class
    18. expect(await panelH.attribute('class')).toContain('uni-panel-h')
    19. await panelH.tap()
    20. await page.waitFor(500)
    21. // 已展开
    22. expect(await panelH.attribute('class')).toContain('uni-panel-h-on')
    23. })
    24. it('.uni-panel', async () => {
    25. const lists = await page.$$('.uni-panel')
    26. expect(lists.length).toBe(9)
    27. })
    28. it('.uni-panel action', async () => {
    29. const listHead = await page.$('.uni-panel-h')
    30. expect(await listHead.attribute('class')).toContain('uni-panel-h-on')
    31. await listHead.tap()
    32. await page.waitFor(200)
    33. expect(await listHead.attribute('class')).toContain(
    34. 'uni-panel-h',
    35. )
    36. // 展开第一个 panel,点击第一个 item,验证打开的新页面是否正确
    37. await listHead.tap()
    38. await page.waitFor(200)
    39. const item = await page.$('.uni-navigate-item')
    40. await item.tap()
    41. await page.waitFor(500)
    42. expect((await program.currentPage()).path).toBe('pages/component/view/view')
    43. await page.waitFor(500)
    44. // 执行 navigateBack 验证是否返回
    45. expect((await program.navigateBack()).path).toBe('pages/tabBar/component/component')
    46. })
    47. })
  18. 运行测试

    1. npm run test:h5
  19. 测试结果

    1. > cross-env UNI_PLATFORM=h5 jest -i
    2. PASS src/pages/tabBar/component/component.test.js (14.789s)
    3. pages/tabBar/component/component.nvue
    4. u-link (8ms)
    5. 视图容器 (518ms)
    6. .uni-panel (2ms)
    7. .uni-panel action (4447ms)
    8. Test Suites: 1 passed, 1 total
    9. Tests: 4 passed, 4 total
    10. Snapshots: 0 total
    11. Time: 14.995s, estimated 16s

    屏幕截图示例

    1. describe('pages/API/set-navigation-bar-title/set-navigation-bar-title.vue', () => {
    2. let page
    3. beforeAll(async () => {
    4. // 重新reLaunch至首页,并获取首页page对象(其中 program 是uni-automator自动注入的全局对象)
    5. page = await program.reLaunch('/pages/API/set-navigation-bar-title/set-navigation-bar-title')
    6. await page.waitFor(3000)
    7. })
    8. it('.uni-hello-text', async () => {
    9. var image = await program.screenshot({
    10. path: "set-navigation-bar-title.png" // 默认项目根目录
    11. })
    12. console.log(image)
    13. })
    14. })

    更多测试示例见: hello uni-app
    GitHub: https://github.com/dcloudio/hello-uniapp

    jest.config.js

    1. module.exports = {
    2. globalTeardown: '@dcloudio/uni-automator/dist/teardown.js',
    3. testEnvironment: '@dcloudio/uni-automator/dist/environment.js',
    4. testEnvironmentOptions: {
    5. compile: true,
    6. h5: { // 为了节省测试时间,可以指定一个 H5 的 url 地址,若不指定,每次运行测试,会先 npm run dev:h5
    7. url: "http://192.168.x.x:8080/h5/",
    8. options: {
    9. headless: false // 配置是否显示 puppeteer 测试窗口
    10. }
    11. },
    12. "app-plus": { // 需要安装 HBuilderX
    13. android: {
    14. executablePath: "HBuilderX/plugins/launcher/base/android_base.apk" // apk 目录
    15. },
    16. ios: {
    17. // uuid 必须配置,目前仅支持模拟器,可以(xcrun simctl list)查看要使用的模拟器 uuid
    18. id: "",
    19. executablePath: "HBuilderX/plugins/launcher/base/Pandora_simulator.app" // ipa 目录
    20. }
    21. },
    22. "mp-weixin": {
    23. port: 9420, // 默认 9420
    24. account: "", // 测试账号
    25. args: "", // 指定开发者工具参数
    26. cwd: "", // 指定开发者工具工作目录
    27. launch: true, // 是否主动拉起开发者工具
    28. teardown: "disconnect", // 可选值 "disconnect"|"close" 运行测试结束后,断开开发者工具或关闭开发者工具
    29. remote: false, // 是否真机自动化测试
    30. executablePath: "", // 开发者工具cli路径,默认会自动查找, windows: C:/Program Files (x86)/Tencent/微信web开发者工具/cli.bat", mac: /Applications/wechatwebdevtools.app/Contents/MacOS/cli
    31. },
    32. "mp-baidu": {
    33. port: 9430, // 默认 9430
    34. args: "", // 指定开发者工具参数
    35. cwd: "", // 指定开发者工具工作目录
    36. launch: true, // 是否主动拉起开发者工具
    37. teardown: "disconnect", // 可选值 "disconnect"|"close" 运行测试结束后,断开开发者工具或关闭开发者工具
    38. remote: false, // 是否真机自动化测试
    39. executablePath: "", // 开发者工具cli路径,默认会自动查找
    40. }
    41. },
    42. testTimeout: 15000,
    43. reporters: [
    44. 'default'
    45. ],
    46. watchPathIgnorePatterns: ['/node_modules/', '/dist/', '/.git/'],
    47. moduleFileExtensions: ['js', 'json'],
    48. rootDir: __dirname,
    49. testMatch: ['<rootDir>/src/**/*test.[jt]s?(x)'], // 测试文件目录
    50. testPathIgnorePatterns: ['/node_modules/']
    51. }

    注意事项

  20. 如果页面涉及到分包加载问题,reLaunch 获取的页面路径可能会出现问题 ,解决方案如下 :

    1. // 重新 reLaunch至首页,并获取 page 对象(其中 program 是 uni-automator 自动注入的全局对象)
    2. page = await program.reLaunch('/pages/extUI/calendar/calendar')
    3. // 微信小程序如果是分包页面,需要延迟大概 7s 以上,保证可以正确获取page对象
    4. await page.waitFor(7000)
    5. page = await program.currentPage()
  21. 微信小程序 element 不能跨组件选择元素,首先要先获取当前组件,在继续查找

    1. <uni-tag>
    2. <view class="test"></view>
    3. </uni-tag>
    1. // 错误,取不到元素
    2. await page.$('.test')
    3. // 可以取到元素
    4. let tag = await page.$('uni-tag')
    5. await tag.$('.test')
  22. 微信小程序暂不支持父子选择器

  23. 百度小程序选择元素必须有事件的元素才能被选中,否则提示元素不存在
  24. 分包中的页面,打开之后要延迟时间长一点,否者不能正确获取到页面信息