跳至主要內容

對前端依存項充滿信心地進行升級

·9 分鐘閱讀時間
Sébastien Lorber
Docusaurus 維護者,This Week In React 編輯

前端開發人員常常需要升級 npm 依存項,但這些升級可能會讓人感到害怕,並導致細微的 UI 副作用,而您的常規測試套件無法偵測到。

升級 Docusaurus 就是一個很好的例子:在不逐頁檢閱所有頁面的情況下,很難確定沒有視覺回歸。Docusaurus v3 即將推出(目前正在 測試版 中),我們希望協助您充滿信心地進行此升級。

本文介紹一個視覺回歸測試工作流程,此工作流程基於 GitHub ActionsPlaywrightArgos。它與 Docusaurus 或 React 無直接關聯,而且可以調整,以用於其他前端應用程式和架構。

Upgrading frontend dependencies with confidence - social card

此工作流程已經在將 Docusaurus v2 升級到 v3 時進行測試,並已幫助在類似 React NativeJestDocusaurus 本身的網站上,捕獲到一些視覺回歸。

Docusaurus v3 的基礎設施有更改,而且有重大相依性升級,例如 MDX v3React 18,而這些可能會產生意外的副作用。如果沒有此一工作流程,將難以注意到所有視覺回歸。這就是我們鼓勵網站擁有者考慮採用視覺回歸測試的原因,特別是對於高度自訂的網站。

工作流程概觀

整個概念相當簡單

  • 在使用 GitHub Actions 於 CI 中建立你的網站
  • 使用 Playwright 截取所有 sitemap.xml 頁面的螢幕擷取畫面
  • 將它們上傳至 Argos
  • 對 Git 分支 mainpr-branch 都執行相同動作
  • Argos 並排比較螢幕擷取畫面

隨後,Argos 將會 回報視覺差異,也就是在 mainpr-branch 之間找到的差異,當成 GitHub 提交狀態和拉取請求註解。這樣的功能能夠協助你自動化偵測視覺回歸,並且及早發現。

Argos GitHub commit status

Argos GitHub PR comment

Argos 會製作一份報告,參考在並排比較兩個 Git 分支網站時,所找到的所有視覺差異,而且會提供便利的 UX 讓你可以輕鬆發現差異。

查看 Docusaurus Argos 頁面,瀏覽我們自己網站的報告。

這裡有 Argos 的具體範例,回報在升級 React-Native 網站時,所發現的一個視覺回歸

Argos GitHub PR comment

工作流程實作

本節將描述工作流程中每個步驟的實作詳細資料。

你需要 註冊 Argos,並將 Argos 連接到你的 GitHub 儲存庫

相依性

此工作流程需要以下的開發相依性,且這些相依性不包含一般 Docusaurus 會用到的相依性

yarn add -D @argos-ci/cli @argos-ci/playwright @playwright/test cheerio

GitHub Action

GitHub action 負責為每個 Git 分支執行工作流程。

一個最小程度的工作流程可以用以下方法執行

.github/workflows/argos.yml
name: Argos CI Screenshots

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
take-screenshots:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4

- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: current

- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Install Playwright browsers
run: yarn playwright install --with-deps chromium

- name: Build the website
run: yarn docusaurus build

- name: Take screenshots with Playwright
run: yarn playwright test

- name: Upload screenshots to Argos
run: yarn argos upload ./screenshots

Playwright 設定檔

Playwright 負責截取 GitHub action 在本地先前已建置的網站之螢幕截圖。

極簡的 Playwright 設定檔 可能是

playwright.config.ts
import {devices} from '@playwright/test';
import type {PlaywrightTestConfig} from '@playwright/test';

const config: PlaywrightTestConfig = {
webServer: {
port: 3000,
command: 'yarn docusaurus serve',
},
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
},
],
};

export default config;

Playwright 測試

Playwright 設定檔還不夠,我們還需要撰寫一個 Playwright 測試檔案來產生網站螢幕截圖。

screenshot.spec.ts
import * as fs from 'fs';
import {test} from '@playwright/test';
import {argosScreenshot} from '@argos-ci/playwright';
import {extractSitemapPathnames, pathnameToArgosName} from './utils';

// Constants
const siteUrl = 'https://127.0.0.1:3000';
const sitemapPath = './build/sitemap.xml';
const stylesheetPath = './screenshot.css';
const stylesheet = fs.readFileSync(stylesheetPath).toString();

// Wait for hydration, requires Docusaurus v2.4.3+
// Docusaurus adds a <html data-has-hydrated="true"> once hydrated
// See https://github.com/facebook/docusaurus/pull/9256
function waitForDocusaurusHydration() {
return document.documentElement.dataset.hasHydrated === 'true';
}

function screenshotPathname(pathname: string) {
test(`pathname ${pathname}`, async ({page}) => {
const url = siteUrl + pathname;
await page.goto(url);
await page.waitForFunction(waitForDocusaurusHydration);
await page.addStyleTag({content: stylesheet});
await argosScreenshot(page, pathnameToArgosName(pathname));
});
}

test.describe('Docusaurus site screenshots', () => {
const pathnames = extractSitemapPathnames(sitemapPath);
console.log('Pathnames to screenshot:', pathnames);
pathnames.forEach(screenshotPathname);
});
我們為什麼使用 Argos 而非 Playwright 來截取螢幕截圖?

Argos 有個 Playwright 整合,可以封裝原始 Playwright 螢幕截圖 API 並提供更好的預設值,讓螢幕截圖更具有決定性。

