一、前言
UI 自动化测试人员反馈,我们的 Vue 应用普遍缺乏用于定位节点的唯一标识,这给他们的测试脚本编写带来了很大的困扰。寻找一个节点可能需要嵌套多层选择器。因此,我们需要为 Vue 项目中的页面节点添加唯一标识,以降低在 UI 自动化测试场景下,测试人员编写脚本时获取节点的难度和复杂度。
这种唯一标识在业界通常被称为 test-id
,其主要作用如下:
data-test-id
属性是通过为 UI 元素提供稳定、唯一的标识符来改进自动化测试的基础工具。这些属性不受界面变化的影响,增强了测试的可靠性。- 它们简化了元素的选择过程,支持高效的自动化测试,并促进了开发过程中关注点的分离。
data-test-id
属性还促进了开发人员和 QA 工程师之间的协作,并且适用于各种测试方法。
直观的做法是由开发人员为每个操作项手动添加语义化的 test-id
,但这种方法不仅过程繁琐、成本高昂,还可能引入代码变更的风险,进而影响业务的稳定性,最终导致得不偿失的结果。
因此,我们期望能够找到一种非侵入式的解决方案,实现 test-id
的自动添加。
本文旨在探讨这一需求,并提供相应的研究与解答。
二、方案要求
- 自动
- 非侵入式
- 考虑移动端的适用性
三、解决方案
3.1 实现效果
3.2 使用步骤
-
安装插件
yarn add vue-plugin-test-id
这是一个极简的vue plugin插件,用于给所有vue组件的根节点添加测试id。对源码感兴趣的可以瞅瞅:vue-plugin-test-id。
-
添加测试环境的构建命令
// package.json { "scripts": { "build:test": "VUE_APP_BUILD_TARGET=test npm run build", } }
-
在应用中有条件的使用插件
// src/main.js import testIdPlugin from "vue-plugin-test-id"; if(process.env.VUE_APP_BUILD_TARGET === "test"){ Vue.use(testIdPlugin) }
-
构建测试环境包
npm run build:test
3.3 优缺点
-
有一定的代码侵入性
但侵入性很小,仅需要在入口安装和使用插件。
完全无侵入需要在代码编译时添加,但它有致命问题。当我们给组件源码添加唯一标识后,这些组件运行、实例化后的节点ID都是一样的。
-
有一定性能开销
首先我们选用的基于vue.mixin的实现方案,已经比基于监听页面变化遍历DOM节点生成,轻便很多。
再者使用环境变量,能够保证该插件仅在测试环境运行。
四、实现原理
这里包含两个关键要点。
4.1 如何全局的自动添加
借助vue全局mixin,在组件mounted钩子执行添加属性动作。
mounted() {
try {
const element = this.$el instanceof Element ? this.$el : null;
if (!element || !element.getAttribute(attrName)) {
// 1. 将 Element 元素转换为字符串
const elementString = createElementString(element);
// 2. 计算字符串的哈希值
const hash = sha256(elementString);
// 3. 截取哈希值的前16位作为 test-id 的值
const testId = "test-id-" + hash.toString().slice(0, 16);
// 4. 为元素添加 test-id 属性
element.setAttribute(attrName, testId);
}
} catch (error) {
// 处理错误的逻辑
console.log("元素获取失败,跳过", error);
}
}
4.2 如何保证生成的id,全局唯一且重启应用、更换设备都不会变
从节点中提取其标签名(tagName)、属性(attributes)和文本内容(textContent),并以此信息作为基准调用哈希函数生成唯一标识符。只要这些基准信息保持不变,生成的哈希值也将保持一致。
import sha256 from 'crypto-js/sha256';
function createElementString(element) {
const json = {
tagName: element.tagName,
attributes: {},
textContent: element.textContent
};
Array.from(element.attributes).forEach(attr => {
const name = attr.name;
const value = attr.value;
json.attributes[name] = value;
});
return JSON.stringify(json);
}
// 1. 将 Element 元素转换为字符串
const elementString = createElementString(element);
// 2. 计算字符串的哈希值
const hash = sha256(elementString);
五、其他解决思路
5.1 编译时添加
借助webpack loader插件编译时解析源码,给节点添加唯一标识
// 定义loader
// loader/replace-loader.js
module.exports = function(source) {
console.log("===zhaohp====", source);
return source.replace(/World/g, 'Loader');
};
// 使用loader
// vue.config.js
module.exports = {
// webpack配置
configureWebpack: {
module:{
rules:[
{
test:/.m?js$/,
use: 'replace-loader'
}
]
},
resolveLoader:{
modules: ['./loader']
}
}
}
优点
- 相较于运行时添加,节省了运行时的开销
缺点
- 在同一页面中使用多个相同的前端组件(即同一个 Vue 文件),可能会导致生成的 ID 相同,这不符合测试的要求,因为每个操作的 DOM 元素应当具有唯一的 ID。
- 需要为项目中的
node_modules
依赖(如element-ui
组件)添加唯一 ID,但这在解析层面带来了一定的挑战。
5.2 运行时添加
程序运行时,监听页面变化、循环遍历给节点添加唯一标识。
优点:
- 规避了编译时给唯一标识添加给组件,渲染后存在标识一样的问题
缺点
- 需要监听路由变化事件并循环编辑文档节点,有异步问题和较高的运行时开销
5.3 手工添加
借助vue指令,手工添加
优点
- 可以指定id,语义化强
- 可以指定添加的位置,相较于运行时添加性能开销少一些
- 方案简单,维护成本低
缺点
- 侵入式变更,需要批量添加指令
- 需要与测试人员约定什么地方加,有新增功能时需要始终关注该事务,有额外的心智负担