最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 从Three.js测试源码探索前端可视化项目的E2E测试方案

    正文概述 掘金(淘系前端团队)   2021-04-12   806

    作者:笙海

    前言

    最近在工作中开发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服务。

    从Three.js测试源码探索前端可视化项目的E2E测试方案

    2. 比对迭代前后差异:

    • 启动一个静态文件的HTTP服务,加载 Three.js 本地的示例。

    • 用无头浏览器(Headless browser) 访问迭代后的示例,并将原有正确效果的图片做图片像素的差异匹配。如果匹配结果像素差异度大于 0.5% 就是测试用例失败。

    • 关闭静态HTTP服务。

    从Three.js测试源码探索前端可视化项目的E2E测试方案

    涉及到的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 把匹配差异的像素标记给保存成图片,方便查看差异

    从Three.js测试源码探索前端可视化项目的E2E测试方案

    • 第一张图片为预期正确结果。

    • 第二张图片加了蓝色的圆环,为错误结果。

    • 第三张图片是对比了预期和错误结果后,用红色像素标记出差异点。

    修改后的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();
      });
    });
    

    修改后的链路流程图大致如下

    从Three.js测试源码探索前端可视化项目的E2E测试方案

    完整的E2E测试链路

    从Three.js测试源码探索前端可视化项目的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试跑成功,再也不用担心图像处理渲染结果的质量啦。感兴趣的小伙伴可以去看看里面的测试用例。

    起源地下载网 » 从Three.js测试源码探索前端可视化项目的E2E测试方案

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元