使用 Cypress 進行視覺回歸測試:一種務實的方法
已發表: 2022-03-11每次發布我們的組件庫 Picasso 的新版本時,我們都會更新我們所有的前端應用程序,以充分利用新功能並在我們網站的所有部分調整我們的設計。
上個月,我們推出了 Toptal 人才門戶網站的畢加索更新,這是我們的人才用來尋找工作和與客戶互動的平台。 知道發布會帶來重大的設計更改,並且為了盡量減少意外問題,使用視覺回歸測試技術幫助我們在發布之前發現問題是有意義的。
視覺回歸測試並不是一個新概念; Toptal 的許多其他項目已經在使用它,包括畢加索本身。
Percy、Happo 和 Chromatic 等工具可用於幫助團隊構建健康的視覺回歸管道,我們最初確實考慮過添加它們。 我們最終決定設置過程太耗時,並且可能會破壞我們的日程安排。 我們已經設定了代碼凍結開始遷移的日期,距離截止日期僅剩幾天,我們別無選擇,只能發揮創造力。
通過 UI 測試進行視覺回歸測試
雖然我們在項目中沒有進行視覺回歸測試,但我們確實對使用 Cypress 的 UI 集成測試進行了很好的覆蓋。 儘管這不是該工具的主要用途,但賽普拉斯在其文檔中有一頁專門用於可視化測試,另一頁列出了所有可用的插件,以幫助配置賽普拉斯進行可視化測試。
從賽普拉斯到截圖
在瀏覽了可用的文檔之後,我們決定嘗試一下 cypress-snapshot-plugin。 設置只需要幾分鐘,一旦完成,我們很快意識到我們並不是在追求傳統的視覺回歸輸出。
大多數視覺回歸工具通過比較快照和檢測已知、可接受的基線與頁面或組件的修改版本之間的像素差異來幫助識別不需要的更改。 如果像素差異大於設置的容差閾值,則將頁面或組件標記為手動檢查。 不過,在這個版本中,我們知道我們將對大多數 UI 組件進行一些小的更改,因此設置閾值並不適用。 即使給定的組件碰巧有 100% 的不同,它在新版本的上下文中可能仍然是正確的。 同樣,小到幾個像素的偏差可能意味著組件當前不適合生產。
在那一點上,兩個對比的事情變得清晰起來:注意像素差異並不能幫助識別問題,並且對組件進行並排比較正是我們所需要的。 我們將快照插件放在一邊,並著手在應用畢加索更新之前和之後使用我們的組件創建一組圖像。 這樣,我們可以快速瀏覽所有更改,以確定新版本是否仍然符合站點的需求和圖書館的標準。
新計劃是對一個組件進行截圖,將其存儲在本地,使用更新的 Picasso 版本對分支中的同一組件進行新的截圖,然後將它們合併為一個圖像。 最終,這種新方法與我們一開始的方法並沒有太大區別,但它在實施階段為我們提供了更大的靈活性,因為我們不再需要導入插件並使用它的新命令。
利用 API 進行比較圖像
有了明確的目標,是時候看看賽普拉斯如何幫助我們獲得所需的屏幕截圖了。 如前所述,我們進行了大量的 UI 測試,涵蓋了人才門戶的大部分內容,因此為了收集盡可能多的關鍵組件,我們決定在每次交互後對各個元素進行截圖。
另一種方法是在測試期間的關鍵時刻截取整個頁面的屏幕截圖,但我們認為這些圖像太難以比較。 此外,此類比較可能更容易出現人為錯誤,例如錯過頁腳已更改的信息。
第三種選擇是通過每一個測試用例來決定要捕獲什麼,但這會花費更多時間,因此堅持頁面上使用的所有元素似乎是一種實際的折衷方案。
我們求助於賽普拉斯的 API 來生成圖像。 cy.screenshot()命令可以開箱即用地創建組件的單個圖像,After Screenshot API 允許我們重命名文件、更改目錄以及區分視覺回歸運行與標準運行。 通過將兩者結合起來,我們創建的運行不會影響我們的功能測試,並使我們能夠將圖像存儲在相應的文件夾中。
首先,我們擴展了插件目錄中的index.js文件以支持兩種新的運行類型(基線和比較)。 然後,我們根據運行類型設置圖像的路徑:
// plugins/index.js const fs = require('fs') const path = require('path') module.exports = (on, config) => { // Adding these values to your config object allows you to access them in your tests. config.env.baseline = process.env.BASELINE || false config.env.comparison = process.env.COMPARISON || false on('after:screenshot', details => { // We only want to modify the behavior of baseline and comparison runs. if (config.env.baseline || config.env.comparison) { // We keep track of the file name and number to make sure they are saved in the proper order and in their relevant folders. // An alternative would have been to look up the folder for the latest image, but this was the simpler approach. let lastScreenshotFile = '' let lastScreenshotNumber = 0 // We append the proper suffix number to the image, create the folder, and move the file. const createDirAndRename = filePath => { if (lastScreenshotFile === filePath) { lastScreenshotNumber++ } else { lastScreenshotNumber = 0 } lastScreenshotFile = filePath const newPath = filePath.replace( '.png', ` #${lastScreenshotNumber}.png` ) return new Promise((resolve, reject) => { fs.mkdir(path.dirname(newPath), { recursive: true }, mkdirErr => { if (mkdirErr) { return reject(mkdirErr) } fs.rename(details.path, newPath, renameErr => { if (renameErr) { return reject(renameErr) } resolve({ path: newPath }) }) }) }) } const screenshotPath = `visualComparison/${config.env.baseline ? 'baseline' : 'comparison'}` return createDirAndRename(details.path .replace('cypress/integration', screenshotPath) .replace('All Specs', screenshotPath) ) } }) return config } 然後我們通過將相應的環境變量添加到項目的package.json中的 Cypress 調用來調用每個運行:

