Playwright + reg-actionsを使ってGitHub Actions上でビジュアルリグレッションテストをする

Blog

解決したい問題

CSSの変更やライブラリのアップデートなどが原因で、ブログのデザインが崩れてしまうことがある。
実際の画面を確認するのは手間がかかるので、自動化したい。
解決策の1つとして、ビジュアルリグレッションテストがある。

ビジュアルリグレッションテスト(VRT)とは、変更前後のスクリーンショットの差分を確認するテストである。
ライブラリのアップデートやCSSを変更する際はPull Requestを作成する運用としているため、GitHub Actionsを使ったVRTを実施することで、意図した変更かを確認できる。

確認したバージョン

  • pnpm:v8
  • @playwright/test:v1.40.1
  • reg-actions:v2

Playwrightでスクリーンショットを撮る

Playwrightとは

PlaywrightはE2Eテストツールの1つで、ヘッドレスブラウザーを内包している。
スクリーンショットを撮るために使うのは本来の使い方ではないが、ブラウザーのセットアップは手間がかかるので、設定ファイルを書くだけで実行できるPlaywrightを使う。

Cross-browser end-to-end testing for modern web apps

Playwrightのセットアップ

  1. 以下のコマンドでPlaywrightをインストールする。

    pnpm install -D @playwright/test
    
  2. プロジェクトのルートにPlaywrightの設定ファイルを作成する。
    設定ファイルの内容はPlaywright | Installで案内されているインストールコマンドを実行したときに生成される内容を参考にする。
    唯一の差分としては、動かすブラウザーはGoogle Chromeに限定しているところである。

    playwright.config.ts
    import { defineConfig, devices } from '@playwright/test';
    
    export default defineConfig({
      testDir: './tests',
      fullyParallel: true,
      forbidOnly: !!process.env.CI,
      retries: process.env.CI ? 2 : 0,
      workers: process.env.CI ? 1 : undefined,
      use: {
        trace: 'on-first-retry',
      },
      reporter: 'html',
      projects: [
        {
          name: 'chromium',
          use: { ...devices['Desktop Chrome'] },
        },
      ],
    });
    
  3. GitHub Actions上でブログのローカルサーバーを起動する必要があるため、起動用の設定を追加する。
    Astroでブログを作成している場合はhttp://localhost:4321でローカルサーバーが起動するため、以下のように設定する。

    playwright.config.ts
    export default defineConfig({
      // ...
      use: {
    +    baseURL: 'http://localhost:4321', // ローカルサーバーの URL
        trace: 'on-first-retry',
      },
      reporter: 'html',
      projects: [
        // ...
      ],
    +  webServer: {
    +    command: 'npx astro dev', // ローカルサーバーを起動するコマンド
    +    url: 'http://localhost:4321', // ローカルサーバーの URL
    +    reuseExistingServer: !process.env.CI,
    +    stdout: 'pipe', // デバッグログを出力する
      },
    });
    

テストケースの追加

設定ファイルでtestDirに「tests」を指定したので、「tests」ディレクトリにファイルを作成する。
今回はブログのトップページのスクリーンショットを保存するスクリプトを追加することにした。

Playwrightではpage.screenshot()で現在アクセスしているページのキャプチャができる。

tests/home.spec.ts
import { test, expect } from '@playwright/test';

test('home', async ({ page }) => {
  // トップページにアクセスする
  await page.goto('/');
  // スクリーンショットを撮る
  await page.screenshot({ path: `screenshots/home.png`, fullPage: true });
  // おまけ:タイトルをチェックする
  await expect(page).toHaveTitle(/ひよこまめ/);
});

ローカルでの実行

設定ファイルとテストケースができたので、npx playwright testコマンドでPlaywrightを実行する。
以下の2点が確認できれば成功である。

  • ローカルサーバーが起動して、テストがパスする
  • 「screenshots」ディレクトリにスクリーンショットが生成されている
npx playwright test

[WebServer]
> blog@0.0.1 start /Users/chick-p/repos/github.com/...
> astro dev

[WebServer]   🚀  astro  v3.5.7 started in 717ms

 Local    http://localhost:4321/
 Network  use --host to expose

Running 1 test using 1 worker
[WebServer] Skipping algolia index
  1 passed (8.6s)

To open last HTML report run:

  npx playwright show-report

reg-actionsでVRTを実行する

reg-actionsとは

PlaywrightでもtoMatchSnapshotというAPIを使えば、スクリーンショットの差分を確認できるが、今回のケースでは以下の問題があるため使用しない。

  • 変更前の画像(正解画像)をリポジトリで管理する必要がある。
    差分が出るたびに正解画像を更新する必要があるため、管理が大変である。
  • VRTでは変更前後のスクリーンショットを同じ環境でキャプチャする必要がある。
    GitHub Actions上で撮ったスクリーンショットとローカルの執筆環境を比較しても、フォントが違うなどの差分が生まれてしまう。
    この解決策として一般的にDockerが利用されることが多いが、こちらも準備が大変である。

そこで、reg-actionsというGitHub Actionsアクションを使うことにした。

