放心升级前端依赖
前端开发者经常需要 升级 npm 依赖 ,但这些升级可能会让人感到害怕,并导致常规测试套件无法捕获的 细微的 UI 副作用 。
升级 Docusaurus 就是一个很好的例子:如果不逐页检查,很难确定是否存在视觉回归。 Docusaurus v3 即将发布 (目前处于 测试阶段 ),我们希望帮助您充满信心地进行此升级。
本文介绍了一个基于 GitHub Actions 、 Playwright 和 Argos 的 视觉回归测试 工作流程。它没有直接与 Docusaurus 或 React 耦合,可以适应其他前端应用程序和框架。
此工作流程在将 Docusaurus v2 升级到 v3 时已经过测试,并且已经帮助在 React Native 、 Jest 和 Docusaurus 网站本身等网站上捕获了一些视觉回归。
Docusaurus v3 带来了基础设施更改和主要依赖项升级,例如 MDX v3 和 React 18 ,这可能会产生意想不到的副作用。如果没有这样的工作流程,很难注意到所有视觉回归。这就是为什么我们鼓励网站所有者考虑采用视觉回归测试,尤其对于高度自定义的网站。
工作流程概述
总体思路很简单:
- 使用 GitHub Actions 在 CI 中构建您的网站
- 使用 Playwright 拍摄所有
sitemap.xml
页面的屏幕截图 - 将它们上传到Argos
- 对 Git 分支
main
和pr-branch
都执行此操作 - 在 Argos 中并排比较屏幕截图
然后,Argos 将在 GitHub 提交状态和拉取请求评论中 报告在main
和pr-branch
之间发现的视觉差异 。这可以帮助您提前以自动化的方式检测视觉回归。
Argos 创建一个报告,参考在并排比较两个 Git 分支网站时发现的所有视觉差异,并提供方便的用户体验来轻松发现差异。
查看 Docusaurus Argos 页面 以浏览我们自己的网站报告。
以下是在升级 React-Native 网站时发现的 Argos 报告视觉回归 的更具体的示例:
[! Argos GitHub PR 评论](https://app.argos-ci.com/slorber/rnw-visual-tests/builds/32/56012838)
工作流程实现
本节将描述工作流程每个步骤的实现细节。
您需要 注册 Argos 并将 Argos 连接到您的 GitHub 存储库
依赖项
除了常用的 Docusaurus 依赖项之外,此工作流程还需要以下开发依赖项:
yarn add -D @argos-ci/cli @argos-ci/playwright @playwright/test cheerio
GitHub Action
GitHub action 负责为每个 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);
});
为什么我们使用 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 - 关于不稳定测试文档 了解详情。
/* 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 示例截图](https://github.com/slorber/docusaurus-argos-example)
相关的拉取请求:
- PR - 设置 GitHub Action + Playwright + Argos :实现上面描述的最小工作流程
- PR - 将 Docusaurus 从 v2 升级到 v3 :展示了 Argos 在升级过程中如何捕获 3 个视觉回归
浏览 Docusaurus 存储库以获取更高级的集成:
降低成本
我们选择的工具是此视觉回归测试工作流程的实现细节。
对于 Docusaurus,我们选择 Argos :它对我们来说效果很好,并提供 免费 和 开源 计划。但是,您可以随意采用替代工具。
如果您不介意在 Git 中存储大型屏幕截图,您也可以尝试免费的、自托管的 Playwright 视觉比较 ,并使用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 包升级,也适用于 任何不应更改用户界面的重构 。
所以,为什么不试一试呢?
祝您编程愉快!
另请参阅
有用的文档链接: