背景
在文章《如何在allure报告中增加新的用例状态》与《js动态向allure报告用例详情页注入状态转换按钮》中分别介绍了如何往原生的allure报告里面增加自定义用例状态与用例状态扭转按钮,但是两篇文章都都是在原始报告文件上进行修改,如果继续增加新的功能,会对原始文件侵入太多,所以本文将介绍一种插件的方式往原生allure报告里面增加自定义功能,减少对源文件的修改的同时方便扩展。
新增功能
新增自定义用例状态新增用例状态扭转按钮与备注输入框新增失败用例缓存生成按钮
实现方案
1. 增加插件
在allure报告插件目录plugins/下创建插件文件夹tailor-made,文件夹下新增文件made.js与made.css,文件内容如下
allure-report/# 【展示】最终生成的可视化报告目录(静态网站)├── index.html# 报告的首页入口(必须通过本地服务打开)├── app.js# 前端核心渲染逻辑与框架资源├── styles.css# 报告的样式文件├── plugins/# 插件目录│ └── tailor-made# 自定义插件目录│ ├── made.js# 实现功能注入│ └── made.css# 注入控件样式made.js
// 获取当前报告idfunctiongetReportId(){// /allure/10001_25_allure-report/ --> 10001_25_allure-reportvarm=window.location.pathname.match(/\/([^\/]+)\/?$/)returnm?m[1]:null;}// 复制到剪贴板functioncopyToClipboard(text){// 动态创建临时输入框(用完即删,不占页面空间)consttempInput=document.createElement('input');tempInput.value=text;document.body.appendChild(tempInput);// 选中+复制(兼容性拉满)tempInput.select();constsuccess=document.execCommand('copy');// 清理临时元素document.body.removeChild(tempInput);// 用原生alert提示结果alert(success?`✅ 内容已复制至剪切板:\n${text}`:'❌ 复制失败,请手动复制');}// 封装一个通用的 POST 请求函数asyncfunctionsendPostRequest(url,data){try{constresponse=awaitfetch(url,{method:'POST',headers:{'Content-Type':'application/json'// 指定提交的数据格式为 JSON},body:JSON.stringify(data)// 将传递进来的参数对象转换为 JSON 字符串});if(!response.ok){thrownewError(`请求失败,状态码:${response.status}`);}// 如果服务器返回的是 JSON 数据,可以解析后返回(如果不需要返回数据,这一步可省略)constresult=awaitresponse.json();returnresult;}catch(error){console.error('POST 请求出错:',error);throwerror;// 将错误继续抛出,交给调用者处理}}// 用例增加状态转换按钮(function(){// 获取当前用例 uidfunctiongetCurrentUid(){// '#categories/db923e8926a341803448fb09fd557b9e/47f029ca0aa9e5e2/' --> db923e8926a341803448fb09fd557b9e 47f029ca0aa9e5e2varm=window.location.hash.match(/#.*\/([^\/]+)\/([^\/]+)/);returnm?[m[1],m[2]]:[null,null];}// 创建按钮functioncreateElement(){varreport_id=getReportId()if(!report_id)return;varuids=getCurrentUid();if(!uids[0]||!uids[1])return;vartargetElement=document.querySelector('.test-result-overview__tags');varnewElement=document.querySelector('.custom-action-box');if(targetElement&&!newElement){// 2. 创建一个容器 div,用来包裹输入框和两个按钮,方便控制布局constcontainer=document.createElement('div');// 给容器加个类名,方便后续写 CSS 样式container.className='custom-action-box';// 3. 创建输入框constinput=document.createElement('input');input.type='text';input.placeholder='请输入内容...';// 设置输入框样式,让它和按钮有点间距input.style.marginLeft='16px';input.style.marginRight='10px';// 4. 创建“标记通过”按钮constpassBtn=document.createElement('button');passBtn.innerText='标记通过';passBtn.type='button';// 加上这一行,防止触发表单默认提交passBtn.style.marginRight='3px';// 绑定点击事件passBtn.onclick=asyncfunction(){text_str=input.value||passBtn.innerText// 1. 准备参数constrequestData={desc:text_str,status:'passed',uid:uids[1],report_id:report_id};try{// 2. 调用封装好的函数,传入接口地址和参数constresult=awaitsendPostRequest('/allure/edit',requestData);// 3. 请求成功并等待执行完成后,提示并刷新页面console.info('状态扭转成功!');location.reload();}catch(error){// 4. 处理请求失败的情况console.error('操作失败,请稍后重试。');}};// 5. 创建“标记失败”按钮constfailBtn=document.createElement('button');failBtn.innerText='已排查';failBtn.type='button';// 加上这一行,防止触发表单默认提交// 绑定点击事件failBtn.onclick=asyncfunction(){text_str=input.value||failBtn.innerText// 1. 准备参数constrequestData={desc:text_str,status:'checked',uid:uids[1],report_id:report_id};try{// 2. 调用封装好的函数,传入接口地址和参数constresult=awaitsendPostRequest('/allure/edit',requestData);// 3. 请求成功并等待执行完成后,提示并刷新页面console.info('状态扭转成功!');location.reload();}catch(error){// 4. 处理请求失败的情况console.error('操作失败,请稍后重试。');}};// 6. 将输入框和按钮依次添加到容器中container.appendChild(input);container.appendChild(passBtn);container.appendChild(failBtn);// 7. 将整个容器插入到目标标签的后面 (afterend 表示在元素自身的后面插入)targetElement.insertAdjacentElement('afterend',container);}return;}// 监听路由变化varlastHash='';functioncheckRoute(){varh=window.location.hash;if(h!==lastHash&&/^#.*\//.test(h)){// #xxxx/ 这样的路由才能匹配成功lastHash=h;setTimeout(function(){createElement();},300);}else{lastHash='';}}// 多种方式监听window.addEventListener('hashchange',checkRoute);setInterval(checkRoute,500);})();// 增加生成缓存按钮(function(){// 等待 DOM 加载完成window.addEventListener('DOMContentLoaded',function(){varreport_id=getReportId()if(!report_id)return;varnewElement=document.getElementById('custom-float-btn');if(newElement)return;// 1. 动态创建按钮元素constbtn=document.createElement('div');btn.id='custom-float-btn';// 修改按钮内容btn.innerHTML='<span style="font-size: 21px;">生成缓存</span>';// 2. 样式(made.css样式文件注入)// 3. 绑定点击事件btn.onclick=asyncfunction(){constrequestData={report_id:report_id};try{constresult=awaitsendPostRequest('/allure/make_cache',requestData);copyToClipboard(`缓存下载地址:${result.downloadURL}\n用例数:${result.caseNum}`)console.info(`缓存下载地址:${result.downloadURL}\n用例数:${result.caseNum}`);}catch(error){// 4. 处理请求失败的情况alert('缓存生成失败,请联系管理员');}};// 4. 将按钮添加到页面 body 中document.body.appendChild(btn);});})();made.css
.alert_status_checked{background:#4ecfca}.bar__fill_status_checked{background:#4ecfca}.label_status_checked{background:#4ecfca}.text_status_checked{color:#4ecfca}.message_status_checked{border-color:#4ecfca}.status-details_status_checked{background:#4ecfca}.status-details_status_checked{border-color:#4ecfca}.status-details_status_checked{background:#4ecfca}.chart__legend-icon_status_checked{background:#4ecfca}.chart__fill_status_checked{fill:#4ecfca}.y-label_status_checked{background:#4ecfca}.n-label_status_checked{color:#4ecfca}#custom-float-btn{position:fixed;right:30px;bottom:30px;padding:12px 24px;/* 药丸按钮需要左右内边距 */background-color:#4CAF50;color:white;border-radius:50px;/* 药丸形状的关键:设置足够大的圆角 */display:flex;align-items:center;justify-content:center;box-shadow:0 4px 8pxrgba(0,0,0,0.3);cursor:pointer;z-index:9999;transition:transform 0.2s ease,background-color 0.2s ease;font-size:16px;font-weight:bold;white-space:nowrap;/* 防止文字换行 */user-select:none;/* 防止双击时选中文字 */}2. 修改原报告文件
app.js 增加用例状态
由于用例状态需要在app.js中显示声明,所以这一点修改无法动态注入,所以只能修改原始文件,可参考《如何在allure报告中增加新的用例状态》中介绍的app.js 文件改动