Vue项目自动添加测试id方案

379 阅读5分钟

一、前言

UI 自动化测试人员反馈,我们的 Vue 应用普遍缺乏用于定位节点的唯一标识,这给他们的测试脚本编写带来了很大的困扰。寻找一个节点可能需要嵌套多层选择器。因此,我们需要为 Vue 项目中的页面节点添加唯一标识,以降低在 UI 自动化测试场景下,测试人员编写脚本时获取节点的难度和复杂度。

这种唯一标识在业界通常被称为 test-id,其主要作用如下:

  • data-test-id 属性是通过为 UI 元素提供稳定、唯一的标识符来改进自动化测试的基础工具。这些属性不受界面变化的影响,增强了测试的可靠性。
  • 它们简化了元素的选择过程,支持高效的自动化测试,并促进了开发过程中关注点的分离。
  • data-test-id 属性还促进了开发人员和 QA 工程师之间的协作,并且适用于各种测试方法。

直观的做法是由开发人员为每个操作项手动添加语义化的 test-id,但这种方法不仅过程繁琐、成本高昂,还可能引入代码变更的风险,进而影响业务的稳定性,最终导致得不偿失的结果。

因此,我们期望能够找到一种非侵入式的解决方案,实现 test-id 的自动添加。

本文旨在探讨这一需求,并提供相应的研究与解答。

二、方案要求

  1. 自动
  2. 非侵入式
  3. 考虑移动端的适用性

三、解决方案

3.1 实现效果

image.png

3.2 使用步骤

  1. 安装插件

    yarn add vue-plugin-test-id
    

    这是一个极简的vue plugin插件,用于给所有vue组件的根节点添加测试id。对源码感兴趣的可以瞅瞅:vue-plugin-test-id

  1. 添加测试环境的构建命令

    // package.json
    {
     "scripts": {
         "build:test": "VUE_APP_BUILD_TARGET=test npm run build",
     }
    }
    
  2. 在应用中有条件的使用插件

    // src/main.js
    import testIdPlugin from "vue-plugin-test-id";
    
    if(process.env.VUE_APP_BUILD_TARGET === "test"){
       Vue.use(testIdPlugin)
    }
    
  3. 构建测试环境包

    npm run build:test
    

3.3 优缺点

  1. 有一定的代码侵入性

    但侵入性很小,仅需要在入口安装和使用插件。

    完全无侵入需要在代码编译时添加,但它有致命问题。当我们给组件源码添加唯一标识后,这些组件运行、实例化后的节点ID都是一样的。

  2. 有一定性能开销

    首先我们选用的基于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']
    }
  }
}

优点

  1. 相较于运行时添加,节省了运行时的开销

缺点

  1. 在同一页面中使用多个相同的前端组件(即同一个 Vue 文件),可能会导致生成的 ID 相同,这不符合测试的要求,因为每个操作的 DOM 元素应当具有唯一的 ID。
  2. 需要为项目中的 node_modules 依赖(如 element-ui  组件)添加唯一 ID,但这在解析层面带来了一定的挑战。

5.2 运行时添加

程序运行时,监听页面变化、循环遍历给节点添加唯一标识。

segmentfault.com/a/119000004…

优点:

  1. 规避了编译时给唯一标识添加给组件,渲染后存在标识一样的问题

缺点

  1. 需要监听路由变化事件并循环编辑文档节点,有异步问题和较高的运行时开销

5.3 手工添加

借助vue指令,手工添加

github.com/ChronicSton…

优点

  1. 可以指定id,语义化强
  2. 可以指定添加的位置,相较于运行时添加性能开销少一些
  3. 方案简单,维护成本低

缺点

  1. 侵入式变更,需要批量添加指令
  2. 需要与测试人员约定什么地方加,有新增功能时需要始终关注该事务,有额外的心智负担

六、参考资料