微信小程序 UI 测试方案

准备工作

工具介绍

Node.js

这个解决方案中要求 node 版本大于 v7.6.0,因为 puppeteer 要求最低版本是 v6.4.0 ,但是官方实例中大量使用 async await 新特性,所以需要使用 v7.6.0 或更高版本的 node。

wept

wept是一个微信小程序web端实时运行工具。它的后台使用 node 提供服务完全动态生成小程序,前端实现了 view 层、service 层和控制层之间的相关通讯逻辑。支持 Mac, Window 以及 Linux。

puppeteer

Chrome 团队出品的一款更友好的 Headless Chrome Node API ,用于代替用户在页面上面点击、拖拽、输入等多种操作,常见的使用场景还是应用到 UI 自动化测试,puppeteer 可以对页面进行截图保存为图片或者 PDF,解决爬虫无法实现的一些操作(异步加载页面内容) 。

其他类似的工具:

puppeteer 出来以后,phantomjs 即宣布不再继续开发维护,而 puppeteer 的使用更简单,功能更丰富,所以这里选择了puppeteer

Jest

Jest 是 Facebook 出品的一个测试框架,相对其他测试框架,其一大特点就是就是内置了常用的测试工具,比如自带断言、测试覆盖率工具,实现了开箱即用。

其他类似的工具:

开始配置环境

1
2
npm i -g wept
npm i --save-dev puppeteer jest

踩过的坑

在安装puppeteer有可能会出现以下报错:

1
2
3
4
5
6
7
8
9
10
11
12
ERROR: Failed to download Chromium r508693! Set "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" env variable to skip download.
Error: Download failed: server returned code 502. URL: https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/508693/chrome-win32.zip
at ClientRequest.https.get.response (D:\chromium\node_modules\puppeteer\utils\ChromiumDownloader.js:197:21)
at Object.onceWrapper (events.js:316:30)
at emitOne (events.js:115:13)
at ClientRequest.emit (events.js:210:7)
at HTTPParser.parserOnIncomingClient [as onIncoming] (_http_client.js:565:21)
at HTTPParser.parserOnHeadersComplete (_http_common.js:116:23)
at Socket.socketOnData (_http_client.js:454:20)
at emitOne (events.js:115:13)
at Socket.emit (events.js:210:7)
at ClientRequest.onsocket (D:\chromium\node_modules\https-proxy-agent\index.js:181:14)

原因是安装 puppeteer 时,都会下载 Chromium,然而墙内网络环境不友好可能会导致下载失败。

解决方案

安装puppeteer时直接跳过Chromium的下载:

1
npm install puppeteer --ignore-scripts

手动下载 Chrome 开发版,Win 平台下载链接是https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/508693/chrome-win32.zip

翻开仓库源码可以得知其他下载地址:

1
2
3
4
5
6
const downloadURLs = {
linux: 'https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/%d/chrome-linux.zip',
mac: 'https://storage.googleapis.com/chromium-browser-snapshots/Mac/%d/chrome-mac.zip',
win32: 'https://storage.googleapis.com/chromium-browser-snapshots/Win/%d/chrome-win32.zip',
win64: 'https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/%d/chrome-win32.zip',
};

选择对应平台,将%d替换成具体编号下载即可。

一个测试实例

执行wept

直接在小程序根目录执行wept,然后打开chrome访问http://localhost:3000/#!pages/index/index ,就可以看到小程序运行在chrome上了

img

用 puppeteer 抓取小程序里的内容

新建一个 /test 目录,并增加一个 hello.test.js 测试,直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const puppeteer = require('puppeteer');
const devices = require('puppeteer/DeviceDescriptors');

(async () => {
try {
const browser = await puppeteer.launch({
// 如果是手动下载chromium则需要制定chromium所在目录的地址
// executablePath: '/chromium/chrome.exe'
headless: false
});
// 新建选项卡
const page = await browser.newPage();
// 设置展示设备
await page.emulate(devices['iPhone 6']);
// waitUnitil参数为了保证截图不是白屏
await page.goto('http://localhost:3000/#!pages/index/index', { waitUntil: 'networkidle2' });
// 截图
// await page.screenshot({path: 'example.png'});
// 根据iframe的name属性来获取正确的iframe
const frames = await page.frames();
const weChatFrame = frames.find(f => f.name() === 'view-0');
const outerText = await weChatFrame.evaluate(() => {
const anchors = Array.from(document.querySelectorAll('.container'));
return anchors.map(anchor => anchor.textContent);
});
console.log('the outerText: ', outerText);
// 关闭页面
await browser.close();
} catch (e) {
console.log(e);
}
})();

加入 Jest 来进行测试

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const puppeteer = require('puppeteer');
const devices = require('puppeteer/DeviceDescriptors');

test('should return "Hello WorldHello World"', () => {
(async () => {
try {
const browser = await puppeteer.launch({
headless: false
});
const page = await browser.newPage();
await page.emulate(devices['iPhone 6']);
await page.goto('http://localhost:3000/#!pages/index/index', { waitUntil: 'networkidle2' });
const frames = await page.frames();
const weChatFrame = frames.find(f => f.name() === 'view-0');
const outerText = await weChatFrame.evaluate(() => {
const anchors = Array.from(document.querySelectorAll('.container'));
return anchors.map(anchor => anchor.textContent);
});
console.log('the outerText: ', outerText);
expect(outerText[0]).toBe('Hello WorldHello World');
await browser.close();
} catch (e) {
console.log(e);
}
})();
})

运行效果:

img

其他

此套方案的优点是易于 set up,无需代理,支持目前所有的小程序API,可使用 Chrome 调试。缺点是 wept 项目作者不再继续维护,并且测试环境和正式环境有一定差异。

另外小程序官方也有一个云测试,但是一个开发者 24 小时内只能提交一次。