reg-actionsとは、reg-cliを使ってGitHub Actions上でVRTを実行するためのアクションである。
reg-actionsでは、Playwrightでキャプチャした画像をArtifactsに保存する。
差分確認ではArtifactsに保存された比較対象の画像をコミットツリーを辿って特定するので、正解画像をリポジトリで管理する必要がない。
もちろん、変更前後のスクリーンショットはどちらもGitHub Actionsのランナーでキャプチャするので、環境の差分が発生しない。

reg-actionsが内部的に使っているreg-cliやreg-suitを使う方法もあるが、画像を保存するS3などのストレージの準備や管理がいる。
またレポートを確認するにはCloudFrontやCloudFront Functionsのセットアップも必要になる。 reg-actionsはGitHubのみで完結するため、これらの準備が不要になることもメリットである。

A visual regression test tool for github actions :octocat:. - reg-viz/reg-actions
GitHub

テストワークフローの追加

まずPlaywrightを動かすために必要なワークフローを追加する。

.github/workflows/vrt.yml
name: Visual Regression Test
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

permissions:
  contents: write # Need to upload artifacts

jobs:
  test:
    timeout-minutes: 5
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - uses: pnpm/action-setup@v2
      with:
        version: 8

    - uses: actions/setup-node@v4
      with:
        node-version-file: .node-version

    - name: Install dependencies
      run: pnpm install

    - name: Cache playwright binaries
      uses: actions/cache@v3
      id: playwright-cache
      with:
        path:  ~/.cache/ms-playwright
        key: ${{ runner.os }}-playwright-${{ hashFiles('**/pnpm-lock.yaml') }}

    - name: Install Playwright Browsers
      if: steps.playwright-cache.outputs.cache-hit != 'true'
      run: pnpm exec playwright install --with-deps chromium

    - name: Run Playwright tests
      run: pnpm exec playwright test

    - uses: actions/upload-artifact@v3
      if: always()
      with:
        name: playwright-report
        path: playwright-report/
        retention-days: 30

reg-actionsのセットアップ

次にreg-actionsをワークフローに追加する。
reg-actionsでは、Pull RequestとActionsのジョブサマリーに結果をコメントするため、GitHubのトークンにその権限を付与する。

.github/workflows/vrt.yml
permissions:
  contents: write # upload artifacts
+  actions: write # Need to comment to job summary
+  pull-requests: write # Need to comment to PR

jobs:
  test:
    steps:
    # ...
    - uses: actions/upload-artifact@v3
      if: always()
      with:
        name: playwright-report
        path: playwright-report/
        retention-days: 30

+    - uses: reg-viz/reg-actions@v2
+      with:
+        github-token: "${{ secrets.GITHUB_TOKEN }}"
+        image-directory-path: "./screenshots"

日本語フォントのインストール

GitHub ActionsのLinuxランナーでスクリーンショットを撮ると、日本語が豆腐化してしまう。
そのためワークフロー上で日本語フォントをインストールしておく。

.github/workflows/vrt.yml

    - name: Install dependencies
      run: pnpm install

+    - name: Cache fonts
+      uses: actions/cache@v3
+      id: fonts-cache
+      with:
+        path: ~/.fonts
+        key: ${{ runner.OS }}-fonts

+    - name: Install Japanese font
+      if: steps.fonts-cache.outputs.cache-hit != 'true'
+      run: sudo apt install fonts-noto-cjk

    - name: Cache playwright binaries

動作確認

以上で設定が完了したので、Pull Requestを作成してワークフローを実行する。
初回実行にあたるPull Requestでは正解の画像が存在しないため、新規画像として扱われる。

初回の Pull Request のコメント

差分を確認するには、初回実行のPull Requestをマージした後に、Pull Requestを作成する必要がある。
2回目以降のPull Requestでは、正解の画像と比較して差分がある場合、Pull Requestのコメントで差分が通知される。

2回目以降の Pull Request に、「change detected」が表示されている

コメントの「Report」を展開すると、実際のスクリーンショットで差分を確認できる。

画像の差分が表示されている

スクリーンショットの例では、SNSのアイコンを鳥からXに変更したため差分が発生している。
なお、検索ボックスは外部リソースなので、スクリーンショットを撮ったタイミングに依存して検索ボックスに差分が発生している。

注意点

  • reg-actionsでは、Pull Requestの変更元にあたるベースブランチのスクリーンショットを、変更前の画像として扱う。
    そのため、あらかじめベースブランチでのスクリーンショットをArtifactsに保存しておく必要がある。
    たとえば、mainブランチからブランチを作成してPull Requestを作成する場合は、mainでのVRTが終わるのを待ってから、Pull RequestでのVRTを実行する。
  • reg-actionsが作成するArtifactsには、有効期限がある。
    そのため、親ブランチのArtifactsの有効期限が切れる前に、ワークフローを実行しておく必要がある。
    削除されてしまった場合は、新規画像として扱われてしまう。
  • スクリーンショットに差分が発生しても、reg-actionsのワークフローは失敗しない。
    そのため、発生した差分が想定したものかを自分で確認する必要がある。