visual-testing について
visual-testingは、機能テストで見逃す画面の変化を検出するための、ビジュアルリグレッションテストとベースライン管理のスキルです。Playwrightのスクリーンショット比較、専用ツール、動的コンテンツへの対処、レビュー手順を扱います。
主な機能
- 01Playwrightによるスクリーンショット比較
- 02ピクセル差分しきい値の設定
- 03動的コンテンツのマスクや固定
- 04ビジュアルベースラインの管理とレビュー
- 05Chromatic、Percy、Argos CIの紹介
活用シーン
- 01CIで画面のリグレッションを検出
- 02動的ページのスクリーンショット誤検知を削減
- 03デザイン変更を確認してベースラインを更新
- 04Git内のスクリーンショットベースラインを管理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ petrkindlmann/ qa-skills --skill visual-testingインストール後、Claude Code で/visual-testingを入力して読み込まれたか確認します。
同梱ファイルreferences/recipes.md
詳細
クイックガイド
| 状況 | 移動先 |
|---|---|
| CI でピクセル差分を確認したいだけで、レビュー用 SaaS は不要 | Playwright ビジュアル比較(以下) |
| リポジトリで Storybook を使用している | 専用ツール → Chromatic、または recipes.md「Chromatic を使わない Storybook」 |
| ホスト型のレビュー/承認 UI と AI による差分のトリアージが必要 | 専用ビジュアルテストツール |
| タイムスタンプ/アバター/広告でスクリーンショットが不安定になる | 動的コンテンツのマスキングと固定 |
| ベースラインによって git 履歴が肥大化している | ベースライン管理 → Git LFS |
確認事項
まず .agents/qa-project-context.md を確認してください。存在する場合はその内容を使用し、そこに記載済みの項目は省略してください。
ツールの選択
- Playwright 組み込み機能と専用ツールのどちらを使うか?
toHaveScreenshotは無料で、ベースラインはリポジトリ内に保存されます(git/LFS のコストは自己負担です)。専用ツール(Chromatic、Percy、Argos)はアーティファクトをリポジトリ外に保存し、レビュー用ワークフロー、ブラウザーファーム、履歴追跡、そして 2025 年後半以降は AI による差分トリアージを追加します。チームの規模、レビューの要件、アーティファクトを自分のリポジトリとツール側のどちらに保存したいかを基準に選んでください。 - プロジェクトで Storybook を使用しているか? 使用している場合、Chromatic が自然な選択肢です(すべてのストーリーがビジュアルテストになります)。ただし、費用をかけずに Playwright でストーリーを操作する方法もあります(recipes.md を参照)。Storybook を使用していない場合は、Playwright 組み込み機能または Percy を選びます。
- CI プラットフォームとアーティファクトの予算は? ビジュアルテストでは、大容量のスクリーンショット/差分アーティファクトが生成されます。組み込み機能ではベースラインが git に保存されるため、リポジトリが肥大化します(LFS を使用する場合もあります)。ホスト型ツールでは、サブスクリプション費用がかかりますが、保持するアーティファクトはリポジトリ外に保存されます。規模を拡大する前に、CI のストレージと実行時間に余裕があることを確認してください。
対象範囲
- ページ全体とコンポーネント単位のどちらを撮影するか? ページ全体のスクリーンショットはレイアウトの問題を検出できますが、無関係な変更の影響を受けやすくなります。コンポーネント単位のスクリーンショットは、より安定して対象を絞れます。
- 視覚的に重要なページ/コンポーネントはどれか? すべてに必要なわけではありません。ユーザー向けのページ、マーケティングページ、デザインシステムのコンポーネント、複雑なレイアウトに絞ってください。
- どのビューポートを対象にするか? デスクトップ、タブレット、モバイルなど、考えられるすべての幅を試すのではなく、分析データを基に事前にビューポートの組み合わせを定義してください。
動的コンテンツ
- 実行ごとに何が変わるか? 日付、タイムスタンプ、ユーザー生成コンテンツ、分析 ID、ランダムなコンテンツ、広告、アバターは、すべてマスキングまたは固定が必要です。
- アニメーションやトランジションはあるか? キャプチャ前に無効化または完了させないと、誤検出の原因になります。
- 外部リソースを使用しているか? フォント、CDN の画像、サードパーティーのウィジェットは、実行ごとに変化する可能性があります。
基本原則
1. ビジュアルテストは機能テストでは見逃される問題を検出します
機能テストでは動作を検証します(「送信」をクリックすると成功メッセージが表示される)。ビジュアルテストでは見た目を検証します(成功メッセージが緑色で、正しい位置にあり、フォームと重なっていない)。どちらも必要です。ビジュアルテストは機能テストを補完するものであり、置き換えるものではありません。
2. ベースライン管理が最も難しい部分です
スクリーンショットを撮るのは簡単です。ベースラインの管理、つまりデザインを意図的に変更した際の更新、差分のレビュー、チーム内での承認の調整が本当の課題です。早い段階でレビュー用ワークフローに投資してください。
3. 動的コンテンツは誤検出の原因になります
実行ごとに変化するコンテンツ(タイムスタンプ、アバター、広告、ランダム ID)はすべて、実際のリグレッションではないピクセル差分を生み出します。積極的にマスキングするか、固定してください。誤検出率が 10% のテストスイートは、1 か月以内に無視されるようになります。ホスト型ツール(Percy の Visual Review Agent、Chromatic の AI トリアージ)は、こうした差分の多くを自動で除外するようになりました。それでも、使用するツールにかかわらず、マスキングと固定が最初の防御策です。
4. しきい値の調整は反復して行います
適切なしきい値は、コンポーネント、レンダリングエンジン、「視覚的な違い」とみなす基準によって異なります。まずは厳格な設定(許容誤差ゼロ)から始め、誤検知を確認し、コンポーネントごとに緩和します。しきい値を選んだ理由を記録してください。
5. スクリーンショットはテスト結果ではなく成果物です
スクリーンショットファイルが証拠となります。保存し、バージョン管理し、レビューで確認できるようにしてください。差分を表示せずに「視覚差分を検出しました」とだけ知らせるテストには意味がありません。期待値・実際の画像・差分画像を、必ず CI 成果物としてアップロードしてください。
Playwright のビジュアル比較
Playwright に組み込まれている toHaveScreenshot と toMatchSnapshot を使えば、外部サービスなしでビジュアルリグレッションテストを実施できます。
基本的なスクリーンショット比較
import { test, expect } from '@playwright/test';
test('dashboard matches baseline', async ({ page }) => {
await page.goto('/dashboard');
// Wait for data to load before capturing — never waitForTimeout
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
await expect(page.getByTestId('chart-container')).toBeVisible();
await expect(page).toHaveScreenshot('dashboard.png', { animations: 'disabled' });
});
初回の実行でベースラインが作成されます。2 回目以降の実行では比較が行われ、ピクセルの差がしきい値を超えると失敗します。
設定オプション
await expect(page).toHaveScreenshot('dashboard.png', {
maxDiffPixels: 100, // Allow up to 100 pixels to differ
// OR
maxDiffPixelRatio: 0.01, // Allow up to 1% of pixels to differ
threshold: 0.2, // Per-pixel color difference tolerance (0-1, YIQ space)
animations: 'disabled', // Freeze CSS animations and transitions
caret: 'hide', // Hide blinking cursor
stylePath: './screenshot.css', // Inject a stylesheet at capture to hide dynamic chrome
timeout: 15000, // Wait up to 15s for a stable screenshot
});
デフォルトの比較器は pixelmatch(YIQ 色空間)です。関連する設定項目は comparator: 'pixelmatch' と threshold/maxDiffPixel* です。安定した知覚色モードのキーはないため、mode: は設定しないでください(知覚的 SSIM は内部用/実験的なアンダースコア付き API であり、安定した設定ではありません)。
しきい値の使い分け:
| オプション | 使用する場面 |
|---|---|
maxDiffPixels: 0 | ピクセル単位で完全一致させたいコンポーネント(アイコン、ロゴ、デザインシステムのアトム) |
maxDiffPixels: 50-100 | アンチエイリアスにわずかな差が生じる、ページ全体のレイアウト |
maxDiffPixelRatio: 0.01 | ビューポートによって絶対ピクセル数が変わる、ページ全体のスクリーンショット |
threshold: 0.2 | 色のレンダリングにわずかな差が生じる、クロスブラウザテスト |
playwright.config.ts のビジュアル設定
import { defineConfig } from '@playwright/test';
export default defineConfig({
expect: {
toHaveScreenshot: {
comparator: 'pixelmatch', // Default comparator (YIQ color space)
maxDiffPixelRatio: 0.005, // Global default: 0.5% tolerance
animations: 'disabled',
caret: 'hide',
},
toMatchSnapshot: {
maxDiffPixelRatio: 0.005,
},
},
projects: [
{
name: 'visual-desktop',
use: { viewport: { width: 1280, height: 720 }, colorScheme: 'light' },
testMatch: /.*visual.*\.spec\.ts/,
},
{
name: 'visual-mobile',
use: { viewport: { width: 375, height: 667 }, colorScheme: 'light', isMobile: true },
testMatch: /.*visual.*\.spec\.ts/,
},
],
});
動的コンテンツのマスクと固定
相補的な方法が 2 つあります。マスクで特定の要素を空白にし、stylePath/固定化で時間やアニメーションによるノイズを取り除きます。
test('profile page visual test', async ({ page }) => {
await page.goto('/profile');
await expect(page.getByRole('heading', { name: 'Profile' })).toBeVisible();
await expect(page).toHaveScreenshot('profile.png', {
mask: [
page.getByTestId('user-avatar'), // User-specific image
page.getByTestId('last-login-time'), // Timestamp
page.getByTestId('activity-feed'), // Dynamic content
],
maskColor: '#FF00FF', // Visible mask color for debugging
});
});
カーソル、アニメーション、動的な UI については、要素ごとのマスクよりも stylePath(キャプチャ時に挿入するスタイルシート)を優先してください。宣言的で、DOM が変更されても影響を受けません。タイムスタンプやライブデータを排除するには、page.clock.setFixedTime で時計を固定し(時計を完全に停止するため、スクリーンショットの決定性を確保するのに最適です。page.clock.install を使うと、シード値から時刻を進められます)、page.route と route.fulfill で API をスタブ化します。
データを完全に固定するレシピ(時計+ルート+フォントの中断+getAnimations().finish())、stylePath の例、コンポーネントの状態ごとのスクリーンショットについては、references/recipes.md を参照してください。
ベースラインの更新
# Update all baselines (when design intentionally changes)
npx playwright test --update-snapshots
# Update baselines for specific tests only
npx playwright test visual-dashboard --update-snapshots
# Review what changed before committing
git diff --stat # See which baseline files changed
npx playwright show-report # Visually review expected/actual/diff for each
ベースラインの更新ワークフロー:
- デザイン変更を実装します。
- ビジュアルテストを実行します。テストは想定どおり差分を検出して失敗します。
show-reportで各差分をレビューし、変更が意図したものか確認します。- ベースラインを更新します:
npx playwright test --update-snapshots。 - デザイン変更に言及したメッセージとともに、更新したベースラインをコミットします。
- PR のレビュー担当者が、ファイル差分だけでなく、ベースライン画像が正しいことも確認します。
コンポーネント単位のテストとレスポンシブテスト
ページ全体ではなく、コンポーネント(例:getByRole('table'))をスクリーンショットにします。より安定し、対象も絞れます。テストごとに API をスタブ化して、空・エラー・通常の状態を再現します。レスポンシブ対応をテストするには、VISUAL_VIEWPORTS 配列(isMobile フラグ付きのモバイル/タブレット/デスクトップ)をループ処理するか、ビューポートごとに Playwright のプロジェクトを 1 つ定義します。コンポーネントの状態ごとのテストとレスポンシブ対応のループ処理のレシピについては、references/recipes.md を参照してください。
専用のビジュアルテストツール
ホスト型のレビュー/承認 UI、クロスブラウザーのレンダリングファーム、履歴管理、または AI 支援による差分のトリアージが必要で、アーティファクトをサブスクリプションサービス上に保存することに問題がない場合に利用してください。
| ツール | 最適な用途 | 統合方法 | 主な機能 |
|---|---|---|---|
| Chromatic | プロジェクトで Storybook を使用している | 各ストーリーをビジュアルテストとして実行 | レビュー/承認 UI、クロスブラウザー、TurboSnap と AI トリアージ |
| Percy | Storybook を使わず、複数ブラウザーでのテストが必要 | SDK を介して任意のフレームワークに対応 | 複数幅でのキャプチャ、CSS オーバーライド、AI Visual Review Agent(誤検出の約 40%を自動除外)、BrowserStack Test Observability |
| Argos CI | オープンソースを優先し、予算を抑えたい | Playwright レポーター | セルフホスト可能なプラン、充実したクラウド無料プラン |
AI による差分のトリアージ(2026 年の大きな変化): Percy の Visual Review Agent と Chromatic の AI トリアージは、多くの差分をノイズと実際の変更に自動分類し、レビュー疲れを軽減します。これが、組み込みのベースラインではなくホスト型ツールに料金を支払う最大の理由です。ただし、動的コンテンツのマスクや固定が不要になるわけではなく、その必要性を軽減するにとどまります。
避けてください: Lost Pixel — 2026 年 4 月 22 日にリポジトリがアーカイブされました(読み取り専用)。代わりに Argos、Chromatic、または Playwright 組み込みの
toHaveScreenshotを使用してください。
Chromatic GitHub Action、Percy の percySnapshot、Argos の argosScreenshot、および Chromatic を使わずに Storybook を利用する場合のスニペットについては、references/recipes.md を参照してください。
ベースラインの管理
Git に保存するベースライン、LFS、プラットフォームタグ
Playwright は、レンダリングが OS ごとに異なるため、プラットフォームごとにタグ付けしたベースラインをテストファイルと同じ場所に保存します。
e2e/tests/visual/
dashboard.visual.spec.ts
dashboard.visual.spec.ts-snapshots/
dashboard-chromium-linux.png # Platform-specific baselines
dashboard-chromium-darwin.png
dashboard-firefox-linux.png
利点: コードと一緒にバージョン管理され、PR でレビューでき、オフラインでも利用できます。欠点: リポジトリのサイズが増え、大きな PNG によって Git の履歴が肥大化します。
肥大化を防ぐには Git LFS を使用し、snapshotPathTemplate でレイアウトをカスタマイズします。
# .gitattributes
*.png filter=lfs diff=lfs merge=lfs -text
// playwright.config.ts
export default defineConfig({
snapshotPathTemplate: '{testDir}/__snapshots__/{testFilePath}/{arg}-{projectName}{ext}',
});
ベースラインにはプラットフォームタグが付くため、CI のレンダリング環境と常に一致するよう、CI で使用するものと同じ Docker イメージで生成してください。開発者のノート PC でキャプチャしたベースラインは、決してコミットしないでください。references/recipes.md にある Docker CI ジョブ(@playwright/test のバージョンに合わせた mcr.microsoft.com/playwright:v1.60.0-noble)を参照してください。
レビューと承認のワークフロー
- CI がビジュアル差分を検出し、期待値/実際の画像/差分画像をアーティファクトとしてアップロードします。
- PR のレビュー担当者が
show-report(またはホスト型ツールの UI)で差分を確認します。 - 意図した変更の場合:ベースラインを更新(
--update-snapshots)して、再度コミットします。 - 意図しないリグレッションの場合:コードを修正し、テストを再実行します。
アンチパターン
1. マスクせずにページ全体をスクリーンショットする
動的コンテンツ(タイムスタンプ、アバター、ライブデータ)をマスクせずにページ全体をキャプチャすると、実行のたびに差分が発生します。チームはビジュアルテストを信頼しなくなります。動的な領域は必ずマスクし、時間に依存するコンテンツは固定してください。
2. CI にアーティファクトを保存しない
スクリーンショットのアーティファクトをアップロードせずにビジュアルテストを実行すると、失敗時に確認できる期待値/実際の画像/差分画像がなく、ローカルでの再現を強いられます。その結果、レンダリングが異なる可能性があります。スクリーンショット、差分、レポートは必ず CI アーティファクトとしてアップロードしてください。
3. ベースライン更新時のレビュープロセスがない
変更をレビューせずに --update-snapshots を実行してコミットすると、リグレッションがベースラインに取り込まれ、見えなくなります。更新のたびに、ファイルの差分だけでなく、変更前後の画像もレビューしてください。
4. 不安定なコンポーネントをビジュアルテストする
設計上変更されるコンポーネント(A/B テスト、パーソナライズされたコンテンツ、頻繁に切り替わるバナー)のビジュアルテストは、意図した変更によって常に失敗します。対象から除外するか、コンテンツをスタブにしてください。
5. ページ全体にピクセル単位の完全一致を求める
ページ全体のスクリーンショットに maxDiffPixels: 0 を設定すると、ブラウザー/OS/フォントの更新によるサブピクセルのレンダリング差異が失敗として検出されます。ページ全体には maxDiffPixelRatio: 0.005 を使用し、ロゴやアイコンなど、小さく重要なコンポーネントに限って許容差をゼロにしてください。
6. 一貫したレンダリング環境がない
さまざまな開発者マシンでビジュアルテストを実行し、ベースラインが一致することを期待すると、フォントのレンダリング、アンチエイリアス、スケーリングにプラットフォームごとの差が生じます。一貫した CI Docker 環境で実行し、そこでベースラインを生成してください。
7. アニメーション処理を省略する
アニメーションを無効にせずにキャプチャすると、遷移の途中のフレームが取得され、ランダムな差分が発生します。キャプチャ前に animations: 'disabled'、継続時間をゼロにする stylePath、または getAnimations().finish() を使用してください。
検証
完了とする前に、まず最小のテストでベースラインが生成され、比較ツールが実行されることを確認してください。
npx playwright test --grep @visual # baselines generate (first run) / compare (later)
npx playwright test --grep @visual --update-snapshots # regenerate intentionally
npx playwright show-report # diff artifacts (expected/actual/diff) render
git check-attr filter -- "e2e/**/*.png" # prints "filter: lfs" if LFS is wired
初回の実行で *-snapshots/*.png ファイルが書き込まれ、2 回目の実行でそれらを基準にテストが成功すれば、パイプラインが最初から最後まで正しく動作することを確認できます。
完了条件
- 一貫した CI Docker 環境(ローカルではない)でベースラインのスクリーンショットをキャプチャし、コミットしていること。
playwright.config.tsでグローバルなmaxDiffPixelRatioが設定され、少なくとも 1 つのアイコン/ロゴのテストでmaxDiffPixels: 0に上書きされていること。- キャプチャ前に、
mask、stylePath、またはクロック/ルートのスタブを使って動的コンテンツ(タイムスタンプ、アバター、ライブ API データ)をマスクするか、固定していること。 - ビジュアル差分が設定済みのしきい値を超えた場合、CI パイプラインがマージをブロックすること。
- Git LFS(
.gitattributesに*.png filter=lfsがある)またはリポジトリ外のホスト型ツールを使ってベースラインを管理し、リポジトリが肥大化しないようにしていること。 - レビューワークフローが定義されていること:差分のレビュー担当者、意図的な変更に伴うベースラインの更新方法、PR レビュアーがベースライン画像を承認する手順。
参考ファイル(references/ 内)
- recipes.md — データを固定したキャプチャ(クロック+ルート+フォント読み込みの中断)、
stylePathによるマスキング、コンポーネントの状態のスクリーンショット、レスポンシブなビューポートのループ、Chromatic/Percy/Argos のスニペット、Chromatic を使わない Storybook、Docker CI ジョブ。
関連スキル
- playwright-automation — Playwright ベースのビジュアルテストの基盤です。ページオブジェクトモデル、フィクスチャ、テスト構成はここでも適用できます。
- ci-cd-integration — ビジュアルテストの実行、成果物のアップロード、レビューワークフローの組み込みに使うパイプライン設定です。
- cross-browser-testing — 1 つのレンダリングのベースライン差分ではなく、ブラウザー間のレンダリングマトリックスが目的の場合に使います。ビューポート/プロジェクト設定が重複します。
- selector-drift-recovery — 再設計によって一度に多くのベースラインが崩れ、テストごとのビジュアル差分確認ではなく、一括再生成が必要な場合に使います。
- qa-project-context — 視覚的に重要なページと、存在する動的コンテンツを記録します。