基于Vue源码中e2e测试实践

article/2025/10/17 0:16:33

您好,如果喜欢我的文章,可以关注我的公众号「量子前端」,将不定期关注推送前端好文~

基于Vue源码中e2e测试实践

  • 前言
  • 技术选型&对Vue的参考
  • Puppeteer测试流程
  • 在Concis中的实践
  • 项目目录整理
  • Concis组件库

在这里插入图片描述

前言

最近半年博主一直在抽空自研一款React组件库Concis,对于测试原本只支持于jest+enzyme单元测试,而单元测试的缺陷是无法模拟用户的一些操作,如表单组件,只能测试到一些组件渲染mount准确性的测试,对于一系列用户操作的模拟测试是无法做到的,因此博主考虑加入e2e测试从而让组件上线后更加健全。

关于单元测试,博主之前整理了一份基于Concis项目自用的总结:

全网最细:Jest+Enzyme测试React组件(包含交互、DOM、样式测试)

技术选型&对Vue的参考

其实博主本身对e2e测试的技术栈比较陌生,于是决定学习一下目前主流框架Vue的e2e测试是如何去做的,学习一下尤大大的技术选型,哈哈哈~

这是github中Vue的test文件夹:

在这里插入图片描述

我们进入到e2e中可以看到,圈起来的都是e2e测试用例,而utils应该就是e2e的初始化用于在每一个测试用例文件执行前初始化的,看一下e2eUtils.ts这个文件:

在这里插入图片描述

好了,技术选型确认完毕,使用的是puppeteer这个包,大概研究一下使用,这里尤大大其实是基于pupeteer一系列page执行性api进行了二次封装并且最终导出,在每一个测试用例中去使用:

在这里插入图片描述
而这些方法博主最后整理了一下,有很详细的备注,具体代码在最后~

Puppeteer测试流程

这里简单介绍一下使用Puppeteere2e测试的整体流程:

  1. 初始化browser实例,构造测试模拟浏览器;
  2. 基于browser实例,新建一个页面实例;
  3. 打开测试url;
  4. 一系列的操作,DOM操作,mock用户;
  5. 关闭页面;
  6. 关闭浏览器;

这里整理了一份入门版简易代码,对照上述流程是这样的:

