Playwright + GitHub ActionsでVRT環境を作る
はじめに
GitHub Actionsを使用し、PlaywrightでVisual Regression TestをCIのDocker上で実施し、Slack通知まで行う基盤を構築したので、自分用の記録として手順を残しておきます。
環境
- Vercel
- Next.js "15.2.1",
- Playwright ^1.51.0",
環境はとりあえずVercelを使ってますが、複数環境を作れるのであれば何でも問題ないです。
下記完成品のURLです。
PlaywrightでVRTを実装する
Playwright環境をセットアップ
npm init playwright@latest
スクリプトを追加する
テスト実行と、スクリーンショットを撮るだけのスクリプトを追加します。
"playwright": "playwright test",
"playwright:update": "playwright test --update-snapshots",
Playwright.config.ts
を編集
BASE_URL
を参照するようにします。testDir
は自身がテストを置きたい任意のパスを
export default defineConfig({
testDir: './src/tests/__vrt__', // 任意の好きなpathで
...
use: {
baseURL: process.env.BASE_URL ?? 'http://127.0.0.1:3000',
},
テストを作成する
toHaveScreenshot
を使用してスクリーンショットの保存・比較を行います。
const ROUTE = {
home: () => '/',
services: () => '/services',
about: () => '/about',
contact: () => '/contact',
auth: {
signin: () => '/auth/signin',
register: () => '/auth/register',
},
} as const;
export type TestRoute = {
name: string;
path: string;
};
export const testRoutes = [
{
name: 'Home',
path: ROUTE.home(),
},
{
name: 'Services',
path: ROUTE.services(),
},
{
name: 'About',
path: ROUTE.about(),
},
{
name: 'Contact',
path: ROUTE.contact(),
},
{
name: 'SingIn',
path: ROUTE.auth.signin(),
},
{
name: 'Register',
path: ROUTE.auth.register(),
},
] as const satisfies TestRoute[];
test.describe('Visual Regression Testing', () => {
for (const route of testRoutes) {
test(`snapshot test ${route.name}`, async ({ page }) => {
await page.goto(route.path);
await expect(page).toHaveScreenshot([route.name, `${route.path === '/' ? 'home' : route.path}.png`], {
fullPage: true,
});
});
}
});
デプロイする
自分はVervelを使ったのでとりあえずVercelにデプロイする。
Preview 環境を作成を作成する
Vercelだとデフォルトでmain以外のbranchを作成しpushされるとPreview環境が整備されるので、previewブランチを作成してpushするだけでOKです。
Github Page を作成する
--orphan オプションを使って、親コミットを持たない gh-pages ブランチ作成
git checkout --orphan gh-pages
全部空にする
git rm -rf .
空コミット作成
git commit --allow-empty -m "Setup GitHub Pages"
リポジトリへ push
git push --set-upstream origin gh-pages
以下を参考にしてます。
GitHub Actions上で実行したPlaywrightのレポートをGitHub Pagesで見るzenn.dev
Githubの設定をする
Developer Settingsから Personal Access Token (PAT) を作成する
Github Actionsの bot に権限を与える為に使います。
(本当はGithub Appsトークンの方がいいらしい)
GitHub Appsトークン解体新書:GitHub ActionsからPATを駆逐する技術zenn.dev
Repository permissions の Contents
を Read and write
にするだけでOK。
SlackでWeb Hook URLを取得
リポジトリ内にある Security の Secrets and vatiavles の Actions の中にある Repository secrets にそれぞれを登録する
VRT を実行する GitHub Actions ワークフローを作成する
name: Playwright VRT
on:
deployment_status:
jobs:
playwright:
if: github.event_name == 'deployment_status' && github.event.deployment_status.state == 'success' && github.event.deployment.environment == 'preview'
name: visual-regression-test
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.51.0-noble
options: --user 1001
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Capture Production Screenshots
run: npm run playwright:update
env:
BASE_URL: // Preview環境URL
- name: Compare Screenshot To Preview
run: npm run playwright
env:
BASE_URL: ${{ github.event.deployment_status.environment_url }}
- name: Upload Test Report
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report
publish_report:
name: Publish Report
needs: [playwright]
if: github.event_name == 'deployment_status' && github.event.deployment_status.state == 'success' && github.event.deployment.environment == 'preview'
runs-on: ubuntu-latest
env:
HTML_REPORT_URL_PATH: reports/${{ github.run_id }}/${{ github.run_attempt }}
outputs:
report_url_path: ${{ env.HTML_REPORT_URL_PATH }}
test_status: ${{ needs.playwright.result }}
steps:
- name: Checkout GitHub Pages Branch
uses: actions/checkout@v4
with:
ref: gh-pages
token: ${{ secrets.PAT }}
- name: Set Git User
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Download Test Report
uses: actions/download-artifact@v4
with:
name: playwright-report
path: ${{ env.HTML_REPORT_URL_PATH }}
- name: Push HTML Report
run: |
git add .
git commit -m "VRT Report: ${{ github.run_id }} (attempt: ${{ github.run_attempt }})"
for i in {1..5}; do
git pull --rebase
git push https://${{ secrets.PAT }}@github.com/${{ github.repository }}.git gh-pages && break || sleep 10
done
notify_slack:
name: Notify Slack
if: github.event_name == 'deployment_status' && github.event.deployment_status.state == 'success' && github.event.deployment.environment == 'preview'
needs: [publish_report]
runs-on: ubuntu-latest
env:
REPORT_URL_PATH: ${{ needs.publish_report.outputs.report_url_path }}
TEST_STATUS: ${{ needs.publish_report.outputs.test_status }}
steps:
- name: Construct Report URL
run: echo "REPORT_URL=https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/${{ env.REPORT_URL_PATH }}/index.html" >> $GITHUB_ENV
- name: Wait for GitHub Pages Deployment
run: |
echo "Waiting for GitHub Pages deployment..."
for i in {1..10}; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$REPORT_URL")
if [ "$STATUS" -eq 200 ]; then
echo "GitHub Pages is live!"
break
fi
echo "Not ready yet. Retrying in 10 seconds..."
sleep 10
done
- name: Determine Status Message
run: |
if [ "$TEST_STATUS" == "success" ]; then
echo "STATUS_EMOJI=✅" >> $GITHUB_ENV
echo "STATUS_TEXT=成功" >> $GITHUB_ENV
else
echo "STATUS_EMOJI=⚠️" >> $GITHUB_ENV
echo "STATUS_TEXT=失敗あり" >> $GITHUB_ENV
fi
- name: Send Slack Notification
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
curl -X POST -H 'Content-type: application/json' \
--data "{
\"blocks\": [
{
\"type\": \"header\",
\"text\": {
\"type\": \"plain_text\",
\"text\": \"$STATUS_EMOJI VRT Report 生成完了 - テスト: $STATUS_TEXT\"
}
},
{
\"type\": \"section\",
\"text\": {
\"type\": \"mrkdwn\",
\"text\": \"*リポジトリ:* ${{ github.repositoryUrl }}\\n*デプロイURL:* ${{ github.event.deployment_status.environment_url }}\"
}
},
{
\"type\": \"actions\",
\"elements\": [
{
\"type\": \"button\",
\"text\": {
\"type\": \"plain_text\",
\"text\": \"レポートを表示\"
},
\"url\": \"$REPORT_URL\",
\"style\": \"primary\"
},
{
\"type\": \"button\",
\"text\": {
\"type\": \"plain_text\",
\"text\": \"GitHub Actionsの結果を表示\"
},
\"url\": \"https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\"
}
]
}
]
}" \
"$SLACK_WEBHOOK_URL" || echo "Failed to send Slack notification"
ワークフローのトリガー設定
deployment_status
イベントが発生した際にワークフローが起動します。
on:
deployment_status:
VRT実行 (playwright)
条件分岐
- デプロイメントが成功 (
state == 'success'
) - 環境が
preview
である場合にのみ実行されます。
if: github.event_name == 'deployment_status' &&
github.event.deployment_status.state == 'success' &&
github.event.deployment.environment == 'preview'
実行環境
Playwright公式が準備してくれている環境をそのまま利用
Docker | PlaywrightIntroductionplaywright.dev
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.51.0-noble
本番環境に対してスクリーンショットの取得
playwright:update
スクリプトで本番環境(固定URL)のスクリーンショットを取得し、基準画像として保存します。
- name: Capture Production Screenshots
run: npm run playwright:update
env:
BASE_URL: // 本番環境URL
Preview環境との比較
デプロイされたプレビュー環境のURLを動的に取得し、基準画像との差分を検出します。
- name: Compare Screenshot To Preview
run: npm run playwright
env:
BASE_URL: ${{ github.event.deployment_status.environment_url }}
テストレポートのアップロード
テスト結果(playwright-report
)をアーティファクトとして保存します。
always()
でテスト失敗時も実行します。
- name: Upload Test Report
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report
レポートのGitHub Pages公開 (publish_report)
環境変数と出力を定義
後続のJobで使用するレポートURLパスとテスト結果ステータスを定義します。
env:
HTML_REPORT_URL_PATH: reports/${{ github.run_id }}/${{ github.run_attempt }}
outputs:
report_url_path: ${{ env.HTML_REPORT_URL_PATH }}
test_status: ${{ needs.playwright.result }}
GitHub Pagesブランチのチェックアウト
uses: actions/checkout@v4
with:
ref: gh-pages
token: ${{ secrets.PAT }}
gh-pages
ブランチを操作するために、先ほど作成した PAT
を使用します。
Slack通知 (notify_slack)
レポートURLの構築
GitHub PagesのURLを環境変数に設定します。
echo "REPORT_URL=..." >> $GITHUB_ENV
GitHub Pagesのデプロイ待ち
レポートがアクセス可能になるまで最大10回(100秒)待機します。
curl -s -o /dev/null -w "%{http_code}" "$REPORT_URL"
テスト結果に応じたメッセージ生成
if [ "$TEST_STATUS" == "success" ]; then ...
Slack通知の送信
SlackのIncoming Webhookを使用して、テスト結果を送信します。
curl -X POST -H 'Content-type: application/json' ...
全体のフローを整理
- Playwright設定
toHaveScreenshot
でスクリーンショット比較を実装し、基準画像を管理。- 本番環境(固定URL)とPreview環境(動的URL)の差分を検出。
- デプロイ & 環境構築
- Vercelで本番/Preview環境を自動作成。
gh-pages
ブランチをGitHub Pagesとして設定し、テストレポートを公開。
- GitHub Actions連携
- Previewデプロイ成功時にワークフローをトリガー。
- PlaywrightでVRTを実行し、結果をアーティファクトとして保存。
- レポート & 通知
- テスト結果をGitHub Pagesでホスティング。
- Slackに差分検出結果とレポートリンクを通知(成功/失敗ステータス付き)。
Vercelの時だけ気をつけるポイント
Preview環境にアクセス時の認証を突破する
- settings→deployment-protection
- Vercel Authenticationをオフにする(本当は認証プロセスを挟み込む方がベターだが今回はめんどくさいので)
Preview環境のVercel ToolbarをOFFにする
- Preview環境に対してスクリーンショットを撮っているため、このToolbarのせいで差分がでてしまう為
ブランチ制御を追加する
gh-pages
ブランチがpushされる度にVercelでデプロイしようとしてしまうので無効にする必要がある
まとめ
差分が意図したものであるかどうか、の判断ができるレベルの最低限の実装はできました。
しかし実運用だとページ数が増えるに連れてテストの量が肥大化してくるので差分検出で必要な箇所だけ行う仕組みや、PullRequestのワークフローにいい感じに組み込んだりと、実際の開発フローにどういう風に組み込むかはまだ色々と考えないといけないところがありそうです。