// ==UserScript== // @name 自动化用例失败分析 // @namespace https://orthogonalandparallel.github.io/ // @version 1.0 // @description 自动化用例失败分析 // @author JinChen // @match *://*/* // @grant GM_xmlhttpRequest // @license MIT // @match https://c2.yonyoucloud.com/iuap-yyc-yontest/task/log/execApiLog // ==/UserScript== (function() { 'use strict'; // 创建样式 const style = document.createElement('style'); style.textContent = ` /* 毛玻璃拖动框 */ .custom-glass { position: fixed !important; top: 500px; left: 500px; transform: translate(-50%, -50%); height: 300px; width: 500px; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); box-shadow: inset -0.75px -0.5px rgba(255, 255, 255, 0.1), inset +0.75px +0.5px rgba(255, 255, 255, 0.025), 3px 2px 10px rgba(0, 0, 0, 0.25), inset 0px 0px 10px 5px rgba(255, 255, 255, 0.025), inset 0px 0px 40px 5px rgba(255, 255, 255, 0.025); position: relative; border-radius: 5px; overflow: hidden; z-index: 9999; } .custom-drag-me { display: flex; align-items: center; justify-content: center; height: 44px; background-color: rgba(12, 13, 14, 0.75); color: rgba(255, 255, 255, 1); cursor: move; } .custom-close-btn { position: absolute; right: 10px; top: 10px; width: 24px; height: 24px; border-radius: 50%; background-color: rgba(255, 255, 255, 0.2); color: white; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 16px; transition: background-color 0.3s; } .custom-close-btn:hover { background-color: rgba(255, 255, 255, 0.3); } .custom-fix-btn { position: absolute; left: 50%; bottom: 10px; transform: translateX(-50%); padding: 8px 16px; border-radius: 4px; background-color: rgba(12, 13, 14, 0.75); color: white; cursor: pointer; font-size: 14px; transition: background-color 0.3s; border: none; outline: none; } .custom-fix-btn:hover { background-color: rgba(255, 9, 42, 0.8); } .custom-content { padding: 10px; height: calc(100% - 44px); overflow: hidden; position: relative; } .custom-content textarea { width: 100%; height: calc(100% - 50px); resize: none; border: none; outline: none; background: #0000000a; } `; document.head.appendChild(style); // ------- 页面元素 begin ------- const domainUrl = 'https://c2.yonyoucloud.com' // https://c2.yonyoucloud.com https://yct.yyuap.com let glass = null; let recordListCache = [] // 创建并显示结果窗口 function createAndShowResultWindow() { if (!glass) { // 创建毛玻璃窗口 glass = document.createElement('div'); glass.className = 'custom-glass ignore'; const dragMe = document.createElement('div'); dragMe.className = 'custom-drag-me'; dragMe.textContent = '失败用例结果分析'; glass.appendChild(dragMe); // 添加关闭按钮 const closeBtn = document.createElement('div'); closeBtn.className = 'custom-close-btn'; closeBtn.textContent = '×'; closeBtn.addEventListener('click', () => { glass.style.display = 'none'; }); dragMe.appendChild(closeBtn); // 创建内容区域 const content = document.createElement('div'); content.className = 'custom-content'; // 创建文本框 const textarea = document.createElement('textarea'); textarea.id = 'output'; textarea.rows = 20; textarea.cols = 50; textarea.readOnly = true; textarea.style.resize = 'none'; // 添加一键错误修正按钮 const fixBtn = document.createElement('button'); fixBtn.className = 'custom-fix-btn'; fixBtn.textContent = '一键错误修正'; fixBtn.addEventListener('click', () => { // TODO: 在这里添加错误修正的逻辑 console.log('点击了一键错误修正按钮'); const cookie = '_WorkbenchCross_=Ultraman; loginLocale=en_US; debugger=1; yht_username_yht-server=ST-154678960-xuMsgRupSiSg2hvz9OOb-online__2a063dd6-7931-41b2-85ff-45e2939cf1d9; yht_username=ST-154678960-xuMsgRupSiSg2hvz9OOb-online__2a063dd6-7931-41b2-85ff-45e2939cf1d9; yht_usertoken_yht-server=SybCRs2Eg9U4OULbU11JBmIHNZBihcD9b%2B6PVXzn8lpR%2F161ZY9mG1ELL9RSdlS3jdQhvZTT4t4fIHwYhnzOzA%3D%3D; com.yonyou.yht.web.utils.CookieLocaleResolver.LOCALE=zh-CN; JSESSIONID=0000GX90I1E889KhrnmODO1ryBs8n9SlRcaujDIuyTQfK0wLe9Oavgs8bMJCUecXniC7:896ae5b3-05b8-4074-8c42-5cc3dd0627fd; yht_access_token=bttTmUwUUlXR1FEUmI3TU5VQnRYeWxvQWRmT0pxY2V4Vk8xN0VBYU1rRlR2NHk5VlpoWXd4WkR0VFBqWmFlTzN1dV9fZXVjLnlvbnlvdWNsb3VkLmNvbQ..__017ae6a003622bb3e71e7eb6037a3a7d_1748590319366TGTGdccore0iuap-apcom-workbencha829b8cbYT; multilingualFlag=true; timezone=UTC+08:00; language=001; locale=zh_CN; orgId=""; defaultOrg=""; tenantid=jqilbhs8; theme=""; languages=1_3-2_1-3_1; newArch=true; sysid=diwork; defaultLocale=zh_CN; a00=LKIyf97QgNYvyiIPKKrFHo0PVn4ped79N_9TzbB7KmhqcWlsYmhzOGAzMzkzNzA3MzE1NTQ5Nzc2YGpxaWxiaHM4YDJhMDYzZGQ2LTc5MzEtNDFiMi04NWZmLTQ1ZTI5MzljZjFkOWAxYGBlOTlkYjNlNzkwOWJgYGAyMjA0MDY2NzMyNzE5OTk2OTMzYGZhbHNlYGAxNzQ4NTkwMzE5MzgxYHltc3NlczplZTY4YzFhODljNjM4NGMwNTNlZTgzYTYwYjQ4NTZiMGBkaXdvcmtg; n_f_f=false; c800=dccore0; yht_username_diwork=ST-1559404-rrm4fdncbepG3nbERcyH-online__2a063dd6-7931-41b2-85ff-45e2939cf1d9; yht_usertoken_diwork=rLn%2BpJQPLrrhNdQKujzNPdo%2F%2FL%2BK1DyYHRa6WYOCg3n%2FPFIA%2B%2FkHAzcp0JUG3kjRRJlFouWIMBcvV6%2BjyKwiUA%3D%3D; acw_tc=1a0c638e17485910289983293e0073f84d0e84008bbb3b099e471e387df263; a10=MjgxNDQ4MDIyNzg3NjgxMjM1NDE; XSRF-TOKEN=MDF_D1R5FGJLF9MQJ1S342ETB8XDO!160306; UBA_LAST_EID=shdklaair1f8'; // 创建接口映射表 const interfaceMap = new Map(); // 用例ID let testCaseId = recordListCache[0].ext.testCaseId // 列出用例接口 let listInterfaceUrl = domainUrl + '/iuap-yyc-yontest/case/interface/apiCase/detail?id=' + testCaseId + '&billnum=yontest_caseTreeTable&serviceCode=YYCTCW&terminalType=1&fromYonyou=true&busiObj=yontest_case&locale=zh_CN&isDistinct=true' let listInterfaceResp = sendGetRequest(listInterfaceUrl, cookie) // 构建映射关系 let listInterfaceRespObj = JSON.parse(listInterfaceResp) if (listInterfaceRespObj?.data?.interfaceVoList) { for (const interfaceItem of listInterfaceRespObj.data.interfaceVoList) { interfaceMap.set(interfaceItem.title, interfaceItem.id); } } // 遍历用例接口列表 for (const recordCache of recordListCache) { // 用例ID testCaseId = recordCache.ext.testCaseId // 接口名称 let interfaceName = recordCache.name /* if (interfaceName !== '大供应链_iuap-api-gateway_yonbip_QMS_QIT_inspectorder_update_更新(update)_250526135624803') { continue } */ // 接口ID const caseInterfaceRefId = interfaceMap.get(interfaceName); // 接口断言结果列表 let errorDescArr = [] let interfaceAssertResultList = recordCache.assertResultVo.interfaceAssertResultList for (const interfaceAssertResult of interfaceAssertResultList) { let desc = interfaceAssertResult.desc.replace(/\(脚本断言\)/, '') // 状态码(脚本断言) let result = interfaceAssertResult.result // 是否成功 true/false if (!result) { errorDescArr.push(desc) } } if (errorDescArr.length == 0) { continue } // 编辑脚本 let openScriptUrl = domainUrl + '/iuap-yyc-yontest/case/api/script/list?refId=' + caseInterfaceRefId + '&billnum=yontest_caseTreeTable&serviceCode=YYCTCW&terminalType=1&fromYonyou=true&busiObj=yontest_case&locale=zh_CN&isDistinct=true&domainKey=iuap-yyc-yontest'; let openScriptResp = sendGetRequest(openScriptUrl, cookie) let openScriptRespObj = JSON.parse(openScriptResp) // 脚本ID let scriptId = openScriptRespObj.data['2'].id let script = openScriptRespObj.data['2'].script const updatedScript = processScript(errorDescArr, script); console.log('脚本 -- {}' + updatedScript) let scriptEncode = utf8ToBase64(updatedScript); const postData = { "1": { "domainKey": "iuap-yyc-yontest", "type": 2, "caseInterfaceRefId": caseInterfaceRefId, "caseId": testCaseId, "script": "", "runStage": 1 }, "2": { "domainKey": "iuap-yyc-yontest", "type": 2, "caseInterfaceRefId": caseInterfaceRefId, "caseId": testCaseId, "script": scriptEncode, "runStage": 2, "id": scriptId } }; const url = domainUrl + '/iuap-yyc-yontest/case/api/script/save?refId=’ + caseInterfaceRefId + ‘&billnum=yontest_caseTreeTable&serviceCode=YYCTCW&terminalType=1&fromYonyou=true&busiObj=yontest_case&locale=zh_CN&isDistinct=true'; let saveResp = sendPostRequest(postData, url, cookie); let saveRespObj = JSON.parse(saveResp) // alert(interfaceName + '------' + saveRespObj.message); let container = document.getElementById('notification-container'); if (!container) { container = document.createElement('div'); container.id = 'notification-container'; container.style.cssText = 'position: fixed; right: 20px; top: 20px; max-height: 80vh; overflow-y: auto; z-index: 9999;'; document.body.appendChild(container); } const messageElement = document.createElement('div'); messageElement.textContent = interfaceName + '------' + saveRespObj.message; messageElement.style.cssText = 'margin-bottom: 10px; padding: 15px; background: #f0f0f0; border-radius: 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.2);'; container.appendChild(messageElement); setTimeout(() => messageElement.remove(), 3000); } }); // 将元素添加到内容区域 content.appendChild(textarea); content.appendChild(fixBtn) // 将内容区域添加到毛玻璃窗口 glass.appendChild(content); const topBody = document.body; topBody.appendChild(glass); // 初始化拖拽功能 initDragFeature(dragMe, glass); } // 显示窗口 glass.style.display = 'block'; } // ------- 页面元素 end ------- // ------- 解析用例数据 begin ------- function utf8ToBase64(str) { return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function toSolidBytes(match, p1) { return String.fromCharCode('0x' + p1); } )); } // 处理接口返回数据 function processApiResponse(response) { let msg = []; let recordList = response.data.recordList; recordListCache = recordList for (const record of recordList) { msg.push('\n' + record.name + '\n'); let interfaceAssertResultList = record.assertResultVo.interfaceAssertResultList; for (const interfaceAssertResult of interfaceAssertResultList) { if (interfaceAssertResult.result == false) { msg.push(interfaceAssertResult.desc + ' -- ' + '期望值:' + interfaceAssertResult.value + ' -- ' + '实际值:' + interfaceAssertResult.actual); } } } createAndShowResultWindow(); const outputElement = document.getElementById('output'); outputElement.textContent = ''; // 先清空内容 outputElement.textContent = msg.join('\n'); // 再设置新内容 } // ------- 解析用例数据 end ------- // ------- 拦截用例数据 begin ------- // 拦截原生XHR const XHR = XMLHttpRequest.prototype; const open = XHR.open; const send = XHR.send; // 拦截 open 方法 XHR.open = function(method, url) { this._method = method; this._url = url; return open.apply(this, arguments); }; // 拦截 send 方法 XHR.send = function(postData) { // 只处理目标URL的POST请求 if (this._url.includes('/iuap-yyc-yontest/task/log/execApiLog') && this._method === 'POST') { // 监听请求完成事件 this.addEventListener('load', function() { try { const response = JSON.parse(this.responseText); console.log('【油猴】拦截到的请求数据:', { url: this._url, method: this._method, requestData: postData ? JSON.parse(postData) : null, responseData: response }); // 这里可以添加你的数据处理逻辑 if (response.code === 200) { // 处理成功响应 console.log('【油猴】请求成功,数据:', response.data); processApiResponse(response); } else { // 处理错误响应 console.log('【油猴】请求失败,错误信息:', response.msg); } } catch (error) { console.error('【油猴】数据解析错误:', error); } }); } return send.apply(this, arguments); }; // ------- 拦截用例数据 end ------- // ------- 一键修正 end ------- // 获取第三个参数 function getThreeParam(line) { const regex = /['"]([^'"]+)['"]\);$/; const match = line.match(regex); if (match && match[1]) { return match[1] } else { console.log("未找到匹配项"); } } // 添加注释 function processScript(errorDescArr, script) { const lines = script.split('\n'); const processedLines = []; for (const line of lines) { // 检查是否是 assert 语句 const assertMatch = line.includes('assert'); // 检查是否是注释 const assertMatch2 = line.startsWith("//"); // 匹配 assert 语句,并捕获第三个参数 const threeParam = getThreeParam(line); if (assertMatch && !assertMatch2 && threeParam && errorDescArr.includes(threeParam)) { processedLines.push('// ' + line); // 添加注释 } else { processedLines.push(line); } } return processedLines.join('\n'); } /** * 发送Post请求 * @param {Object} postData - 请求数据体 * @param {string} url - 请求地址 * @param {string} cookie - 认证凭证 */ function sendPostRequest(postData, url, cookie) { const xhr = new XMLHttpRequest(); xhr.open('POST', url, false); // false表示同步请求 xhr.setRequestHeader('Content-Type', 'application/json'); xhr.setRequestHeader('Cookie', cookie); try { xhr.send(JSON.stringify(postData)); console.log('请求成功:', xhr.responseText); if (xhr.status === 403) { alert('权限验证失败,请检查Cookie'); throw new Error('权限验证失败'); } return xhr.response; } catch (error) { console.error('请求失败:', error); throw error; } } /** * 发送Get请求 * @param {string} url - 请求地址 * @param {string} cookie - 认证凭证 */ function sendGetRequest(url, cookie) { const xhr = new XMLHttpRequest(); xhr.open('GET', url, false); // false表示同步请求 xhr.setRequestHeader('Content-Type', 'application/json'); xhr.setRequestHeader('Cookie', cookie); try { xhr.send(); if (xhr.status === 403) { alert('权限验证失败,请检查Cookie'); throw new Error('权限验证失败'); } return xhr.responseText; } catch (error) { console.error('请求失败:', error); throw error; } } // ------- 一键修正 end ------- // ------- 拖拽功能 begin ------- // 初始化拖拽功能 function initDragFeature(dragMe, glass) { let isDragging = false; let currentX; let currentY; let initialX; let initialY; let xOffset = 0; let yOffset = 0; dragMe.addEventListener('mousedown', dragStart); document.addEventListener('mousemove', drag); document.addEventListener('mouseup', dragEnd); function dragStart(e) { initialX = e.clientX - xOffset; initialY = e.clientY - yOffset; if (e.target === dragMe) { isDragging = true; } } function drag(e) { if (isDragging) { e.preventDefault(); currentX = e.clientX - initialX; currentY = e.clientY - initialY; xOffset = currentX; yOffset = currentY; setTranslate(currentX, currentY, glass); } } function dragEnd() { isDragging = false; } function setTranslate(xPos, yPos, el) { el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`; } } // ------- 拖拽功能 end ------- })();