Visual Regression Test 導入その2

今日は[Visual Regression Test 導入その1](https://qiita.com/y_mukasa/items/7e5af84f01745d3324c7)の続きをします。

:::note warn
いろいろと端折っているのでこのままコピペしても動きません
:::

## 技術選定

今回はアプリケーションにアクセスし画面を撮影できるツールが必要になります。E2Eテストツールとして採用しているPlaywrightが利用できそうだったので使う事にしました。

### Playwright

https://playwright.dev/

E2Eテストツール。スクリーンショットの撮影、正解画像との比較、レポーティングすべてをこなせる。

## 構成図

CIはGitHub Actionsを使用しています。ビジュアルのテストなのでデータを固定しないといらぬ差分が生まれてしまいますが、開発環境のアプリケーションを使用するとテストデータの扱いがややこしくなるため、GitHub Actions上でアプリケーションを起動するスタイルを取りました。

![vrt.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3889009/8850dafc-a5d6-f299-b85b-27368e506a03.png)


## 作っていく

### 各種インストール

まずは選定した各種ツールをインストールします。

```bash
pnpm add -D playwright
```

### VRTの設定

E2Eテストで使用するPlaywrightの設定ファイルとは分けてVRT用の設定ファイルを作成します。

```tsx
import { defineConfig } from '@playwright/test';
import config from './playwright.config';

export default defineConfig({
    // 基本的にはE2Eテストと同じ設定
    ...config,
    // テストファイルをディレクトリごとE2Eテストとは分ける
    testDir: './vrt',
    // S3にアップロードしやすいようにsnapshotをひとつのディレクトリにまとめる
    snapshotDir: './vrt/snapshot/',
    use: {
        ...config.use,
        // Service ContainersにGitHub Actionsランナーからアクセスする時はlocalhostを指定する
        baseURL: 'http://127.0.0.1:3000',
    },
    reporter: 'html',
});

```

### 各種スクリプト

`package.json` にVRT関連のスクリプトを追加します

```json
"vrt.page": "playwright test --config playwright.vrt.config.ts",
"vrt.update": "playwright test --config playwright.vrt.config.ts --update-snapshots",
```

- `vrt.page` VRTを実行します。VRT用設定ファイルを指定します。
- `vrt.update` 正解イメージを撮影します。

### GitHub Actions

まずは `pull request` 時のVRT本体を作っていきます。前回と違うのはService Containerを利用しているところくらいです。

```yaml
name: ビジュアルリグレッションテスト

on: pull_requst

# 実行内容
jobs:
    # jobID
    visual_regression_test:
        name: ビジュアルリグレッションテスト
        runs-on: ubuntu-latest
        services:
            # アプリケーションを起動する
            app:
                image: ${{ vars.image-path }}
                credentials:
                    username: ${{ secrets.username }}
                    password: ${{ secrets.password }}
                ports:
                    - '3000:3000'
        permissions:
            id-token: write # AWS認証のため
            contents: read
            packages: read
            pull-requests: write

        # 実行コマンドたち
        steps:
            # node installとか略
              
            - name: AWS認証
              uses: aws-actions/configure-aws-credentials@v4
              with:
                  aws-region: ap-northeast-1
                  role-to-assume: ${{ secrets.aws-role-arn }}

            - name: mainブランチの画像をダウンロード
              run: aws s3 sync s3://${{ vars.snapshot-s3-bucket }}/ ./vrt/snapshot/

            - name: playwrightのブラウザをインストール
              run: pnpm exec playwright install --with-deps

            - name: ビジュアルリグレッションテストの実行
              id: visual-regression-test
              run: pnpm vrt.page
              continue-on-error: true # 差分があっても失敗とはマークしない

            - name: テスト結果を保存
              id: save-report
              # VRTが実行された場合のみ保存
              if: always() && (steps.visual-regression-test.outcome == 'success' || steps.visual-regression-test.outcome == 'failure')
              run: aws s3 sync ./playwright-report/ s3://${{ vars.report-s3-bucket }}/

			# 結果のPR連携 略
```

GitHub Actions内でアプリケーションを起動するためService Containersの機能を利用します。

https://docs.github.com/ja/actions/use-cases-and-examples/using-containerized-services/about-service-containers

```yaml
        services:
            # アプリケーションを起動する
            app:
                image: ${{ vars.image-path }}
                credentials:
                    username: ${{ secrets.username }}
                    password: ${{ secrets.password }}
                ports:
                    - '3000:3000'
```

S3から画像をDLするのに必要なAWSの認証を済ませてから比較用の画像をDLします

```yaml
            - name: AWS認証
              uses: aws-actions/configure-aws-credentials@v4
              with:
                  aws-region: ap-northeast-1
                  role-to-assume: ${{ secrets.aws-role-arn }}

            - name: mainブランチの画像をダウンロード
              run: aws s3 sync s3://${{ vars.snapshot-s3-bucket }}/ ./vrt/snapshot/
```

Playwrightがテストに使用するブラウザをインストールします。既にインストール済みのコンテナがPlaywrightから公開されていますが、すべてが揃っている訳ではないようで結局差分のインストールが必要だったり、AWS CLIは手動でインストールが必要だったり、結果的に実行時間がさほど変わらなかったため、ランナーを使用しワンライナーで実行できるPlaywrightブラウザインストールの方を実施するようにしました。

```yaml
            - name: playwrightのブラウザをインストール
              run: pnpm exec playwright install --with-deps
```

先ほど追加したスクリプトを利用して画像比較＆レポートを出力します。ここでは省略していますがこの結果を以ってプルリクエストへコメントする内容を変更するため、差分が出ても失敗とはせずCIを続行させます。

```yaml
            - name: ビジュアルリグレッションテストの実行
              id: visual-regression-test
              run: pnpm vrt.page
              continue-on-error: true # 差分があっても失敗とはマークしない
```

最後に結果レポートをS3へ保存します

```yaml
            - name: テスト結果を保存
              id: save-report
              # VRTが実行された場合のみ保存
              if: always() && (steps.visual-regression-test.outcome == 'success' || steps.visual-regression-test.outcome == 'failure')
              run: aws s3 sync ./playwright-report/ s3://${{ vars.report-s3-bucket }}/
```

次に `merge` 時のイメージの更新を作ります。

```yaml
name: イメージ更新

on: pull_requst

# 実行内容
jobs:
    # jobID
    update_images:
        name: 正解イメージ更新
        runs-on: ubuntu-latest
        services:
            # アプリケーションを起動する
            app:
                image: ${{ vars.image-path }}
                credentials:
                    username: ${{ secrets.username }}
                    password: ${{ secrets.password }}
                ports:
                    - '3000:3000'
        permissions:
            id-token: write # AWS認証のため
            contents: read
            packages: read
            pull-requests: write

        # 実行コマンドたち
        steps:
            # node installとか略

            - name: playwrightのブラウザをインストール
              run: pnpm exec playwright install --with-deps

            - name: スナップショットの更新
              id: update-snapshot
              run: pnpm vrt.update
              
            - name: AWS認証
              uses: aws-actions/configure-aws-credentials@v4
              with:
                  aws-region: ap-northeast-1
                  role-to-assume: ${{ secrets.aws-role-arn }}

            - name: スナップショットのアップロード
              run: aws s3 sync ./vrt/snapshot/ s3://${{ vars.snapshot-s3-bucket }}/pages/ --delete
```

最後のスナップショットのアップロード以外はVRT本体と変わりません

## 最後に

まだ実運用前なのでいったんこれで様子見していきます。