utils.ts 裡有什麼?

此模組包含我們基於清楚起見而選擇隱藏的實作細節。

import * as cheerio from 'cheerio';
import * as fs from 'fs';

// Extract a list of pathnames, given a fs path to a sitemap.xml file
// Docusaurus generates a build/sitemap.xml file for you!
export function extractSitemapPathnames(sitemapPath: string): string[] {
const sitemap = fs.readFileSync(sitemapPath).toString();
const $ = cheerio.load(sitemap, {xmlMode: true});
const urls: string[] = [];
$('loc').each(function handleLoc() {
urls.push($(this).text());
});
return urls.map((url) => new URL(url).pathname);
}

// Converts a pathname to a decent screenshot name
export function pathnameToArgosName(pathname: string): string {
return pathname.replace(/^\/|\/$/g, '') || 'index';
}

樣式表

螢幕截圖不總是具有決定性,截取頁面的螢幕截圖兩次可能會導致細微的變異,而 Argos 會將這類變異報告為假正向視覺回歸錯誤。

因此,我們建議注入一張額外的樣式表來隱藏有問題的元素。你可能會需要根據在你自己的網站上找到出現變異的元素,新增新的 CSS 規則到這個基本樣式表。請參閱 Argos - 關於變異性測試文件 以取得詳細資料。

screenshot.css
/* Iframes can load lazily */
iframe,
/* Avatars can be flaky due to using external sources: GitHub/Unavatar */
.avatar__photo,
/* Gifs load lazily and are animated */
img[src$='.gif'],
/* Algolia keyboard shortcuts appear with a little delay */
.DocSearch-Button-Keys > kbd,
/* The live playground preview can often display dates/counters */
[class*='playgroundPreview'] {
visibility: hidden;
}

/* Different docs last-update dates can alter layout */
.theme-last-updated,
/* Mermaid diagrams are rendered client-side and produce layout shifts */
.docusaurus-mermaid-container {
display: none;
}
防止版面變動

我們建議使用 display: none; 隱藏影響版面的不穩定 UI 元素。

例如,文件「最後更新時間」可能會轉譯成兩行以上,最終「推移」你的其他內容到更下面,導致 Argos 偵測到許多不同的像素。

範例存放庫

slorber/docusaurus-argos-example 存放庫展示了使用 Yarn monorepo 在新初始化的 Docusaurus v2 網站上實作此工作流程的完整範例。

Docusaurus + Argos monorepo example screenshot

相關的 pull 要求

更進階的範例?

瀏覽 Docusaurus 儲存庫以獲取更進階的整合

讓它便宜一些

我們選擇的工具是這個視覺回歸測試工作流程的實作詳細資料。

對於 Docusaurus,我們選擇 Argos:它對我們來說很好用,並提供免費開源方案。但是,您可以自由採用其他工具。

萬一您不介意將大型螢幕截圖儲存在 Git 中,您也可以嘗試免費的自託管Playwright Visual Comparisons,並使用 npx playwright show-report 瀏覽視覺差異。但是,我們發現使用專門的外部工具比較方便。

外部工具可能很昂貴,但通常提供免費方案,並有充足的螢幕截圖配額。您可以透過實作以下一些技巧來減少螢幕截圖使用量。

限制路徑名稱數量

基本設定包含擷取在 sitemap.xml 中找到的每個單獨路徑名稱的螢幕截圖。對於大型網站,這可能會產生許多螢幕截圖。

您可以決定過濾路徑名稱,僅擷取最關鍵的頁面的螢幕截圖。

對於 Docusaurus 網站,不要為版本化文件頁面擷取螢幕截圖

screenshot.spec.ts
function isVersionedDocsPathname(pathname: string): boolean {
return pathname.match(/^\/docs\/((\d\.\d\.\d)|(next))\//);
}

test.describe('Docusaurus site screenshots', () => {
const pathnames = extractSitemapPathnames(sitemapPath)
.filter(isVersionedDocsPathname);

pathnames.forEach(screenshotPathname);
});

限制工作流程同時執行

實作 GitHub Actions 同時執行群組 將防止後續提交觸發多個無用的工作流程執行。工作流程將僅針對最後一次提交執行,先前提交將被自動取消。

.github/workflows/argos.yml
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

有條件執行您的工作流程

不值得針對每一個提交和 pull 要求執行此工作流程。

例如,如果有人更正您的文件中的拼寫錯誤,您可能不希望截取數百個螢幕擷圖並請 Argos 指出只有修改過的頁面視覺上有所差異:嗯,這是有點預期的!

對於 Docusaurus 網站,我們僅針對具有 Argos 標籤的 pull 要求執行工作流程。

.github/workflows/argos.yml
name: Argos CI Screenshots

on:
push:
branches: [main]
pull_request:
branches: [main]
types:
- opened
- synchronize
- reopened
- labeled

jobs:
take-screenshots:
if: ${{ github.ref_name == 'main' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'Argos')) }}
runs-on: ubuntu-latest
steps:
# Your job steps here ...

有許多選項可以探索,例如 手動觸發工作流程僅在與特定模式相符的檔案修改時

結論

我相信視覺回歸測試在前端生態系統中未得到充分利用

擷取全頁螢幕擷圖是垂手可得、容易設定且可以協助您抓出一般測試套件會錯過的全新錯誤類型。此技術不僅適用於 npm 套件升級,也適用於不應變更使用者介面的任何重構

因此何不給它一個機會?

駭得開心!

另請參閱

有用的文件連結