作者:笙海
前言
最近在工作中开发Canvas相关功能的工具库,需要写测试用例保证每次迭代不影响最终渲染效果。用关键词“canvas e2e test” 在线搜索相关资料后,看到网友们E2E测试方法八仙过海,各显神通,但都满足不了验证渲染结果的目的。后来换个角度去思考,研究了Three.js的E2E在github源码里测试用例,总结出该篇文章。
探索的背景
在浏览了网上大部分关于前端E2E测试文章后,归纳出方案分为两种。
-
Node环境纯数据验证:
-
记录Canvas的API操作记录,验证执行记录,例如: jest-mock-canvas
-
浏览器环境比对验证
-
需要真实浏览器渲染结果,例如: cypress
很明显第一种不能彻底验证渲染结果,第二种比较重,还需要开启浏览器验证,对后续自动化集成有些不友好。
思来想去后,觉得可能是搜索的姿势不对,Canvas的E2E测试应该是存在比较稳定丝滑的方案,要不然开源社区那些基于Canvas的大型可视化项目怎么保证渲染结果的质量呢?
Three.js的E2E方案
提起社区基于Canvas的大型Web项目,很容易可以想出 Three.js ,是基于 Canvas 来执行WebGL的3D渲染。
翻了一下 Three.js 在Github上的官方仓库 github.com/mrdoob/thre… ,找到e2e的测试源码的目录 ./test/e2e 发现 Three.js 的E2E 的测试步骤主要有两步
1. 确定正确期待值: 创建期望正确渲染的截图快照。
-
启动一个静态文件的HTTP服务,加载 Three.js 本地的示例。
-
用无头浏览器(Headless browser) 访问示例,并保存3D正确效果的截图快照。
-
关闭HTTP服务。
2. 比对迭代前后差异:
-
启动一个静态文件的HTTP服务,加载 Three.js 本地的示例。
-
用无头浏览器(Headless browser) 访问迭代后的示例,并将原有正确效果的图片做图片像素的差异匹配。如果匹配结果像素差异度大于 0.5% 就是测试用例失败。
-
关闭静态HTTP服务。
涉及到的npm包
-
puppeteer 提供无头浏览器(Headless browser)
-
jimp 提供图像处理程序
-
pixelmatch 提供图片像素级别的比较
-
serve-handler 提供静态服务操作
实现E2E测试用例
1. 制作正确期待结果截图
const path = require('path');
const http = require('http');
const jimp = require('jimp');
const puppeteer = require('puppeteer');
const serveHandler = require('serve-handler');
const port = 3001;
const width = 400;
const height = 400;
const snapshotPicPath = path.join(__dirname, 'snapshot', 'expect.png');
async function main() {
const server = http.createServer((req, res) => serveHandler(req, res, {
// 这里需要改成所需静态资源目录
public: path.join(__dirname, '..', 'src'),
}));
server.listen(port, async () => {
try {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport( { width: width, height: height } );
await page.goto(`http://127.0.0.1:${port}/index.html`);
const buf = await page.screenshot();
await browser.close();
server.close();
(await jimp.read(buf)).scale(1).quality(100).write(snapshotPicPath);
console.log('create snapshot of screen scuccess!')
} catch (err) {
server.close();
console.error(err);
process.exit(-1);
}
});
server.on('SIGINT', () => process.exit(1) );
}
main();
2. 实现E2E测试
const fs = require('fs');
const path = require('path');
const http = require('http');
const assert = require('assert');
const jimp = require('jimp');
const pixelmatch = require('pixelmatch');
const puppeteer = require('puppeteer');
const serveHandler = require('serve-handler');
const port = 3001;
const width = 400;
const height = 400;
const snapshotPicPath = path.join(__dirname, 'snapshot', 'expect.png');
let server = null;
let browser = null;
describe('E2E Testing', function() {
before(function(done) {
server = http.createServer((req, res) => serveHandler(req, res, {
// 这里需要改成所需静态资源目录
public: path.join(__dirname, '..', 'src'),
}));
server.listen(port, done);
server.on('SIGINT', () => process.exit(1) );
});
it('testing...', function(done){
this.timeout(1000 * 60);
const expectDiffRate = 0.005;
new Promise(async (resolve) => {
browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport( { width: width, height: height } );
await page.goto(`http://127.0.0.1:${port}/index.html`);
const buf = await page.screenshot();
const actual = (await jimp.read(buf)).scale(1).quality(100).bitmap;
const expected = (await jimp.read(fs.readFileSync(snapshotPicPath))).bitmap;
const diff = actual;
const failPixel = pixelmatch(expected.data, actual.data, diff.data, actual.width, actual.height);
const failRate = failPixel / (width * height);
resolve(failRate);
}).then((failRate) => {
assert.ok(failRate < expectDiffRate);
done();
}).catch(done);
});
after(function() {
browser && browser.close();
server && server.close();
});
});
3. 改进E2E测试,让差异可视化
-
用 jimp 把匹配差异的像素标记给保存成图片,方便查看差异
-
第一张图片为预期正确结果。
-
第二张图片加了蓝色的圆环,为错误结果。
-
第三张图片是对比了预期和错误结果后,用红色像素标记出差异点。
修改后的E2E测试代码如下
const fs = require('fs');
const path = require('path');
const http = require('http');
const assert = require('assert');
const jimp = require('jimp');
const pixelmatch = require('pixelmatch');
const puppeteer = require('puppeteer');
const serveHandler = require('serve-handler');
const port = 3001;
const width = 400;
const height = 400;
const snapshotPicPath = path.join(__dirname, 'snapshot', 'expect.png');
const diffPicPath = path.join(__dirname, 'snapshot', 'diff.png');
let server = null;
let browser = null;
describe('E2E Testing', function() {
before(function(done) {
server = http.createServer((req, res) => serveHandler(req, res, {
public: path.join(__dirname, '..', 'src'),
}));
server.listen(port, done);
server.on('SIGINT', () => process.exit(1) );
});
it('testing...', function(done){
this.timeout(1000 * 60);
const expectDiffRate = 0.005;
new Promise(async (resolve) => {
browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport( { width: width, height: height } );
await page.goto(`http://127.0.0.1:${port}/index.html`);
const buf = await page.screenshot();
const actual = (await jimp.read(buf)).scale(1).quality(100).bitmap;
const expected = (await jimp.read(fs.readFileSync(snapshotPicPath))).bitmap;
const diff = actual;
const failPixel = pixelmatch(expected.data, actual.data, diff.data, actual.width, actual.height);
const failRate = failPixel / (width * height);
if (failRate >= expectDiffRate) {
(await jimp.read(diff)).scale(1).quality(100).write(diffPicPath);
console.log(`create diff image at: ${diffPicPath}`)
}
resolve(failRate);
}).then((failRate) => {
assert.ok(failRate < expectDiffRate);
done();
}).catch(done);
});
after(function() {
browser && browser.close();
server && server.close();
});
});
修改后的链路流程图大致如下
完整的E2E测试链路
其他注意点
-
同样的Canvas代码在不同操作系统,通过puppeteer生成的截图会有少量的像素差异,如果要做多系统的E2E测试,需要将不同系统的E2E测试用例区分开来。
前端可视化质量测试的思考
经过上述一番折腾后,顺便学习了一下Three.js所有的测试用例,总结出前端可视化的质量验证一般有以下三个方面。
-
代码质量验证
-
单元测试,模块的颗粒化验证
-
单元测试的代码覆盖率
-
常见可使用工具或npm模块有: jest, mocha 等
-
可视化效果验证
-
E2E测试,渲染结果像素基本验证
-
常见可使用工具或npm模块有: puppeteer + pixelmatch, jest-image-snapshot
-
可视化性能验证
-
基准测试,代码执行性能验证
-
常见可使用工具或npm模块有: benchmark.js
后记
- 探索了一阵子后,发现通过puppeteer + pixelmatch 关键词结合一起搜索资料发现其实已经有个类似能力的jest插件 jest-image-snapshot ,最后总结搜索姿势很重要,o(╯□╰)o!
- 下班后给自己业余开发H5图像处理小玩具加上测试用例 github.com/chenshenhai… 在GitHub Actions试跑成功,再也不用担心图像处理渲染结果的质量啦。感兴趣的小伙伴可以去看看里面的测试用例。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!