自动化用例失败分析

公司白盒自动化用例失败分析

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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 -------

})();