"scripts": { "cypress:baseline": "BASELINE=true yarn cypress:open", "cypress:comparison": "COMPARISON=true yarn cypress:open" }運行新命令後,我們可以看到運行期間拍攝的所有屏幕截圖都已移動到相應的文件夾中。
接下來,我們嘗試覆蓋 Cypress 用於返回 DOM 元素的主要命令cy.get() ,並對調用的任何元素及其默認實現進行截圖。 不幸的是, cy.get()是一個很難更改的命令,因為在其自己的定義中調用原始命令會導致無限循環。 解決此限制的建議方法是創建一個單獨的自定義命令,然後讓該新命令在找到元素後截取屏幕截圖:
Cypress.Commands.add("getAndScreenshot", (selector, options) => { // Note: You might need to tweak the command when getting multiple elements. return cy.get(selector).screenshot() }); it("get overwrite", () => { cy.visit("https://example.cypress.io/commands/actions"); cy.getAndScreenshot(".action-email") }) 但是,我們與頁面上的元素交互的調用已經包含在內部getElement()函數中。 所以我們所要做的就是確保在調用包裝器時截取屏幕截圖。
通過視覺回歸測試獲得的結果
一旦我們有了屏幕截圖,剩下要做的就是合併它們。 為此,我們使用 Canvas 創建了一個簡單的節點腳本。 最終,腳本讓我們生成了 618 張對比圖! 打開人才門戶很容易發現其中一些差異,但有些問題並不那麼明顯。
為 UI 測試增加價值
首先,添加的視覺回歸測試被證明是有用的,並且發現了一些如果沒有它們我們可能會錯過的問題。 儘管我們預計組件會有所不同,但了解實際更改的內容有助於縮小問題案例的範圍。 所以,如果你的項目有一個接口,但你還沒有執行這些測試,那就去做吧!
這裡的第二個教訓,也許是更重要的一個教訓,是我們再次被提醒完美是善的敵人。 如果我們因為沒有預先設置而排除了為此版本運行視覺回歸測試的可能性,那麼我們可能在遷移過程中錯過了一些錯誤。 相反,我們同意了一個雖然並不理想,但執行速度很快的計劃,我們朝著它努力,並且得到了回報。
有關在您的項目中實施強大的視覺回歸管道的更多詳細信息,請參閱賽普拉斯的視覺測試頁面,選擇最適合您需求的工具,並觀看教程視頻。
