メインコンテンツにスキップ

フロントエンドの依存関係を自信を持ってアップグレードする

·読了時間 9 分
Sébastien Lorber
Docusaurus メンテナー、This Week In React 編集者

フロントエンド開発者はしばしば npm 依存関係をアップグレード する必要がありますが、これらのアップグレードは不安を感じさせ、通常のテストスイートでは捕捉されない **微妙な UI の副作用** をもたらす可能性があります。

Docusaurus のアップグレードはその好例です。すべてのページを 1 つずつ確認しないと、視覚的な回帰がないことを確認するのは困難です。**Docusaurus v3 は間もなくリリースされます** (現在 ベータ版)。そのため、自信を持ってこのアップグレードを行えるよう支援したいと考えています。

この記事では、**GitHub Actions****Playwright****Argos** をベースとした **視覚的リグレッションテスト** ワークフローを紹介します。これは 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 は、2 つの Git ブランチのサイトを並べて比較した際に発見されたすべての視覚的な違いを参照するレポートを作成し、違いを簡単に見つけるための便利な UX を提供します.

Docusaurus Argos ページ を確認して、当社のウェブサイトのレポートをご覧ください。

React-Native ウェブサイトのアップグレード中に発見された **視覚的な回帰を報告する** Argos の具体的な例を次に示します Argos の視覚的な回帰の報告例

Argos GitHub PR comment

ワークフローの実装

このセクションでは、ワークフローの各ステップの実装の詳細について説明します。

Argos にサインアップ し、Argos を GitHub リポジトリに接続 する必要があります

依存関係

このワークフローでは、通常の Docusaurus の依存関係に加えて、次の開発依存関係が必要です

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

GitHub Action

GitHub アクションは、各 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 アクションによってローカルにビルドされたウェブサイトのスクリーンショットを撮る役割を担います。

最小限の 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 = '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);
});
なぜ Playwright ではなく Argos でスクリーンショットを撮るのですか?

Argos には、元の Playwright スクリーンショット API をラップし、より決定論的なスクリーンショットを作成するためのより良いデフォルトを提供する Playwright 統合 があります。

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';
}

スタイルシート

スクリーンショットは常に決定論的とは限らず、ページのスクリーンショットを 2 回撮ると、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;
}
レイアウトのずれを防ぐ

レイアウトに影響を与える不安定な UI 要素は、display: none; を使用して非表示にすることをお勧めします。

たとえば、ドキュメントの「最終更新日」が複数行にレンダリングされ、コンテンツの残りの部分が下に「プッシュ」される可能性があり、Argos が多くの異なるピクセルを検出する原因となります。

リポジトリの例

slorber/docusaurus-argos-example リポジトリは、Yarn モノレポを使用して、新しく初期化された Docusaurus v2 サイトにこのワークフローを実装する完全な例を示しています。

Docusaurus + Argos monorepo example screenshot

関連するプルリクエスト

より高度な例はありますか?

より高度な統合については、Docusaurus リポジトリを参照してください

コストを削減する

私たちが選択するツールは、このビジュアルリグレッションテストワークフローの実装の詳細です。

Docusaurusでは、Argos を選択します。これは私たちにとってうまく機能し、無料およびオープンソースプランを提供しています。ただし、代替ツールを採用することも自由です。

Gitに大きなスクリーンショットを保存しても構わない場合は、無料のセルフホスト型Playwright Visual Comparisonsを試して、`npx playwright show-report` で視覚的な違いをブラウズすることもできます。ただし、専用の外部ツールを使用する方が便利であることがわかりました。

外部ツールは高価になる場合がありますが、一般的に十分なスクリーンショット容量の無料プランを提供しています。以下のいくつかのコツを実装することで、スクリーンショットの消費量を削減できます。

パス名の数を制限する

基本的な設定では、`sitemap.xml` にあるすべてのパス名のスクリーンショットを撮影します。大規模なサイトの場合、多くのスクリーンショットにつながる可能性があります。

最も重要なページのスクリーンショットのみを撮影するように、パス名をフィルタリングすることができます。

Docusaurus Webサイトの場合、バージョン付きドキュメントページのスクリーンショットは撮影しないでください。

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 Webサイトの場合、`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パッケージのアップグレードだけでなく、ユーザーインターフェースを変更すべきでない**あらゆる種類のリファクタリング**にも有効です。

試してみませんか?

ハッピーハッキング!

関連項目

役立つドキュメントリンク