自动化工具 禅道测试报告导出 -- 油猴脚本

杨腾 · 2024年05月20日 · 最后由 杨腾 回复于 2024年11月20日 · 5290 次阅读

前言

公司正在使用的项目管理工具是禅道 17.8,还是开源版。项目的测试管理、用例编写、缺陷录入都在系统上进行,但是开源版本并不支持测试报告的导出,这就会碰到整理测试文档时,缺少测试报告或者需要手工处理的情况。
为了解决这个问题我们编写了一段简单的油猴脚本,在禅道对应的报告页添加了一个导出的按钮,点击后整理页面相关内容形成 Markdown 格式文档后导出到本地,

这边文章主要是分享下油猴脚本和我的导出脚本,希望能帮助遇到同样问题的小伙伴。

油猴脚本

  • 官网和下载地址 它是一款非常流行的免费浏览器插件,网络上俗称:油猴和油猴脚本。作为一款浏览器插件,它的作用是管理安装在浏览器上的所有脚本对象。

安装插件并且编写脚本后,它可以做到:
1.在匹配的网页上自动运行;
2.改善用户界面;
3.绕过限制;
4.一系列自动化的操作。

脚本编写使用的语言是 JavaScript,熟悉自动化的小伙伴可以轻松入手

禅道测试报告导出

禅道的测试报告记录了整个测试过程,包括总结、测试范围、用例,内容详尽。但是开源版本的禅道,是没有导出按钮的,

通过油猴脚本我可以在上边添加一个导出按钮

后续的导出点击按钮就可以直接下载页面的内容,生成一份 Markdown 文件

附上脚本内容

// ==UserScript==
// @name         调试脚本
// @namespace    http://tampermonkey.net/
// @version      2024-05-16
// @description  try to take over the world!
// @author       You
// @icon         https://www.google.com/s2/favicons?sz=64&domain=cdf-hn.com
// @grant        GM_xmlhttpRequest
// @require      https://unpkg.com/turndown@7.1.3/dist/turndown.js
// ==/UserScript==