const testPupeteer = async () => {let brwoser: puppeteer.Browser;let page: puppeteer.Page;brwoser = await puppeteer.launch({                          //打开浏览器headless: false,})await page.goto("http://localhost:8000");                 //page页面跳转测试urlawait page.type('body .testDiv .username', 'username');   //输入用户名await page.type('body .testDiv .password', '123456');     //输入密码await page.click('body .testdiv .login-btn');             //登录
}

可以看到,代码很清晰,由于是真实操作,可能有的操作会涉及网络请求,因此都是异步访问按顺序阻塞执行操作。

代码中模拟了一次用户打开浏览器、打开页面、输入用户名密码、点击登录按钮的一次登录操作,但是pupeteer只提供了模拟操作,并没有对操作进行反馈做判断,这时就需要配合jest的断言来进行e2e整体测试了。

在Concis中的实践

这里博主借鉴了Vue的e2e测试方法,即同样的写了一个setup方法用于初始化pupeteer的起步动作,并且二次封装了一系列方法。

e2eUtils.ts代码如下:

import puppeteer from 'puppeteer';const getExampleUrl = (componentName: string) => {//获取测试组件的页面urlreturn `http://localhost:8000/#/common/${componentName}`;
};
const e2eTestTimeout = 30 * 1000;const setupPuppeteer = () => {let browser: puppeteer.Browser;let page: puppeteer.Page;beforeAll(async () => {browser = await puppeteer.launch({headless: false,});});beforeEach(async () => {page = await browser.newPage();await page.evaluateOnNewDocument(() => {localStorage.clear();});page.on('console', (e) => {if (e.type() === 'error') {const err = e.args()[0];console.error(`Error from Puppeteer-loaded page:\n`, err);}});});afterEach(async () => {await page.close();});afterAll(async () => {await browser.close();});const click = async (dom: string, options?: puppeteer.ClickOptions) => {//点击元素await page.click(dom, options);};const getCount = async (dom: string) => {//获取元素数量return (await page.$$(dom)).length;};const getText = async (dom: string) => {//获取元素文本内容return await page.$eval(dom, (node) => node.textContent);};const getValue = async (dom: string) => {//获取文本框的内容return await page.$eval(dom, (node) => (node as HTMLInputElement).value);};const getHtml = async (dom: string) => {//获取元素的innerHTMLreturn await page.$eval(dom, (node) => node.innerHTML);};const getClassList = async (dom: string) => {//获取元素所有类名return await page.$eval(dom, (node) => [...node.classList]);};const getChildrenCount = async (dom: string) => {//获取子元素数量return await page.$eval(dom, (node) => node.children.length);};const domIsShow = async (dom: string) => {//判断元素是否在document中const display = await page.$eval(dom, (node) => {return window.getComputedStyle(node).display;});return display !== 'none';};const isChecked = async (dom: string) => {//判断多选框是否被选中return await page.$eval(dom, (node) => (node as HTMLInputElement).checked);};const isFocused = async (dom: string) => {//判断元素是否被聚焦return await page.$eval(dom, (node) => node === document.activeElement);};const setValue = async (dom: string, value: string) => {//设置输入框内容const el = (await page.$(dom))!;await el.evaluate((node) => ((node as HTMLInputElement).value = ''));await el.type(value);};const setText = async (dom: string, value: string) => {//设置元素内容const el = (await page.$(dom))!;await el.evaluate((node) => ((node as HTMLElement).innerText = ''));await el.evaluate((node) => ((node as HTMLElement).innerText = value));};const enterValue = async (dom: string, value: string) => {//设置输入框内容后回车const el = (await page.$(dom))!;await el.evaluate((node) => ((node as HTMLInputElement).value = ''));await el.type(value);await el.press('Enter');};const clearValue = async (dom: string) => {//清空输入框内容await page.$eval(dom, (node) => ((node as HTMLInputElement).value = ''));};const timeout = async (time: number) => {//延时return page.evaluate((time) => {return new Promise((r) => {setTimeout(r, time);});}, time);};return {page: () => page,click,getCount,getText,getValue,getHtml,getChildrenCount,getClassList,domIsShow,isChecked,isFocused,setValue,setText,enterValue,clearValue,timeout,};
};export { getExampleUrl, setupPuppeteer, e2eTestTimeout };

尤大大的源码路径是这个:https://github.com/vuejs/vue/blob/main/test/e2e/e2eUtils.ts
以上代码主要做了这些事情:

  1. getExampleUrl函数用于获取测试的url地址,博主是测试组件库,因此就是每次测试单个组件文档的页面地址;
  2. setupPupeteer函数用于初始化测试环境(浏览器、打开页面)并且封装了一系列方法导出;

接下来看一下博主对于Form组件的测试:

import { getExampleUrl, setupPuppeteer, e2eTestTimeout } from '../e2eUtils';describe('form e2e test', () => {const { click, page, getCount, getText, getChildrenCount, setValue, getValue } = setupPuppeteer();const formTest = async () => {await page().goto(getExampleUrl('form'), {waitUntil: 'domcontentloaded',});//基本demo展示Dom测试expect(await getCount('#form-index1 .concis-form .concis-form-item')).toBe(4);expect(await getText('#form-index1 .concis-form .concis-form-item:nth-child(1) .concis-form-item-label',),).toBe('Username');expect(await getText('#form-index1 .concis-form .concis-form-item:nth-child(2) .concis-form-item-label',),).toBe('Post');expect(await getText('#form-index1 .concis-form .concis-form-item:nth-child(3) .concis-form-item-label',),).toBe('');expect(await getText('#form-index1 .concis-form .concis-form-item:nth-child(4) .concis-form-item-label',),).toBe('');//切换布局radioGroup测试expect(await getCount('#form-index2 .concis-radio-group')).toBe(1);//全局禁用测试expect(await getCount('#form-index5 .disabled')).toBe(1);//单行禁用测试expect(await getCount('#form-index7 .concis-form .concis-form-item:nth-child(2) .concis-form-item-disabled',),).toBe(1);//测试添加校验label icon显示expect(await getChildrenCount('#form-index8 .concis-form .concis-form-item:nth-child(1) .concis-form-item-label',),).toBe(1);//校验失败的测试await click('#form-index8 .concis-form .concis-form-item:nth-child(3) .concis-form-item-content',);expect(await getText('#form-index8 .concis-form .show-rule-label')).toBe('必须包含a');//提交后弹窗测试await click('#form-index9 .concis-form .concis-form-item:nth-child(3) .concis-form-item-content',);expect(await getChildrenCount('.all-container')).toBe(1);//测试重置await setValue('#form-index6 .concis-form .concis-form-item:nth-child(1) input', '123');expect(await getValue('#form-index6 .concis-form .concis-form-item:nth-child(1) input')).toBe('123',);await click('#form-index6 .concis-form .concis-form-item:nth-child(8) .concis-form-item-content .concis-button:nth-child(2)',);expect(await getValue('#form-index6 .concis-form .concis-form-item:nth-child(1) input')).toBe('',);};test('test e2e test', async () => await formTest(), e2eTestTimeout);
});

其实有了e2eUtils.ts后,编写测试用例方便了很多,只需要在调用封住好的api基础上加入jest的一些断言,就可以很好的测试组件的交互性。
这里Form的测试都有注释,博主不再依次阐述。

项目目录整理

由于之前只有单元测试,因此需要整理项目目录,整理后的目录如下:

在这里插入图片描述

对于package.json也是可以重新配置:

 "scripts": {"build": "rollup -c ./rollup.config.js","test:unit": "jest ./__tests__/unit",		//单元测试"test:e2e": "jest ./__tests__/e2e"			//e2e测试},

Concis组件库

博主自研Concis组件库已经有半年时间,组件库也是慢慢成型,目前已整合前端组件30+、组件unit/e2e测试、组件库文档、全局配置等等。

在这里插入图片描述

一路人也是就自己一个人,也是真的挺花费功夫的…博主也是需要更多的有兴趣小伙伴可以关注一下参与进来,一起贡献开源项目,建设一个基于Concis的技术社区,相互讨论、学习、帮助,这是博主从最初的兴趣想去实现一个小项目到目前为止一个更大的愿景。

在这里插入图片描述

更具体的可以参考 React组件库Concis,寻求社区中有兴趣的小伙伴加入…

Concis组件库线上链接:http://react-view-ui.com:92
github:https://github.com/fengxinhhh/Concis
npm:https://www.npmjs.com/package/concis

开源不易,欢迎学习和体验,喜欢请多多支持,有问题请留言,如果此文对你有帮助,博主需要你的支持,感谢。


http://chatgpt.dhexx.cn/article/CRpNNKWX.shtml

相关文章

【测试与自动化】介绍-框架-Jest-覆盖率-异步代码-e2e-Vue测试

测试与自动化 软件测试和自动化测试介绍前端自动化测试框架使用Jest编写单元测试统计测试覆盖率使用Jest测试异步代码使用Cypress进行e2e测试测试 Vue 项目 软件测试和自动化测试介绍 了解什么是软件测试,以及自动化方式的测试 什么是软件测试? 定义&am…

AUTOSAR E2E Introduction

E2E – Functional Safety E2E 用于保护安全相关的数据交互,以防止数据在交互过程中因为” FAULT” 导致数据完整性,时效性,合法性出现问题, 比如HW RANDOM FAULT 导致数据被篡改 E2E 的使用可以满足ISO26262 -6 对于数据安全传输的要求, 最高可以满足ASILD 的要求 需要…

【小猫爪】AUTOSAR学习笔记12-功能安全之E2E模块

【小猫爪】AUTOSAR学习笔记12-功能安全之E2E模块 前言1 E2E模块简介2 E2E功能简介2.1 五种保护机制2.2 E2E的状态机2.3 E2E Protection Wrapper2.4 E2E 错误反馈方式 END 前言 从这一节开始,正式步入功能安全专题。这一节先来看一个与Communication Stack强相关的且…

AUTOSAR专项--E2E

1. E2E基本概念 E2E,全称叫做End To End。这个概念的提出是用于保护在ECU运行时与安全相关的数据交换不受通信链路中故障的影响。根据AUTOSAR E2E Protocol Specification描述,基于E2E的通信可以按照如下示例: 很明显,E2E在传输中用于防止MCU硬件的随机失效、通信总…

Vue中如何进行自动化测试与端到端测试(E2E测试)

Vue中如何进行自动化测试与端到端测试(E2E测试) Vue.js是一种流行的前端JavaScript框架,用于构建现代的单页应用程序。在Vue.js中,测试是一个非常重要的主题。测试可以确保代码的正确性,使代码更加可靠和可维护。在这…

前端测试:e2e测试

为什么进行测试 你是否有以下烦恼: 当你加班加点完成一个功能后,提交给测试部,立马返回几个bug 当你修改完bug后,并检查了好几遍,确保无误后,提交给测试部,有返回几个bug …… 对于以上情境…

【AUTOSAR-E2E】-1.1-End-to-End通信保护介绍(Functional Safety功能安全相关)

目录 1 常见的通讯故障以及E2E机制能够检出的通讯故障 2 Functional safety功能安全对通信的要求 3 通信故障的原因 3.1 软件故障 3.2 随机硬件故障 3.3 外部影响、环境压力 4 常见的“E2E通讯保护”解决方案 4.1 无E2E保护的信号数据流示例 4.2 E2E Protection Wrapper解决方案…

Autosar BSW层CAN通讯开发------08(Autosar的E2E开发-----以E2E Profile01为例)

Crc校验在CAN报文中的实际应用介绍: Crc在报文传输过程中的实际应用如下(在汽车中,Crc一般是对8个字节进行校验,目前我接触到的是这样): ECU-A和ECU-B之间进行CAN报文的传输,双方规定ECU-A发出的…

功能安全专题之端到端(E2E) 的通信保护

本文来自AUTOSAR技术资料。 前言 功能安全(Functional Safety)是一项系统特性,由于基于功能安全的设计会影响到系统设计,所以从系统开发初始阶段就要进行考虑。由于软件的复杂度会影响 到功能安全的设计,所以在AUTOS…

AUTOSAR的E2E通信安全

导语:近期项目中遇到一些E2E的诊断故障,涉及到整车需求和AutoSAR配置,对这个概念重新做了下梳理,与大家交流。由于E2E机制比较成熟,本文章内容更多出自于AutoSAR标准、ISO 26262 和相关文献,这里只捡重点和…

E2E通信保护协议学习笔记

E2E通信保护协议学习笔记 最近在做功能安全方面工作,想了解E2E保护的问题。本文试着说明两个点: 功能安全需要考虑通信失效造成的影响,因此E2E通信保护协议被提出,以满足功能安全要求; 简单介绍E2E通信保护协议机制。…

什么是 E2E 保护 ?

安全在每个领域都是一个永恒的话题,汽车也不例外,而随着最近几年汽车电动化、智能化和网联化的发展,汽车安全也越来越受到用户及开发人员的重视,安全的要素也是多方面的,例如用户可能关心在使用车机系统时的隐私安全、…

HVS颜色空间的提取

在日常的图片处理中,常常要把RGB转成HVS,然后再提取色调、亮度、饱合度通道图片进行操作,网上大多介绍都只是提到转换,如果提取都不准确,我这里记录一下准确提取的方法。V,S提取只要将值乘255就可以&#x…

matlab绘制hsv色轮图

% 生成网格 tListlinspace(0,2.*pi,300); rListlinspace(0,1,100); [theta,R]meshgrid(tList,rList);% 角度及半径转换为坐标 Xcos(thetapi).*R; Ysin(theta).*R; Zzeros(size(X));% 构造hsv网格并转换为rgb网格 hsvMeshcat(3,theta./2./pi,ones(size(R)),R); rgbMeshhsv2rgb(h…

图像融合:Exposure Fusion Using Boosting Laplacian Pyramid

Exposure Fusion Using Boosting Laplacian Pyramid 文章目录 Exposure Fusion Using Boosting Laplacian PyramidJND ModelLuminance AdaptationContrast MaskingOverall JND Model A Hybrid Exposure Weight MeasurementLocal Exposure WeightGlobal Exposure WeightJND-Base…

基于主动视觉机制的深度学习--一个综合池化框架

卷积神经网络(CNN)是深度学习的代表算法之一,长期以来被广泛应用于图像识别领域。它是受到了生物处理过程的启发,通过模仿人类视觉系统(HVS)的工作机制,完成各种视觉任务等。但与HVS相比,CNN不能够像人类一样,迅速的分…

第13章:直方图处理

第13章:直方图处理 一、直方图的含义:1. 普通直方图:2. 归一化直方图: 二、绘制直方图:1. 使用Numpy绘制直方图:2. 使用OpenCV绘制直方图:3. 使用掩码绘制直方图: 三、直方图均衡化&…

通过matlab,基于DCT变换,利用hvs实现水印嵌入强度自适应

我真的会谢,为什么我跑出来是一片黑呀?本matlab小白跪求matlab大佬答疑解惑555555感谢 以下是我的程序: % 读取原始图像和水印图像 I imread(lena.bmp); W imread(waterMark.bmp);% 将图像转为灰度图,并将水印调整为与原始图像…

基于HVS 的结构相似性的视频质量评价

清华大学的汪志兵, 廖煜鹏, 汪 博, 秦明海, 林行刚等人在《通信技术》2010年第2期上发表。   HVS 对不同频率分量的敏感性不同,对视频帧不同部分的关注度也不相同。 为了达到更好的效果,论文中引入3 个因…

一种基于HVS特性的视频质量评测方法

本篇论文是由厦门大学的袁飞,黄联芬,姚彦发表于《光电工程》2008年1月刊上。   本文针对视频质量的评测应用,通过在视频帧内图像和帧间图像的处理过程中引入人眼视觉系统(HVS)的主要特性,克服传统PSNR 算法在序列质量检测应用方…