試報(bào)告生成實(shí)戰(zhàn):mochawesome輸出HTML報(bào)告與合并)
Cypress 跑完測(cè)試只會(huì)看到命令行里一串綠色的通過標(biāo)記如果只把這條輸出給團(tuán)隊(duì)那可太虧了。測(cè)試跑完只是第一步真正關(guān)鍵的是把結(jié)果轉(zhuǎn)成一眼能看出問題、能歸檔、能塞進(jìn)CI流水線的報(bào)告。這篇是Cypress系列的第三十三篇專門聊生成測(cè)試報(bào)告的第一部分——先把最常用的HTML報(bào)告方案講透后續(xù)再補(bǔ)上其他格式與CI深度集成。先說清楚這篇要解決什么問題你的自動(dòng)化測(cè)試已經(jīng)寫了不少在本地跑能通過但項(xiàng)目成員怎么確認(rèn)當(dāng)前版本的回歸結(jié)果歷史用例失敗趨勢(shì)怎么追蹤新同事進(jìn)來如何快速查看最近一次全量測(cè)試的失敗用例這些問題的答案都指向同一個(gè)東西一份漂亮且信息完整的測(cè)試報(bào)告。Cypress 本身默認(rèn)基于 Mocha底層渲染出一份簡(jiǎn)單的“點(diǎn)線報(bào)告”但那種樣式只適合開發(fā)機(jī)上看一跑而過。真正的團(tuán)隊(duì)級(jí)報(bào)告需要更豐滿的數(shù)據(jù)每個(gè)用例的執(zhí)行時(shí)間、失敗棧、截圖與視頻鏈接、斷言詳情等。這篇文章適合三類人剛能把 Cypress 跑起來、但還沒認(rèn)真研究過報(bào)告格式的入門者已經(jīng)在用mochawesome但沒搞定數(shù)據(jù)合并的進(jìn)階選手還有需要在 Jenkins 這類工具里穩(wěn)定產(chǎn)出報(bào)告的工程化實(shí)踐者。下面直接進(jìn)入正題我盡量用實(shí)操時(shí)的經(jīng)驗(yàn)順一遍而不是照著官方文檔念。1. 報(bào)告設(shè)計(jì)思路先想清楚你要的是一份什么樣的報(bào)告很多人一上來就裝mochawesome裝完發(fā)現(xiàn)生成的 JSON 和 HTML 分開又不知道合并最后干脆只保留 HTML。這種用法不能說錯(cuò)但如果你真想用好測(cè)試報(bào)告第一步其實(shí)是問自己這份報(bào)告最終給誰看要承載哪些信息1.1 報(bào)告的使用場(chǎng)景與核心需求拆解個(gè)人開發(fā)時(shí)你需要的可能只是“哪些用例掛了、掛在哪個(gè)斷言”這時(shí)候 Cypress 自帶的終端輸出配合截圖就能湊合。但團(tuán)隊(duì)協(xié)作時(shí)就需要一份包含匯總統(tǒng)計(jì)、失敗用例趨勢(shì)、歷史對(duì)比的報(bào)告最好還能按套件或標(biāo)簽篩選。再往上走CI 流水線里要自動(dòng)歸檔那么報(bào)告文件名就不能帶時(shí)間戳或隨機(jī)值否則每次構(gòu)建都會(huì)覆蓋舊記錄問題追溯無從談起。我在項(xiàng)目里見過最典型的翻車場(chǎng)景報(bào)告生成出來了很好看但有個(gè)同事跑完本地后順手把mochawesome-report目錄提交到代碼倉庫導(dǎo)致合并沖突不斷。這就是典型的沒有設(shè)計(jì)好報(bào)告存放與命名規(guī)則。我的建議是先把報(bào)告的輸出目錄統(tǒng)一固定為test-results并且加入.gitignore任何本地產(chǎn)出的報(bào)告都不入庫只在 CI 服務(wù)端備份或者使用插件上傳到公共平臺(tái)。1.2 Cypress 報(bào)告機(jī)制的本質(zhì)Mocha reporter 的擴(kuò)展邏輯Cypress 的測(cè)試執(zhí)行器沿用了 Mocha 的 reporter 機(jī)制也就是說你在cypress.config.js里配置的reporter字段直接被 Cypress 讀取并傳給內(nèi)置的 Mocha 實(shí)例。這帶來一個(gè)很大的好處生態(tài)里絕大多數(shù)為 Mocha 開發(fā)的 reporter 插件都能在 Cypress 中使用哪怕插件從沒說過自己支持 Cypress。理解了這一點(diǎn)你就知道為什么配置報(bào)告不需要改動(dòng)測(cè)試代碼只需在配置文件中指定reporter和reporterOptions。Cypress 在運(yùn)行時(shí)會(huì)把每個(gè)測(cè)試文件spec單獨(dú)交給 Mocha 跑一遍對(duì)應(yīng)的 reporter 也會(huì)在每個(gè) spec 結(jié)束時(shí)生成輸出。這個(gè)“按 spec 單獨(dú)生成”的特性直接導(dǎo)致了多 spec 場(chǎng)景下報(bào)告文件會(huì)被多次覆蓋或各自獨(dú)立。這也是后面我會(huì)強(qiáng)調(diào)合并數(shù)據(jù)的關(guān)鍵原因。2. 基礎(chǔ)配置reporters 與 reporterOptions 的準(zhǔn)確含義大部分 Cypress 用戶都知道在cypress.config.js里寫reporter: mochawesome但未必理解reporterOptions中每個(gè)字段怎么設(shè)。這一節(jié)先把配置項(xiàng)講明白后續(xù)再給完整示例。2.1 Cypress 配置文件中的 reporter 字段與內(nèi)置選項(xiàng)打開cypress.config.js配置片段一般長這樣const { defineConfig } require(cypress); module.exports defineConfig({ e2e: { specPattern: cypress/e2e/**/*.cy.js, reporter: mochawesome, reporterOptions: { reportDir: cypress/results, overwrite: false, html: true, json: true, charts: true, }, }, });reporter指定使用的 reporter 包名。如果不加任何配置默認(rèn)走的是specreporter也就是終端里列出每個(gè)用例的√或×的結(jié)果。這個(gè)選擇只會(huì)影響輸出形式不影響測(cè)試執(zhí)行行為。reporterOptions則直接傳給 reporter 插件不同插件的配置項(xiàng)不一樣但幾個(gè)常用項(xiàng)在 mochawesome 里很關(guān)鍵reportDir控制輸出目錄overwrite決定是否覆蓋已有文件html和json分別控制是否生成兩種格式的文件charts控制是否在報(bào)告中渲染統(tǒng)計(jì)圖表。有件事得單獨(dú)拎出來說Cypress 的reporterOptions不一定能被所有插件完整讀取。有些 reporter 插件只認(rèn)頂層reporterOptions如果你在某個(gè)測(cè)試文件的before鉤子里動(dòng)態(tài)調(diào)整配置會(huì)不生效。所以盡量保證配置文件的靜態(tài)設(shè)置不要用代碼動(dòng)態(tài)改。2.2 為什么默認(rèn)的報(bào)告不適合團(tuán)隊(duì)協(xié)作三個(gè)致命缺陷先看 Cypress 內(nèi)置specreporter 的幾個(gè)問題。第一個(gè)缺陷沒有統(tǒng)計(jì)匯總。跑完 20 個(gè)文件終端底部會(huì)給出總量、通過數(shù)、失敗數(shù)但你想看每個(gè)文件的“通過率柱狀圖”抱歉沒有。第二個(gè)缺陷歷史比對(duì)困難。今天的報(bào)告和上周對(duì)比只能靠肉眼一行一行看如果測(cè)試數(shù)量從三百條漲到三百五十條很難直觀看出新增了哪些。第三個(gè)缺陷截圖和視頻信息脫離用例。失敗用例在 Cypress 的cypress/screenshots目錄里能翻到 PNG但報(bào)告頁面上看不到快捷入口尤其在 CI 機(jī)器上你不可能遠(yuǎn)程登錄去看文件系統(tǒng)。mochawesome這類報(bào)告器正好補(bǔ)上了這幾個(gè)缺口。它生成的 HTML 頁面自帶統(tǒng)計(jì)面板、用例列表、失敗信息、截圖嵌入通過相對(duì)路徑預(yù)覽還有按套件折疊的視圖。更關(guān)鍵的是它會(huì)把每個(gè)用例的數(shù)據(jù)完整寫到 JSON 里之后想二次處理或生成自定義報(bào)表JSON 就是數(shù)據(jù)源。3. 實(shí)操基礎(chǔ)直接用 mochawesome 生成你的第一份 HTML 報(bào)告前面理論聊得差不多了下面進(jìn)入動(dòng)手環(huán)節(jié)。這一步不難但有幾個(gè)坑我踩過會(huì)在過程中指出來。3.1 依賴安裝與初始配置三步實(shí)現(xiàn)從零到報(bào)告第一步安裝必須的 npm 包。這里要注意比較穩(wěn)妥的組合是mochawesome和mochawesome-mergenpm install --save-dev mochawesome mochawesome-merge第二步在cypress.config.js中寫入 reporter 配置const { defineConfig } require(cypress); module.exports defineConfig({ reporter: mochawesome, reporterOptions: { reportDir: cypress/results/mochawesome, overwrite: false, html: false, json: true, }, });這里我把html設(shè)為false只先生成 JSON。原因后面會(huì)說當(dāng)你有多個(gè) spec 文件時(shí)mochawesome 會(huì)為每個(gè) spec 生成獨(dú)立的報(bào)告而這些報(bào)告會(huì)互相覆蓋。如果在每個(gè)獨(dú)立報(bào)告里都生成 HTML最后你只會(huì)得到最后一個(gè) spec 的 HTML意義不大。正確玩法是先讓每個(gè) spec 生成獨(dú)立的 JSON再用合并工具把 JSON 合成一份完整數(shù)據(jù)最后基于合并后的數(shù)據(jù)生成總報(bào)告。第三步運(yùn)行測(cè)試npx cypress run跑完后進(jìn)入cypress/results/mochawesome目錄應(yīng)該能看到類似.json的文件里面包含每個(gè)用例的標(biāo)題、狀態(tài)、耗時(shí)、錯(cuò)誤信息等。如果這一步報(bào)錯(cuò)多半是 reporter 包沒裝上或者目錄不可寫。3.2 用 mochawesome 直接生成 HTML 時(shí)的坑多 spec 覆蓋問題有人會(huì)想既然mochawesome本身就能生成 HTML那我直接在配置里設(shè)html: true不就行了不行除非你的項(xiàng)目里只有單個(gè) spec 文件。只要 Cypress 執(zhí)行了多個(gè) spec每一個(gè) spec 跑完時(shí) mochawesome 都會(huì)嘗試寫一個(gè)固定名字的mochawesome.html后寫的會(huì)把先寫的覆蓋掉。最終你得到的是最后一個(gè) spec 的報(bào)告前面 spec 的結(jié)果全丟。解決思路是設(shè)置overwrite: false讓它為每個(gè) spec 生成帶不同后綴的文件。但overwrite: false生成的文件名仍基于同一個(gè)基礎(chǔ)名會(huì)變成mochawesome_001.json、mochawesome_002.json這種。如果項(xiàng)目 spec 數(shù)量不固定編號(hào)亂掉的可能性很高。所以我的推薦配置還是html: false, json: true把生成最終 HTML 的職責(zé)交給合并腳本。4. 合并與生成mochawesome-merge 的正確打開方式先說明一下為什么需要合并。Cypress 執(zhí)行多個(gè) spec 時(shí)測(cè)試數(shù)據(jù)其實(shí)是分散在多個(gè) JSON 文件里的如果直接打開某個(gè) spec 的 JSON里面只有那個(gè) spec 的用例信息。團(tuán)隊(duì)報(bào)告要的是“全量匯總”你需要把多個(gè) JSON 合并成一個(gè)“總 JSON”再用 mochawesome 的 HTML 生成能力渲染總報(bào)告。4.1 合并命令的三種寫法與適用場(chǎng)景合并命令的核心很簡(jiǎn)單npx mochawesome-merge cypress/results/mochawesome/*.json cypress/results/mochawesome/merged.json注意我加了引號(hào)因?yàn)橥ㄅ浞枰?shell 展開某些 Windows 環(huán)境下沒有引號(hào)會(huì)匹配不到文件。執(zhí)行完這個(gè)命令你會(huì)得到一個(gè)merged.json里面是一個(gè)完整的 JSON 數(shù)組包含了所有 spec 的數(shù)據(jù)。如果你不想在命令行里拼字符串也可以寫成 npm scripts{ scripts: { merge-reports: mochawesome-merge \cypress/results/mochawesome/*.json\ cypress/results/mochawesome/merged.json, generate-report: marge cypress/results/mochawesome/merged.json --reportDir cypress/results/mochawesome --reportTitle My Test Report } }這里的marge就是mochawesome-report-generator的命令行工具在安裝 mochawesome 時(shí)會(huì)作為依賴一起被安裝也可以顯式安裝mochawesome-report-generator。marge接受一個(gè) JSON 文件生成一個(gè)完整的 HTML 報(bào)告。第三種寫法是直接在 Node 腳本里調(diào)用const merge require(mochawesome-merge).merge; const generate require(mochawesome-report-generator).generate; (async () { const report await merge({ files: [./cypress/results/mochawesome/json/*.json], }); await generate({ report, reportDir: cypress/results/mochawesome/final, reportTitle: 最終測(cè)試報(bào)告 }); })();這種寫法適合你需要在合并前后做額外處理的場(chǎng)景比如過濾某些 spec或者改報(bào)告標(biāo)題。但對(duì)于大部分項(xiàng)目命令行方式足夠。4.2 報(bào)告文件名管理與防覆蓋策略剛才我提到文件名覆蓋問題這里展開講一下常用策略。當(dāng)overwrite: false時(shí)mochawesome 會(huì)生成mochawesome_001.json、mochawesome_002.json……這樣的文件。問題是如果你在同一個(gè)目錄里第二次跑測(cè)試新生成的文件序號(hào)可能會(huì)接著原來的編號(hào)繼續(xù)也可能前綴相同而直接覆蓋這取決于 Cypress 執(zhí)行時(shí)的內(nèi)部邏輯。保險(xiǎn)做法是每次跑測(cè)試前先清空?qǐng)?bào)告輸出目錄rm -rf cypress/results/mochawesome或者在 npm script 里加一句清理命令scripts: { clean:reports: rm -rf cypress/results/mochawesome, test:report: npm run clean:reports cypress run npm run merge-reports npm run generate-report }這樣每次都是一次干凈的執(zhí)行不會(huì)出現(xiàn)舊數(shù)據(jù)殘留導(dǎo)致合并結(jié)果翻倍。5. 讓報(bào)告真實(shí)可用幾項(xiàng)關(guān)鍵配置與踩坑記錄配置對(duì)了報(bào)告能出來。但離“真實(shí)可用”還差幾步這節(jié)把最常見的幾個(gè)坑和對(duì)應(yīng)的解決思路整理成表格方便你對(duì)照排查。5.1 常見問題速查表從無報(bào)告到報(bào)告信息不全現(xiàn)象可能原因解決方式跑完后results目錄為空reporter 配置沒生效或reporterOptions里的輸出目錄寫錯(cuò)確認(rèn)cypress.config.js在cypress run時(shí)被加載刪掉node_modules/.cache后重試多個(gè) spec 只生成一份 HTMLoverwrite設(shè)為true或沒設(shè)多 spec 互相覆蓋設(shè)overwrite: false并使用mochawesome-merge合并JSON 文件生成但沒有 HTMLhtml設(shè)為false或合并后沒跑marge執(zhí)行npx marge或調(diào)用generate生成最終報(bào)告報(bào)告里失敗用例沒有截圖Cypress 截圖文件路徑與報(bào)告不在同一根目錄將screenshotsFolder與reportDir設(shè)置在同一個(gè)父目錄下配置overwrite之外還需要手動(dòng)處理相對(duì)路徑合并后報(bào)告數(shù)量翻倍舊 JSON 未被清理與新 JSON 一起合并每次跑測(cè)試前清理輸出目錄CI 上報(bào)告文件名為空或固定名被覆蓋未使用時(shí)間戳或不傳唯一標(biāo)識(shí)在 CI 腳本中為報(bào)告目錄加入構(gòu)建號(hào)如--reportDirtest-results/$BUILD_NUMBER這張表是我在維護(hù)多個(gè)項(xiàng)目時(shí)總結(jié)出來的高頻問題。其中最隱蔽的是截圖路徑問題Cypress 默認(rèn)會(huì)把截圖放在cypress/screenshots下而報(bào)告的 HTML 在cypress/results/mochawesome下兩者沒有相對(duì)關(guān)系導(dǎo)致 HTML 中嵌入的img src../../screenshots/...失效。解決辦法是設(shè)置統(tǒng)一根目錄比如把截圖目錄設(shè)為cypress/results/screenshots這樣報(bào)告的 HTML 就能通過相對(duì)路徑訪問到截圖。5.2 高級(jí)配置技巧標(biāo)題、時(shí)間戳、環(huán)境變量與自定義圖表報(bào)告生成出來了接下來要讓它更符合項(xiàng)目需要。幾個(gè)常用技巧在marge命令中設(shè)置--reportTitle給報(bào)告起一個(gè)包含日期或版本號(hào)的標(biāo)題方便歷史留存。使用--enableChart或配置charts: true讓報(bào)告頂部顯示柱狀圖看起來更專業(yè)。如果想在 HTML 中顯示測(cè)試環(huán)境信息可以通過--showPassed、--showFailed、--showPending控制展示哪些狀態(tài)的用例默認(rèn)全顯示測(cè)試量很大時(shí)可以只顯示失敗項(xiàng)。我最常用的 npm script 示例scripts: { start:report: npm run clean:reports cypress run npm run merge:json npm run gen:html, merge:json: mochawesome-merge \cypress/results/mochawesome/*.json\ cypress/results/mochawesome/merged.json, gen:html: marge cypress/results/mochawesome/merged.json --reportDir cypress/results/mochawesome/final --reportTitle \$(date %Y-%m-%d_%H-%M-%S) Report\ --charts true }這段腳本既完成了清理、執(zhí)行、合并、生成四個(gè)環(huán)節(jié)還動(dòng)態(tài)生成了帶時(shí)間戳的標(biāo)題用于區(qū)分不同輪次的測(cè)試。注意 macOS/Linux 的date命令格式Windows 下要換成 PowerShell 或使用第三方工具來生成時(shí)間字符串這塊在 CI 上一般有現(xiàn)成的環(huán)境變量供使用。6. 從本地到CI報(bào)告產(chǎn)物交付前的最后幾步本地能出報(bào)告只是萬里長征第一步。如果報(bào)告不能穩(wěn)定地在 CI 上生成并歸檔團(tuán)隊(duì)的效率還是上不來。最后聊聊 CI 集成時(shí)的幾個(gè)關(guān)鍵點(diǎn)。6.1 在 CI 流水線中執(zhí)行報(bào)告命令的順序與緩存清理以 Jenkins 為例流水線的 shell 步驟可以這樣組織npm ci npm run test:report關(guān)鍵是test:report里的清理步驟不能省。很多情況下 CI 的工作空間是復(fù)用的上一次構(gòu)建留下的test-results會(huì)混入本次產(chǎn)物導(dǎo)致報(bào)告數(shù)據(jù)重復(fù)。最佳實(shí)踐是把報(bào)告輸出目錄和node_modules一樣放入緩存清理范圍或者用 CI 自帶的工作空間清理插件。還要注意一個(gè)細(xì)節(jié)Cypress 在 CI 上默認(rèn)可能并行執(zhí)行 spec并行模式下各進(jìn)程的輸出目錄是一樣的如果沒有按進(jìn)程號(hào)區(qū)分報(bào)告文件合并時(shí)就會(huì)互相覆蓋。建議在并行執(zhí)行時(shí)給每個(gè)進(jìn)程設(shè)置一個(gè)獨(dú)立的環(huán)境變量并將reportDir設(shè)置成包含該變量的路徑。例如配置reporterOptions: { reportDir: test-results/${process.env.CYPRESS_PARALLEL_INDEX || default} }然后合并時(shí)用mochawesome-merge test-results/*/*.json來匯總。6.2 讓報(bào)告成為團(tuán)隊(duì)資產(chǎn)歸檔與可視化頁面CI 上生成的 HTML 報(bào)告默認(rèn)沒有地方展示。如果只是放在工作空間里構(gòu)建結(jié)束后通過鏈接下載體驗(yàn)很差??梢钥紤]兩個(gè)方向一是用 CI 自帶的上報(bào)插件把報(bào)告目錄作為構(gòu)建產(chǎn)物歸檔這樣在構(gòu)建詳情頁能直接打開 HTML。二是對(duì)接開源或商業(yè)的報(bào)告平臺(tái)把 JSON 數(shù)據(jù)上傳生成可長期訪問的在線報(bào)告。無論哪種方案都需要在流水線中加入“上傳歸檔”的步驟而不能只生成文件就完事。歸檔時(shí)要注意HTML 報(bào)告引用的是 JSON 文件如果只把 HTML 上傳而 JSON 文件不在同目錄頁面會(huì)加載不全。所以要整個(gè)目錄一起歸檔。另外報(bào)告里的截圖路徑如果指向 Cypress 的臨時(shí)截圖目錄歸檔時(shí)也要一并包含否則頁面里圖片區(qū)域會(huì)裂開。這個(gè)坑我是在一次演示給上司看的時(shí)候踩的當(dāng)時(shí)報(bào)告標(biāo)題、統(tǒng)計(jì)都正常唯獨(dú)截圖全裂原因就是歸檔時(shí)漏了截圖目錄。從本地跑通報(bào)告到在 CI 上穩(wěn)定產(chǎn)出并歸檔整個(gè)過程貫穿著“數(shù)據(jù)完整性”和“文件命名可預(yù)測(cè)性”這兩個(gè)原則。只要每次都先想清楚這兩點(diǎn)即使換了不同的 reporter 插件也不會(huì)走太多彎路。下一部分我會(huì)繼續(xù)寫如何將 mochawesome 的 JSON 數(shù)據(jù)類型轉(zhuǎn)換為 JUnit XML 格式以及如何在 GitLab CI 中與測(cè)試趨勢(shì)圖結(jié)合讓報(bào)告真正變成項(xiàng)目里的“儀表盤”而不是一堆靜態(tài)文件。