自信地升级前端依赖项
前端开发者经常需要升级npm依赖,但这些升级可能会让人感到害怕,并导致细微的UI副作用,这些副作用通常不会被常规的测试套件捕获。
升级Docusaurus是一个很好的例子:如果不逐一审查所有页面,很难确保没有视觉回归。Docusaurus v3即将到来(目前处于测试版),我们希望帮助您自信地进行此升级。
本文介绍了一个基于GitHub Actions、Playwright和Argos的视觉回归测试工作流程。它并不直接与Docusaurus或React耦合,可以适应其他前端应用程序和框架。

此工作流程已在将Docusaurus v2升级到v3时进行了测试,并且已经帮助捕获了一些视觉回归问题,例如在React Native、Jest以及Docusaurus网站本身。
Docusaurus v3 带来了基础设施的变化和主要依赖项的升级,例如 MDX v3 和 React 18,这些升级可能会产生意想不到的副作用。如果没有这样的工作流程,很难注意到所有的视觉回归。这就是为什么我们鼓励网站所有者考虑采用视觉回归测试,特别是对于高度定制的网站。
工作流程概述
总体思路非常简单:
- 在CI中使用GitHub Actions构建您的站点
- 使用Playwright截取所有
sitemap.xml页面的截图 - 将它们上传到 Argos
- 对两个Git分支
main和pr-branch都执行此操作 - 在Argos中并排比较截图
Argos 随后会报告视觉差异,这些差异是在 main 和 pr-branch 之间发现的,并以 GitHub 提交状态和拉取请求评论的形式呈现。这可以帮助你以自动化的方式提前检测视觉回归。


Argos 创建了一个报告,引用了在并排比较两个 Git 分支站点时发现的所有视觉差异,并提供了一个便捷的用户体验,以便轻松发现差异。
查看Docusaurus Argos页面以探索我们自己的网站报告。
这里有一个更具体的例子,展示了Argos在升级React-Native网站时报告视觉回归的情况:
工作流实现
本节将描述工作流程每个步骤的实现细节。
你需要注册Argos并将Argos连接到你的GitHub仓库
依赖项
此工作流程除了通常的Docusaurus依赖项外,还需要以下开发依赖项:
yarn add -D @argos-ci/cli @argos-ci/playwright @playwright/test cheerio
GitHub Action
GitHub 操作负责为每个 Git 分支执行工作流程。
一个最小的工作流程可能如下所示:
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 配置 可能如下所示:
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测试文件来生成网站截图。
import * as fs from 'fs';
import {test} from '@playwright/test';
import {argosScreenshot} from '@argos-ci/playwright';
import {extractSitemapPathnames, pathnameToArgosName} from './utils';
// Constants
const siteUrl = 'http://localhost: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);
});
Why do we take screenshots with Argos instead of Playwright?
Argos 提供了一个 Playwright 集成,它封装了原始的 Playwright 截图 API 并提供了更好的默认设置,以使截图更加确定。
What's inside utils.ts?
该模块包含我们选择隐藏的实现细节,以便更清晰。
import * as cheerio from 'cheerio';
import * as fs from 'fs';
// 提取路径名列表,给定一个指向sitemap.xml文件的fs路径
// Docusaurus会为你生成一个build/sitemap.xml文件!
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);
}
// 将路径名转换为合适的截图名称
export function pathnameToArgosName(pathname: string): string {
return pathname.replace(/^\/|\/$/g, '') || 'index';
}
样式表
截图并不总是确定性的,对同一页面进行两次截图可能会导致细微的差异,这些差异会被Argos报告为假阳性视觉回归。
因此,我们建议注入一个额外的样式表来隐藏有问题的元素。根据您自己网站上发现的不稳定元素,您可能需要向此基础样式表添加新的CSS规则。详情请阅读Argos - 关于不稳定测试的文档。
/* 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 仓库展示了一个完整的示例,展示了如何在新初始化的 Docusaurus v2 站点上使用 Yarn monorepo 实现此工作流程。
相关的拉取请求:
- PR - 设置 GitHub Action + Playwright + Argos: 实现了上述的最小工作流程
- PR - 将 Docusaurus 从 v2 升级到 v3: 展示了在升级过程中 Argos 如何捕获了 3 个视觉回归问题
浏览Docusaurus仓库以获取更高级的集成:
让它便宜
我们选择的工具是这个视觉回归测试工作流程的实现细节。
对于Docusaurus,我们选择了Argos:它对我们来说效果很好,并且提供免费和开源计划。然而,您可以自由选择其他工具。
如果你不介意在Git中存储大截图,你也可以尝试免费的、自托管的Playwright Visual Comparisons,并使用npx playwright show-report浏览视觉差异。然而,我们发现使用专门的外部工具更为方便。
外部工具可能价格昂贵,但通常提供免费计划,包含充足的截图配额。您可以通过实施以下一些技巧来减少截图消耗。
限制路径名的数量
基本设置涉及对在sitemap.xml中找到的每个路径名进行截图。对于大型网站,这可能会导致大量的截图。
您可以决定过滤路径名,仅对最关键页面进行截图。
对于Docusaurus网站,不要为版本化的文档页面截图:
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并发组将防止连续提交触发多个无用的工作流运行。工作流将仅针对最后一次提交执行,之前的提交将自动取消。
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
有条件地运行您的工作流程
不值得为每次提交和拉取请求运行此工作流。
例如,如果有人更正了您的文档中的拼写错误,您可能不希望拍摄数百张截图并让Argos指出只有修改过的页面有视觉差异:呃,这是有点预期的!
对于Docusaurus网站,我们只对带有Argos标签的拉取请求运行工作流:
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包升级时表现出色,而且在任何不应改变用户界面的重构中也同样有效。
那么为什么不试一试呢?
快乐编程!
另请参阅
有用的文档链接:


