禪道測試報告匯出 -- 油猴指令碼

杨腾發表於2024-05-20

前言

公司正在使用的專案管理工具是禪道 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();
})();

相關文章