【GitHub Actions】ビジュアルリグレッションテストを実装してみた

# はじめに
Playwrightを用いてVRTをGitHub Actionsで動く過程を小さく実装したので、メモとして残します。

# 開発環境
- VS Code
- node:v22.9.0
- npm: v10.8.3
- playwright：v1.17.134
- reg-actions

# ビジュアルリグレッションテストについて
アプリケーションのユーザーインターフェース(UI)が意図した通りに表示されているかを以下の3つのフェーズを経て見た目のリグレッション(後退)が発生していないかを検証するテスト手法

- スナップショットの取得
変更前のUIのスクリーンショットを取得
- 比較
  コードに変更を加えた後、再度UIのスクリーンショットを取得
- 差分の確認
  取得した変更後の画像と基準画像を比較し、UIに予期しない変更がないかをチェック

# 前提条件
- node/npmインストール済み
- GitHubアカウントあり
- VS Code設定済み

# 実装

## VS Code設定
- 以下に沿って、設定する
- [Getting started - VS Code
](https://playwright.dev/docs/getting-started-vscode)

- VS Code の拡張機能タブからVS Code 拡張機能をインストール

![スクリーンショット 2024-10-27 18.49.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/57861436-e638-29d6-a297-5129e928d62d.png)

- インストールしたら、コマンドパネルを開いて`Install Playwright`と入力する

![スクリーンショット 2024-10-27 18.50.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/3a2ab220-12a8-886b-cdf6-fc75841c0bda.png)

- okをクリック

![スクリーンショット 2024-10-27 18.54.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/fa5b57fc-28ae-cf32-c763-f6b2a37fd24d.png)

## node環境でindex.htmlを表示する

- 以下に沿って簡易的に作成する
[Node.js v16.20.2 ドキュメント](https://nodejs.org/docs/latest-v16.x/api/synopsis.html)

- プロジェクト配下にindex.htmlとserver.jsを作成

```index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TOP</title>
</head>
<body>
    <header>
        <h1>TOP</h1>
    </header>

    <main>
        <div>Top画像</div>
        <img src="images/top2.jpg" alt="TOP画像" width="300" height="200">
    </main>

    <footer>
        <p>フッター</p>
    </footer>
</body>
</html>
```

```server.js
const http = require("node:http");
const fs = require("fs");
const path = require("path");

const hostname = "127.0.0.1";
const port = 3001;

const server = http.createServer((req, res) => {
  // リクエストされたファイルのパスを取得
  let filePath = req.url === '/' ? 'index.html' : `public${req.url}`;

  // ファイルのフルパスを作成
  filePath = path.join(__dirname, filePath);

  // ファイルを読み込む
  fs.readFile(filePath, (err, data) => {
    if (err) {
      res.statusCode = 404;
      res.setHeader("Content-Type", "text/plain");
      res.end("Not Found\n");
    } else {
      // コンテンツタイプを判別
      const extname = path.extname(filePath);
      let contentType = 'text/html';
      if (extname === '.js') {
        contentType = 'text/javascript';
      } else if (extname === '.css') {
        contentType = 'text/css';
      } else if (extname === '.png') {
        contentType = 'images/png';
      } else if (extname === '.jpg') {
        contentType = 'images/jpeg';
      }

      res.statusCode = 200;
      res.setHeader("Content-Type", contentType);
      res.end(data);
    }
  });
});

// サーバーの起動
server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

```

- サーバー実行しweb上で確認

```
node server.js 
```

- 表示されている
![スクリーンショット 2024-11-04 18.51.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/037e22b5-fd3f-6e34-8010-87a295b38e69.png)


## Playwrightインストール

- Playwrightをインストール

```
npm init playwright@latest
```
- 自分の環境に合ったものを選択してインストール

>・TypeScript または JavaScript を選択します (デフォルトは TypeScript)
・テスト フォルダの名前 (プロジェクトに既にテスト フォルダがある場合は、デフォルトは tests または e2e です)
・GitHub Actionsワークフローを追加してCIで簡単にテストを実行
・Playwright ブラウザをインストールする (デフォルトは true)

![スクリーンショット 2024-11-02 17.43.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/2a6895a7-23c2-8156-f0fb-6a31195f0571.png)


- ファイル構成

```
playwright.config.ts
package.json
package-lock.json
tests/
  example.spec.ts
tests-examples/
  demo-todo-app.spec.ts
```
![スクリーンショット 2024-11-02 17.44.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/ef7de4a6-b495-86df-1347-ef27fe543ee9.png)

- サンプルでテスト実行してみる

```
npx playwright test
```

![スクリーンショット 2024-11-02 17.45.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/bf61419e-4c96-b0d0-9ad2-2fd0ea45aebd.png)


- HTMLテストを実行してみる

```
npx playwright show-report
```
![スクリーンショット 2024-11-02 17.47.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/2d40fd65-7582-6be9-499f-1d4f11d9684b.png)

- UIでも動作している

```
npx playwright test --ui
```

![スクリーンショット 2024-11-02 17.48.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/b3969281-7130-cee5-bcaf-71fcb542c6b7.png)


- Chromiumブラウザーでのみテストを実行するには、--projectオプションを使用する見たい。

```
npx playwright test --project chromium
```


## VS Codeで実行してみる

- デバッグの場合は、Show trace Viewer オプションに入れておくとわかりやすい。

![スクリーンショット 2024-11-03 10.53.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/190b5d44-094d-2055-d3b2-23334bbedaf4.png)

- 視覚的にわかりやすい。

![スクリーンショット 2024-11-03 10.54.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/afa4fb75-75ce-6184-e047-ad5c4b18a528.png)



- 以下から色々触ってみる。Chrome以外はコメントアウト

```src/playwright.config.ts

  /* Configure projects for major browsers */
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },

    // {
    //   name: 'firefox',
    //   use: { ...devices['Desktop Firefox'] },
    // },
    //
    // {
    //   name: 'webkit',
    //   use: { ...devices['Desktop Safari'] },
    // },

    /* Test against mobile viewports. */
    // {
    //   name: 'Mobile Chrome',
    //   use: { ...devices['Pixel 5'] },
    // },
    // {
    //   name: 'Mobile Safari',
    //   use: { ...devices['iPhone 12'] },
    // },

    /* Test against branded browsers. */
    // {
    //   name: 'Microsoft Edge',
    //   use: { ...devices['Desktop Edge'], channel: 'msedge' },
    // },
    // {
    //   name: 'Google Chrome',
    //   use: { ...devices['Desktop Chrome'], channel: 'chrome' },
    // },
  ],

```

- TOP画面の画像をスクショ撮るテストを追加

```src/e2e/top.spec.ts
import { test, expect } from '@playwright/test';

test('top', async ({ page }) => {
    // トップページにアクセスする
    await page.goto('/');
    // スクリーンショットを撮る
    await page.screenshot({ path: `screenshots/top.jpg`, fullPage: true });
});

```

## エラー①

- `npx playwright test`実行すると下記のエラーになる

![スクリーンショット 2024-11-03 12.29.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/4315430f-92d6-73c0-4c92-e07309e3e66a.png)


- 公式通り、useにスキップするように設定を追加
- [test-use-options](https://playwright.dev/docs/test-use-options)

```src/playwright.config.ts
  use: {
    /* Base URL to use in actions like `await page.goto('/')`. */
    baseURL: 'http://127.0.0.1:3001',

    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
    trace: 'on-first-retry',

    // Whether to ignore HTTPS errors during navigation.
    ignoreHTTPSErrors: true,
  },
```
- 実行し保存されていることを確認
![スクリーンショット 2024-11-04 18.51.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/bca22e59-6f79-69d1-6870-d356f19e6f3e.png)




## CI環境で動かしてみる
- GitHub Actionsでも確認できるように設定をしてみる
- 以下公式に沿って、設定

```.github/workflows/playwright.yml
name: Playwright Tests
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version: lts/*
    - name: Install dependencies
      run: npm ci
    - name: Install Playwright Browsers
      run: npx playwright install --with-deps
    - name: Run Playwright tests
      run: npx playwright test
    - uses: actions/upload-artifact@v4
      if: ${{ !cancelled() }}
      with:
        name: playwright-report
        path: playwright-report/
        retention-days: 30
```


- GitHub Actions上でブログのローカルサーバーを起動するために以下を設定

```playwright.config.ts

  webServer: {
    command: 'npm run start',
    url: 'http://127.0.0.1:3001',
    reuseExistingServer: !process.env.CI,
  },

```

- GitHub actionsを修正

```.github/workflows/playwright.yml
name: Playwright Tests
on:
  # mainプッシュ時に実行
  push:
    branches: [ main ]
  # mainへプルリク作成時に実行
  pull_request:
    branches: [ main ]
jobs:
  test:
    # タイムアウトを短く設定
    timeout-minutes: 5
    # ジョブを実行する環境は最新のubuntu
    runs-on: ubuntu-latest
    steps:
    # リポジトリのソースコードをチェックアウト
    - uses: actions/checkout@v4
    # nodeのバージョン
    - uses: actions/setup-node@v4
      with:
        node-version: 22.x
    # 依存関係をインストール    
    - name: Install dependencies
      run: npm ci
    # Playwrightが使用するブラウザをインストール
    - name: Install Playwright Browsers
      run: npx playwright install --with-deps
    # test実行  
    - name: Run Playwright tests
      run: npx playwright test
    # テストの結果をアップロード  
    - uses: actions/upload-artifact@v4
      if: ${{ !cancelled() }}
      with:
        name: playwright-report
        path: playwright-report/
        retention-days: 7


```

- `actions/checkout@v4`に関して以下の記事が詳しく書いていて参考になりました。
- [【初心者向け】【入門】GitHub Actionsとは？書き方、デバッグ設定、runs-onやcheckoutなどの仕組みや構造も含めて徹底解説](https://qiita.com/shun198/items/14cdba2d8e58ab96cf95)

## reg-actionsを設定

- Playwrightの`toMatchSnapshot`を使えば、スクリーンショットの差分を確認できるみたいだけど、変更前の画像をリポジトリで管理する必要があるみたいので、今回は管理する必要のないreg-actionsを設定してみる

```.github/workflows/playwright.yml
name: Playwright Tests
on:
  # mainへプルリク作成時に実行
  pull_request:
    branches: [ main ]
permissions:
  # リリースを作成するアクションを許可
  contents: write
  # ワークフローの実行をキャンセルするアクションを許可
  actions: write
  # プルリクエストにラベルを追加するアクションを許可
  pull-requests: write 

jobs:
  test:
    # タイムアウトを短く設定
    timeout-minutes: 5
    # ジョブを実行する環境は最新のubuntu
    runs-on: ubuntu-latest
    steps:
    # リポジトリのソースコードをチェックアウト
    - uses: actions/checkout@v4
    # nodeのバージョン
    - uses: actions/setup-node@v4
      with:
        node-version: 22.x
    # 依存関係をインストール    
    - name: Install dependencies
      run: npm ci
    # フォントのキャッシュ
    - 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

    # Playwrightが使用するブラウザをインストール
    - name: Install Playwright Browsers
      run: npx playwright install --with-deps
    # test実行  
    - name: Run Playwright tests
      run: npx playwright test
    # テストの結果をアップロード  
    - uses: actions/upload-artifact@v4
      if: ${{ !cancelled() }}
      with:
        name: playwright-report
        path: playwright-report/
        retention-days: 7
    # スクリーンショットを保存
    - uses: reg-viz/reg-actions@v2
      with:
        github-token: "${{ secrets.GITHUB_TOKEN }}"
        image-directory-path: "./screenshots"
```

- プルリクするといい感じにできている。すごい。

![スクリーンショット 2024-11-03 13.28.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/0870b4f5-2f9f-7cd1-f66d-8287cc0b8eef.png)


## エラー①
- 画像がないってなってしまい、画像の差分が表示されない。

![スクリーンショット 2024-11-03 15.13.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/85bfacbe-1061-5593-b49d-5673b78092ac.png)

- スナップショットの保存が上手くいっていないのか？と思い`npx playwright test --update-snapshots`でupdateすることにした。

```.github/workflows/playwright.yml
name: Playwright Tests
on:
  # mainへプルリク作成時に実行
  pull_request:
    branches: [ main ]
permissions:
  # リリースを作成するアクションを許可
  contents: write
  # ワークフローの実行をキャンセルするアクションを許可
  actions: write
  # プルリクエストにラベルを追加するアクションを許可
  pull-requests: write 

jobs:
  test:
    # タイムアウトを短く設定
    timeout-minutes: 5
    # ジョブを実行する環境は最新のubuntu
    runs-on: ubuntu-latest
    steps:
    # リポジトリのソースコードをチェックアウト
    - uses: actions/checkout@v4
    # nodeのバージョン
    - uses: actions/setup-node@v4
      with:
        node-version: 22.x
    # 依存関係をインストール    
    - name: Install dependencies
      run: npm ci
    # フォントのキャッシュ
    - 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

    # Playwrightが使用するブラウザをインストール
    - name: Install Playwright Browsers
      run: npx playwright install --with-deps
    # test実行  
    - name: Run Playwright tests
      run: npx playwright test --update-snapshots
    # テストの結果をアップロード  
    - uses: actions/upload-artifact@v4
      if: ${{ !cancelled() }}
      with:
        name: playwright-report
        path: playwright-report/
        retention-days: 7

    # スクリーンショットを保存
    - uses: reg-viz/reg-actions@v2
      with:
        github-token: "${{ secrets.GITHUB_TOKEN }}"
        image-directory-path: screenshots/
        artifact-name: 'test'
```

- レポートが出るようになりました！PR上でTOPの変更がわかるので便利！
![スクリーンショット 2024-11-03 15.16.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/a53e4225-74f1-d6dd-eb83-3e7078b881ae.png)

- レポートが表示が安定しなかったので、ここはdocker使用したりして、画像の差分を安定して表示できるように工夫が必要かと思いました。今回は小さく実装が目的だったので、その目的はクリアかなと。

# まとめ
実際のプロジェクトでは、S3へ保存した画像と比べたり、テスト/本番環境の差分を比較してどうUIが変更されたのかなど、応用として色々便利に使えそうと思いました。
今回は、差分があったとしてもreg-actionsのワークフローは失敗しないので、要注意なところだな・・と、Playwrightを使ったことなかったので勉強になりました！


# 参考
- [Playwright公式](https://arc.net/l/quote/ipyxfybe)
- [GitHub Actions](https://arc.net/l/quote/ufyjvznv)
- [reg-actions](https://arc.net/l/quote/cjddzbwr)
- [GITHUB_TOKEN の権限を制御する](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/controlling-permissions-for-github_token)