(function() {
    'use strict';

    const turndownService = new TurndownService();

    // 对纵向列表的简单处理
    function htmlToMarkdownTable(html) {
        // 使用DOMParser将HTML字符串解析为DOM对象
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/html');

        // 报告只需要table的第一个tbody
        const tbody = doc.querySelector('table');
        if (!tbody) return '';

        let markdown = '| ';
        // 遍历表头以获取列标题
        const headers = [];
        tbody.querySelectorAll('tr:first-child th').forEach(th => {
            markdown += th.textContent.trim() + ' | ';
            headers.push(th.textContent.trim());
        });
        markdown += '\n| --- | ';
        // 添加分隔线,每个标题后都加一个
        for (let i = 1; i < headers.length; i++) {
            markdown += '--- | ';
        }
        markdown += '\n';

        // 遍历表格的其余行
        tbody.querySelectorAll('tr:not(:first-child)').forEach(tr => {
            markdown += '| ';
            let colspanCount = 0;
            tr.querySelectorAll('td, th').forEach(cell => {
                if (cell.getAttribute('colspan')) {
                    colspanCount = parseInt(cell.getAttribute('colspan'), 10) - 1; // 减去1,因为第一个单元格已经处理了
                }
                // 如果不是colspan的单元格,或者colspan的结束单元格,添加内容
                if (colspanCount <= 0) {
                    markdown += cell.textContent.trim() + ' | ';
                    colspanCount = cell.getAttribute('colspan') ? parseInt(cell.getAttribute('colspan'), 10) - 1 : 0;
                }
                // 跳过colspan的额外单元格
                else {
                    colspanCount--;
                    markdown += ' | ';
                }
            });
            markdown += '\n';
        });

        return markdown;
    }

    // 绑定按钮点击事件
    function createButtonAndBindEvent() {
        // 创建按钮并添加到页面
        const button = document.createElement('button');
        button.textContent = '导出为 Markdown';
        button.onclick = exportToMarkdown;
        document.querySelector("#mainMenu").appendChild(button)
    }

    // 导出为 Markdown 的函数
    function exportToMarkdown() {
        // 有多个部分的 HTML 内容需要导出,单独记录selector
        const titleSelector = 'title'; // 标题
        const basicSelector = '#basic'; // 基本信息
        const storyAndBugSelector = '#storyAndBug'; // 测试范围
        const tabBuildSelector = '#tabBuild'; // 测试轮次
        const tabCaseSelector = '#tabCase'; // 关联的用例
        const tabLegacyBugsSelector = '#tabLegacyBugs'; // 遗留BUG
        const tabCommentSelector = '#tabComment'; // 总结
        const tabHistorySelector = '#tabHistory'; // 历史记录

        // 获取 HTML 内容
        const titlePart = document.querySelector(titleSelector).innerHTML;
        const basicPart = document.querySelector(basicSelector).innerHTML;
        const storyAndBugPart = document.querySelector(storyAndBugSelector).innerHTML;
        const tabBuildPart = document.querySelector(tabBuildSelector).innerHTML;
        const tabCasePart = document.querySelector(tabCaseSelector).innerHTML;
        const tabLegacyBugPart = document.querySelector(tabLegacyBugsSelector).innerHTML;
        const tabCommentPart = document.querySelector(tabCommentSelector).innerHTML;
        const tabHistoryPart = document.querySelector(tabHistorySelector).innerHTML;

        // 将 HTML 转换为 Markdown
        let markdownPart = turndownService.turndown(titlePart);
        markdownPart += `\n\n`;
        markdownPart += `## 基本信息`;
        markdownPart += `\n\n`;
        markdownPart += turndownService.turndown(basicPart);

        markdownPart += `\n\n`;
        markdownPart += `## 测试范围`;
        markdownPart += `\n\n`;
        markdownPart += htmlToMarkdownTable(storyAndBugPart);

        markdownPart += `\n\n`;
        markdownPart += `## 测试轮次`;
        markdownPart += `\n\n`;
        markdownPart += htmlToMarkdownTable(tabBuildPart);

        markdownPart += `\n\n`;
        markdownPart += `## 关联的用例`;
        markdownPart += `\n\n`;
        markdownPart += htmlToMarkdownTable(tabCasePart);

        markdownPart += `\n\n`;
        markdownPart += `## 遗留的BUG`;
        markdownPart += `\n\n`;
        markdownPart += htmlToMarkdownTable(tabLegacyBugPart);

        markdownPart += `\n\n`;
        markdownPart += `## 总结`;
        markdownPart += `\n\n`;
        markdownPart += turndownService.turndown(tabCommentPart);

        markdownPart += `\n\n`;
        markdownPart += `## 历史记录`;
        markdownPart += `\n\n`;
        markdownPart += turndownService.turndown(tabHistoryPart);
        // 组合 Markdown 内容(这里只是简单地拼接,可以根据需要进行更复杂的处理)
        const combinedMarkdown = `${markdownPart}`;

        // 显示或处理 Markdown 内容
        // 调试使用,将其显示在一个新的 textarea 元素中
        const textarea = document.createElement('textarea');
        textarea.value = combinedMarkdown;
        textarea.style.width = '100%';
        textarea.style.height = '200px';
        document.body.appendChild(textarea);

        // 创建一个 Blob 对象包含 Markdown 内容
        const blob = new Blob([combinedMarkdown], { type: 'text/plain;charset=utf-8' });

        // 创建一个指向 Blob 对象的 URL
        const url = window.URL.createObjectURL(blob);

        // 创建一个隐藏的 <a> 标签用于下载
        const downloadLink = document.createElement('a');
        downloadLink.href = url;
        downloadLink.download = 'exported_content.md'; // 设置下载的文件名
        document.body.appendChild(downloadLink);

        // 触发点击事件开始下载
        downloadLink.click();

        // 清理
        document.body.removeChild(downloadLink);
        window.URL.revokeObjectURL(url);
    }

    // 执行
    createButtonAndBindEvent();
})();
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 4 条回复 时间 点赞

请问是否有其他类似禅道的项目管理平台可以推荐,实践比较不错的?谢谢

gonezee 回复

我用过管理平台,有最佳体验的是 Jira(收费,但有破解版),现在在用的 TAPD(需要收费版才支持测试管理,但免费版已经足以支持日常的项目管理)。

大神请教一下,我借用你的源代码,生成脚本放到浏览器却无法生效,没有生成按钮是为什么呢,我用的是开源版 20.30

huzongquan 回复

注意 UserScript 的注释栏里需要加上

// @match http://*.*.*/*

http://../需要是你网址的通配符

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册