From 2bceed342b2ead0952b9c4bc3f729c1d25c48fe1 Mon Sep 17 00:00:00 2001 From: fanniehuang Date: Fri, 5 Mar 2021 16:38:20 +0800 Subject: [PATCH] =?UTF-8?q?feat(e2e):=20=E6=94=AF=E6=8C=81=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E4=BE=9D=E8=B5=96=E5=AE=89=E8=A3=85=E4=BD=BF=E7=94=A8?= =?UTF-8?q?e2e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit re #80 --- packages/wxa-cli/README.md | 68 +++++++++++++------ .../wxa-cli/src/const/defaultBabelConfigs.js | 16 ++--- packages/wxa-cli/src/tester/e2eTester.js | 2 - .../src/tester/wxa-e2eTest/e2eTestCaseTpl.ejs | 47 ++----------- .../src/tester/wxa-e2eTest/e2eTestSuite.js | 4 +- .../src/tester/wxa-e2eTest/runTestCase.js | 16 ++++- packages/wxa-cli/src/wxa.js | 2 +- 7 files changed, 77 insertions(+), 78 deletions(-) diff --git a/packages/wxa-cli/README.md b/packages/wxa-cli/README.md index 738554c3..400037b1 100644 --- a/packages/wxa-cli/README.md +++ b/packages/wxa-cli/README.md @@ -7,24 +7,32 @@ * 主动操作返回(因无法监听返回事件,所以录制过程中 *点击物理返回键*、*小程序titlebar返回键*、*ios手势返回*等返回操作,暂无法支持) * 已知bug: * 暂无 -* 待优化: +* 待优化&计划: * 不带-r参数时,即回放模式,仅添加元素id(回放测试用例时能找到对应元素),不侵入过多代码(现在会劫持各种tap等事件,植入全局按钮组件) + * 高大上网页版测试报告 + * 【用例复用】:支持用例复用-公共用例不用重复录制(如:登陆模块只需要录制一次,其他用例复用,当登录模块更改时,只需要重新录制一次登录,不需要每个用例都重新录制) + * 【真机】:支持真机 + * 【服务器】:有个公共服务器解决用例执行的问题 * 各种未知bug 2020年3月2日 # 使用手册 ### 安装 -1. `npm i -g wxa-cli2-apple` -2. `npm i -g jest` -3. 项目下安装 `npm i -DE miniprogram-automator looks-same` +* 项目下安装 `npm i -DE miniprogram-automator looks-same wxa-cli2-apple jest` ### 测试脚本录制 -* 微信开发者工具,打开对应项目,勾选`不校验合法域名` -* windows系统,wxa.config.js里配置wechatwebdevtools: 微信开发者工具目录 -* 项目目录下执行`wxa2-apple test --e2e -r`,开启录制模式 -* 开始脚本录制,录制完成后脚本会保存在`__wxa_e2e_test__`目录下 -* 脚本都录制完毕后需执行,`wxa2-apple test --e2e --base ` 回放用例并检查录制内容是否正确,且此次回放的截屏会作为后续回放用例的比较基准,用于判断测试是否通过,`--test=test1,test2`可指定要回放的用例,多个用例逗号分隔 +1. 微信开发者工具,打开对应项目,勾选`不校验合法域名` +2. windows系统,wxa.config.js里增加属性`wechatwebdevtools `,配置微信开发者工具的安装目录 +3. 项目目录下执行`wxa2-apple test --e2e -r`,开启录制模式 +* 用开发者工具打开项目,页面左上角有`结束录制`button,说明已成功开启录制模式 +* 此时与小程序的每一步交互都会录制为脚本,完成操作后,点击`结束录制`,输入用例名,对应脚本保存在`__wxa_e2e_test__`目录下 + +### 进行基准截屏 +脚本录制过程中不会截屏,需要跑一次case脚本,完成基准截屏。若无基准截屏,用例回归时就不知道测试结果是否正确,所以这一步骤是必须的 + +* 脚本录制完毕后,需执行`wxa2-apple test --e2e --base ` 回放用例,检查录制操作是否正确,且此次回放的截屏会作为后续回放用例的比较基准,用于判断测试是否通过 +* 基准截屏存放在`__wxa_e2e_test__/用例名/base_screenshot`中(`--test=test1,test2`可指定要回放的用例,多个用例逗号分隔) ### 测试脚本回放 * 开发者工具修改调试基础库 2.7.3以上(src/project.config.json需同步修改libVersion) @@ -33,18 +41,22 @@ ``` const path = require('path'); const existsSync = require('fs').existsSync; + + +// try to find @babel/runtime to decide whether add @babel/plugin-transform-runtime. const cwd = process.cwd(); const babelRuntime = path.join(cwd, 'node_modules', '@babel/runtime/package.json'); let hasRuntime = existsSync(babelRuntime); const commonConfigs = { + 'plugins': [ ['@babel/plugin-proposal-decorators', {'decoratorsBeforeExport': true}], ['@babel/plugin-proposal-class-properties'], ], 'presets': ['@babel/preset-env'], -} +}; if (hasRuntime) { const pkg = require(babelRuntime); @@ -52,17 +64,21 @@ if (hasRuntime) { } module.exports = { + 'sourceMap': false, + 'ignore': [], overrides: [{ - exclude: [/node_modules/, /wxa-cli/], + exclude: [/node_modules/, /wxa-cli/], ...commonConfigs - } - ,{ + }, + { test: /wxa-e2eTest/, ...commonConfigs - }] -} + } +] +}; + ``` -* `wxa2-apple test --e2e` 进入测试用例回放模式,`--test=test1,test2`指定执行用例,多个用例逗号分隔,操作截屏以时间命名保存在测试用例目录中,带参数`--screenshot`则会与`base_screenshot`的截屏进行diff +* `wxa2-apple test --e2e` 进入测试用例回放模式,操作截屏以时间命名保存在测试用例目录中(`--test=test1,test2`指定执行用例,多个用例逗号分隔) ### 二次开发录制好的测试用例 @@ -72,12 +88,26 @@ record.js是一个数组,每一项Object对应用户一次操作(点击、 |key|类型|默认值|备注| | :-----| :---- | :---- | :---- | | action | Object| 本次操作信息,小程序包装好的事件信息,可查看文档 | 【type:操作类型,tap touchstart点击,input输入】
【currentTarget.dataset._wxatestuniqueid:触发事件的页面元素id】
| -| screenshotDiff | Boolean| false | 每一步操作截屏,是否要和expect_screenshot进行diff比对。启动命令带--screenshot参数时,忽略该配置,都会截屏diff比对。 | +| screenshotDiff | Boolean| true | 此次交互后的截屏,是否要和base_screenshot目录下的首次截屏进行差异比对| | coustomExpect | Function| - | 编写自定义期望条件 | +### wxa2-apple可支持的参数 +* `--no-mock` `wxa2-apple test --e2e --no-mock`此次用例回归,不mock api,直连真实接口(默认会用录制时的api数据来mock) +* `--screenshot-diff` `wxa2-apple test --e2e --screenshot-diff=fasle` 此次用例回归,截屏是否要和base_screenshot目录下的首次截屏进行比对(不传值默认会比对,false不比对) +* `--custom-expect` 进行自定义期望匹配,需要record.js里每一步的customExpect函数编写期望代码 - - +``` +module.exports = [ + { + "action": { + ..... + }, + customExpect() { + //自定义期望匹配函数 + } + },{ + ... +``` # 开发文档 * wxa下增加.vscode/launch.json文件 ``` diff --git a/packages/wxa-cli/src/const/defaultBabelConfigs.js b/packages/wxa-cli/src/const/defaultBabelConfigs.js index 31d5b8ae..cc2513fc 100644 --- a/packages/wxa-cli/src/const/defaultBabelConfigs.js +++ b/packages/wxa-cli/src/const/defaultBabelConfigs.js @@ -7,33 +7,31 @@ const babelRuntime = path.join(cwd, 'node_modules', '@babel/runtime/package.json let hasRuntime = existsSync(babelRuntime); const commonConfigs = { + 'plugins': [ ['@babel/plugin-proposal-decorators', {'decoratorsBeforeExport': true}], ['@babel/plugin-proposal-class-properties'], ], 'presets': ['@babel/preset-env'], -} +}; if (hasRuntime) { const pkg = require(babelRuntime); commonConfigs.plugins.unshift(['@babel/plugin-transform-runtime', {'version': pkg.version || '7.2.0'}]); } -const defaultConfigs = { +export default { 'cwd': path.join(__dirname, '../../'), 'sourceMap': false, + 'ignore': [], overrides: [{ - exclude: [/node_modules/, /wxa-cli/], + exclude: [/node_modules/, /wxa-cli/], ...commonConfigs - } - ,{ + }, + { test: /wxa-e2eTest/, ...commonConfigs } - ] }; - - -export default defaultConfigs; diff --git a/packages/wxa-cli/src/tester/e2eTester.js b/packages/wxa-cli/src/tester/e2eTester.js index 04caaded..faab4fdc 100644 --- a/packages/wxa-cli/src/tester/e2eTester.js +++ b/packages/wxa-cli/src/tester/e2eTester.js @@ -3,7 +3,6 @@ import Schedule from '../schedule.js'; import Compiler from '../compilers/index'; import domWalker from './domWalker.js'; import Server from './server.js'; -import e2eRecord2js from './wxa-e2eTest/e2eRecord2js'; import logger from '../helpers/logger'; import simplify from '../helpers/simplifyObj'; import {applyPlugins, readFile, writeFile} from '../utils.js'; @@ -12,7 +11,6 @@ import {DirectiveBroker} from '../directive/directiveBroker'; import crypto from 'crypto'; import debugPKG from 'debug'; import path from 'path'; -import mkdirp from 'mkdirp'; import runTestCase from './wxa-e2eTest/runTestcase.js' const debug = debugPKG('WXA:E2ETester'); diff --git a/packages/wxa-cli/src/tester/wxa-e2eTest/e2eTestCaseTpl.ejs b/packages/wxa-cli/src/tester/wxa-e2eTest/e2eTestCaseTpl.ejs index 9eab09e7..0484fa1f 100644 --- a/packages/wxa-cli/src/tester/wxa-e2eTest/e2eTestCaseTpl.ejs +++ b/packages/wxa-cli/src/tester/wxa-e2eTest/e2eTestCaseTpl.ejs @@ -91,47 +91,6 @@ for (let j = 0; j < testCaseNameArr.length; j++) { apiMockMap ) <% }); %> - // } - // if (!noMockApi) { - // await miniProgram.mockWxMethod( - // 'request', - // function(reqObj, apiMockMap) { - // let mapKey = `${reqObj.url}__e2e__${reqObj.method}__e2e__${Object.keys(reqObj.data).join(',')}`; - // if (apiMockMap[mapKey] && apiMockMap[mapKey].length > 0) { - // console.log(mapKey, 'mock success') - // return apiMockMap[mapKey].shift(); - // } - // return new Promise(resolve => { - // reqObj.success = res => resolve(res) - // reqObj.fail = res => resolve(res) - // // origin 指向原始方法 - // console.log(mapKey, 'no mock') - // this.origin(reqObj) - // }) - // }, - // apiMockMap - // ) - // } - // // mock modal框 - // await miniProgram.mockWxMethod( - // 'showModal', - // function(config, apiMockMap) { - // let mapKey = `showModal__e2e__title__${config.titie}__e2e__content__${config.content}__e2e__editable__${config.editable}__e2e__showCancel__${config.showCancel}__e2e__confirText__${config.confirmText}`; - // console.log(mapKey) - // if (apiMockMap[mapKey] && apiMockMap[mapKey].length > 0) { - // console.log(mapKey, 'mock success') - // return apiMockMap[mapKey].shift(); - // } - // return new Promise(resolve => { - // reqObj.success = res => resolve(res) - // reqObj.fail = res => resolve(res) - // // origin 指向原始方法 - // console.log(mapKey, 'no mock') - // this.origin(reqObj) - // }) - // }, - // apiMockMap - // ) // 开始回放+截屏 page = await miniProgram.reLaunch(`/${record[0].action.page}`); @@ -153,10 +112,14 @@ for (let j = 0; j < testCaseNameArr.length; j++) { for (let j = 1; j < idPath.length; j++) { element = await element.$(`.${idPath[j]}`) } + let diff = screenshotDiff; + if (record[i-1] && typeof record[i-1].screenshotDiff !== 'undefined') { + diff = screenshotDiff; + } let same = await screenShot({ screenshotDir, screenCount: screenCount++, - diff: screenshotDiff || (record[i-1] && record[i-1].screenshotDiff) + diff }) if (customExpect && i !== 0 && record[i - 1].customExpect && typeof record[i - 1].customExpect === 'function') { record[i - 1].customExpect(); diff --git a/packages/wxa-cli/src/tester/wxa-e2eTest/e2eTestSuite.js b/packages/wxa-cli/src/tester/wxa-e2eTest/e2eTestSuite.js index 2a02680d..ac2362b4 100644 --- a/packages/wxa-cli/src/tester/wxa-e2eTest/e2eTestSuite.js +++ b/packages/wxa-cli/src/tester/wxa-e2eTest/e2eTestSuite.js @@ -167,9 +167,7 @@ let $$testSuitePlugin = (options) => { let originSuccess = config.success; config.success = function() { const res = arguments[0] || {}; - apiRecord.get(key).push({ - ...res - }) + apiRecord.get(key).push(JSON.parse(JSON.stringify(res))) originSuccess.apply(this, arguments); } diff --git a/packages/wxa-cli/src/tester/wxa-e2eTest/runTestCase.js b/packages/wxa-cli/src/tester/wxa-e2eTest/runTestCase.js index 021d97cf..c4969219 100644 --- a/packages/wxa-cli/src/tester/wxa-e2eTest/runTestCase.js +++ b/packages/wxa-cli/src/tester/wxa-e2eTest/runTestCase.js @@ -52,13 +52,25 @@ export default async function(cmd, wxaConfigs) { screenshotPath = timeStamp; } try { + + let screenshotDiff = cmd.screenshotDiff; + if (typeof screenshotDiff === 'undefined') { + if (!cmd.base && !cmd.record) { + screenshotDiff = true; + } else { + screenshotDiff = false; + } + } else if (screenshotDiff === 'false'){ + screenshotDiff = false; + } + let recordString = await testCase2js({ cliPath: cli, testCaseNameArr: JSON.stringify(testCaseNameArr), testDir, screenshotPath, base: !!cmd.base, - screenshotDiff: !!cmd.screenshotDiff, + screenshotDiff: screenshotDiff, noMockApi: !!cmd.noMock, customExpect: !!cmd.customExpect, mockWxMethodConfig @@ -71,7 +83,7 @@ export default async function(cmd, wxaConfigs) { try { - execSync(`jest ${path.join(testDir, '.cache', 'index.test.js')}`, { + execSync(`./node_modules/.bin/jest ${path.join(testDir, '.cache', 'index.test.js')}`, { stdio: 'inherit' }); process.exit(0); diff --git a/packages/wxa-cli/src/wxa.js b/packages/wxa-cli/src/wxa.js index 315c8e5a..ac2c8a7a 100644 --- a/packages/wxa-cli/src/wxa.js +++ b/packages/wxa-cli/src/wxa.js @@ -97,7 +97,7 @@ commander .option('-r, --record', 'e2e测试录制模式,启动小程序自动开始录制') .option('-t, --test [testName]', 'e2e执行测试用例,缺省则执行所有用例,多个用例名用逗号区分') .option('--base', '仅截屏,作为后续回放用例比较基准') -.option('--screenshot-diff', '是否进行截屏比对') +.option('--screenshot-diff [screenshotDiff]', '是否进行截屏比对') .option('--custom-expect', '进行自定义期望匹配,record.js里每一步的customExpect函数编写期望代码') .option('--no-mock', '不mock接口') .action((cmd)=>{