跳到主要内容

自信地升级前端依赖

·10 分钟阅读
Sébastien Lorber
Docusaurus 维护者,《本周 React》编辑

前端开发者经常需要升级 npm 依赖,但这些升级可能会令人担忧,并导致常规测试套件无法发现的细微 UI 副作用

升级 Docusaurus 就是一个很好的例子:如果不逐页审查所有页面,很难确定没有视觉回退。Docusaurus v3 即将推出(目前处于测试版),我们希望帮助您自信地完成此次升级。

本文介绍了一种基于GitHub ActionsPlaywrightArgos视觉回归测试工作流程。它不直接与 Docusaurus 或 React 耦合,可以适应其他前端应用和框架。

Upgrading frontend dependencies with confidence - social card

此工作流程在 Docusaurus v2 升级到 v3 时进行了测试,并已帮助在React NativeJest以及Docusaurus站点本身等网站上捕获了一些视觉回归。

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 分支网站时发现的所有视觉差异,并提供了方便的用户体验,可以轻松发现差异。

查看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://: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仓库展示了在一个新初始化的 Docusaurus v2 站点上,使用 Yarn monorepo 实现此工作流程的完整示例。

Docusaurus + Argos monorepo example screenshot

相关拉取请求

更高级的示例?

浏览 Docusaurus 仓库以获得更高级的集成

降低成本

我们选择的工具是此视觉回归测试工作流程的实现细节。

对于 Docusaurus,我们选择了Argos:它对我们来说很好用,并提供免费开源计划。但是,您可以自由选择替代工具。

如果您不介意将大量屏幕截图存储在 Git 中,您也可以尝试免费的、自托管的Playwright 视觉比较,并使用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

有条件地运行您的工作流程

为每一次提交和拉取请求都运行此工作流程是不值得的。

例如,如果有人更正了您文档中的一个错字,您可能不希望截取数百张屏幕截图,并让 Argos 指出只有修改过的页面存在视觉差异:这有点意料之中!

对于 Docusaurus 网站,我们只为带有 Argos 标签的拉取请求运行工作流程。

.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 包升级,也适用于任何不应改变用户界面的重构

那为什么不尝试一下呢?

祝您编码愉快!

另请参阅

有用的文档链接