ビジュアルリグレッションテストは、ページのスクリーンショットを承認済みの基準画像と比べ、ピクセルが変わると失敗させます。モバイルのレイアウトでは、機能テストでは見つからない問題を検出します。たとえば、360pxの画面からはみ出したメニューボタン、3行に折り返した見出し、タブレット幅で崩れるカードグリッドなどです。PlaywrightのtoHaveScreenshot()を使えば、スマートフォンとタブレットのビューポートで無料で実行できます。レビュー作業やクラウド上での複数ブラウザの描画が必要になったときに、有料サービスが見合うかを判断してください。
ビジュアルリグレッションテストの仕組み
ビジュアルテストには4つの段階があります。基準を撮る、変更後にもう一度撮る、2枚の画像を比較する、差分を人が承認または却下する。承認された変更は新しい基準になります。
違いは比較の方法にあります。ピクセル比較(Playwright、BackstopJS)は、変化したピクセル数を数え、設定したしきい値を超えると失敗させます。Applitoolsは厳密なピクセル数ではなく、独自のビジュアルAIで比較します。PercyとChromaticは、レビュー画面を持つホスト型サービスです。どの方法でも、ページが安定している必要があります。時計、広告、アニメーションが2回の実行の間に変わると、本当の問題がなくてもテストは失敗します。以下の設定作業の大部分は、そのノイズを取り除くことに関するものです。

ツールの比較
Playwrightの組み込みのアサーションは、無料でセルフホストする場合を扱います。2026年10月時点での比較は次のとおりです。
| ツール | 差分の方法 | 費用 | 強み |
|---|---|---|---|
Playwright toHaveScreenshot |
ピクセル差分、ローカル | 無料、オープンソース | 1つのランナーでChromium、Firefox、WebKitを扱える。基準画像をリポジトリに置ける |
| BackstopJS | ピクセル差分、ローカル | 無料、オープンソース | テストコードなしで設定だけでシナリオを作れる。HTMLレポート、--dockerオプション |
| Percy(BrowserStack) | クラウドでの差分とレビュー画面 | 無料プランは月5,000スクリーンショット | ホスト型のレビューと承認の流れ。複数ブラウザと複数幅に対応 |
| Chromatic | クラウドでの差分、Storybookベース | 無料プランは月5,000スナップショット、Chromeのみ | Storybookを使うプロジェクトでのコンポーネント単位のテスト |
| Applitools Eyes | ビジュアルAI | 年額一括払いで月667ドルから。無料トライアルあり | ピクセル差分が検出する描画ノイズを無視するよう設計されたAI比較。大規模なクロスブラウザ環境 |
Percyは、ページごと、ブラウザごと、幅ごとに1スクリーンショットとして数えます。3つのスマートフォン幅とタブレット幅で、2ブラウザに2ページを撮ると、それだけで12スクリーンショットになります。プランの価格と上限は、2026年10月25日時点の各ベンダーの料金ページによるもので、頻繁に変わります。
スマートフォンのビューポートでビジュアルテストを設定する
この設定は@playwright/test 1.64を使います(2026年10月25日時点の最新版)。Node.jsと、テストマシンからアクセスできるサイトが必要です。ローカルでも、公開済みでも構いません。
-
Playwrightとブラウザをインストールします。
npm init playwright@latest -
playwright.config.tsにモバイルのプロジェクトを追加します。各デバイス記述子は、ビューポート、ピクセル比、ユーザーエージェント、タッチ対応を設定し、エンジンを選びます(iPhone 15はWebKitで、Pixel 7はChromiumで動きます)。import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './visual', use: { baseURL: 'http://localhost:4321' }, expect: { toHaveScreenshot: { maxDiffPixelRatio: 0.01 }, }, projects: [ { name: 'pixel-7', use: { ...devices['Pixel 7'] } }, { name: 'iphone-15', use: { ...devices['iPhone 15'] } }, { name: 'tablet', use: { browserName: 'chromium', viewport: { width: 820, height: 1180 } } }, ], });Playwright 1.64では、
Pixel 7の記述子はビューポートが412 × 839、ピクセル比が2.625です。iPhone 15は393 × 659、ピクセル比は3です。maxDiffPixelRatioの1%は出発点の値であり、標準ではありません。プロジェクトに合わせて調整してください。 -
テストを書きます。レイアウトが最も重要なページに絞ってください。
// visual/pages.spec.ts import { test, expect } from '@playwright/test'; const pages = ['/', '/pricing', '/blog']; for (const path of pages) { test(`layout ${path}`, async ({ page }) => { await page.goto(path); await page.evaluate(async () => { await document.fonts.ready; }); await expect(page).toHaveScreenshot({ fullPage: true, mask: [page.locator('[data-testid="live-date"]')], }); }); } -
基準画像を作成して、リポジトリにコミットします。
npx playwright test --update-snapshotsPlaywrightは、テストとプロジェクトごとに1枚のPNGを保存します。ファイル名の末尾にはプロジェクト名とプラットフォームが付きます(例:
...-iphone-15-linux.png)。*-snapshotsフォルダをコミットしてください。 -
すべてのプルリクエストで
npx playwright testを実行します。比較が失敗するとテストも失敗します。npx playwright show-reportでHTMLレポートを開き、失敗内容を確認してください。変更が意図したものであれば、--update-snapshotsで基準を更新し、コード変更と一緒にコミットします。
既定の設定も役立ちます。toHaveScreenshot()は、比較の前に連続する2枚のスクリーンショットが一致するまで待ちます。CSSのアニメーションとトランジションを無効にし、テキストのキャレットを隠します(Playwright APIのドキュメント)。scaleオプションの既定値は"css"で、CSSのピクセル1つに画像のピクセル1つを対応させます。そのためiPhoneの基準画像は1179pxではなく393pxの幅になり、差分の計算も速く終わります。
基準を作る前にレイアウトを確認する
基準は、撮影した時点のページの見た目を記録します。最初のスクリーンショットがモバイルのレイアウトの崩れた状態を含んでいると、それ以降の実行はその不具合を「正しい」として守ってしまいます。基準を作る前に、テストする各幅でページを見てください。
- モバイル幅でページを開きます。Mobile View Extensionは、タブをスマートフォンのユーザーエージェント、画面サイズ、ピクセル比で読み込み直し、2台のデバイスを並べて表示することもできます。そのため、同じURLのスマートフォンとタブレットのレイアウトをまとめて確認できます。localhostでも動作します。
- ページ全体をスクロールし、ナビゲーション、折り返す見出し、表、固定要素を確認します。
- 見つかった問題を直してから、
--update-snapshotsを実行します。
この拡張機能はデスクトップ版Chromeのエンジンとマウス操作で描画するため、表示されるのはレイアウトであり、Safariの描画やタッチ操作ではありません。PlaywrightのWebKitプロジェクトはその差の一部を埋め、残りは実機のiPhoneで確認します。手動確認のより広い手順はレスポンシブデザインのテスト方法に、チケット用の一時的なキャプチャはウェブサイトのモバイル向けスクリーンショットの撮り方にあります。
不安定な差分を直す
不安定な差分は、実行のたびに変わるものが原因です。そのため、対策はそれらを毎回同じにすることです。よくある原因と対処は次のとおりです。
| 原因 | 対処 |
|---|---|
| OSやブラウザのバージョンが違う | 基準は同じ種類のマシンで作成して比較する。CIでは、Playwrightのバージョンに合った固定のDockerイメージを使う(例:mcr.microsoft.com/playwright:v1.64.0-noble) |
| Webフォントが遅れて届く、またはフォールバックが使われる | document.fonts.readyを待つ。フォントを自前でホストし、CDNの遅延で描画が変わらないようにする |
| 日付、カウンター、広告、埋め込み | maskでロケーターを隠す。またはstylePathのスタイルシートで隠す |
| カルーセルやJavaScriptのアニメーション | テスト専用のフラグで止める。toHaveScreenshotが凍結するのはCSSとWeb Animationsだけ |
| ホバーの状態 | 先にマウスを外す:page.mouse.move(-1, -1) |
| フルページのスクリーンショットでの遅延読み込み画像 | 最後までスクロールするか、画像の読み込みを待ってから撮る |
| アンチエイリアスのノイズ | threshold(既定値0.2)を上げるか、maxDiffPixelsを設定する |
最初の行は、ランダムに見える失敗の説明になります。Playwrightのドキュメントは、描画がホストのOS、バージョン、設定、ハードウェアによって変わり得ると警告しています。プラットフォームがスナップショットの名前に含まれるのもそのためです。たとえばMacで作成した基準(-darwin.png)は、LinuxのCIランナー(-linux.png)では使われません。基準の環境は1つに決めてください。CIと同じDockerイメージの中でnpx playwright test --update-snapshotsを実行すると、この差の原因がなくなります。
すべてのテストでノイズを隠すには、stylePathのファイルが最も整理しやすい方法です。
/* visual/screenshot.css */
iframe, [data-testid="cookie-banner"] { visibility: hidden; }
import path from 'node:path';
await expect(page).toHaveScreenshot({
stylePath: path.join(__dirname, 'screenshot.css'),
});
同じオプションを設定ファイルのexpect.toHaveScreenshotに書けば、すべてのテストに適用されます。
ビジュアルテストでわからないこと
スクリーンショットの差分が確認するのは、実行したエンジンでのピクセルだけです。ボタンが動くか、タップ領域が十分な大きさか、ブラウザのツールバーやキーボードを含む実機の振る舞いは確認できません。PlaywrightのWebKitはSafariに近いものの、iPhoneのSafariそのものではありません。機能テストをビジュアルテストと並べて使い、実際のブラウザはクロスブラウザテストのチェックリストでカバーしてください。
スイートは小さく保ってください。ページ、プロジェクト、状態が増えるほど、確認する画像が増えます。主要なテンプレート3〜10個(トップ、一覧、詳細、フォーム)を、スマートフォンの2つの幅とタブレットの1つの幅で実行すると、1回の実行で確認するのは数十枚に収まります。この数は出発点であり、設計上の判断として調整するものです。規則ではありません。
よくある質問
ビジュアルリグレッションテストは無料ですか?
はい、オープンソースのツールを使えば無料です。PlaywrightのtoHaveScreenshot()やBackstopJSは、ローカルでもCIでも費用なしで動きます。Percy、Chromatic、Applitoolsなどの有料サービスは、ホスト型の描画とレビュー画面を追加します。PercyとChromaticには、2026年10月時点で月5,000スクリーンショットまたはスナップショットの無料プランがあります。
ビジュアルテストとスクリーンショットテストの違いは何ですか?
どちらもスクリーンショットを撮り、承認済みの基準と比較します。違いは比較方法です。PlaywrightとBackstopJSは変化したピクセル数を数えますが、ApplitoolsはビジュアルAIを使います(上のツール比較を参照)。
PlaywrightのスクリーンショットがCIでは失敗し、ローカルでは成功するのはなぜですか?
基準を別のOSやブラウザのバージョンで作ったからです。Playwrightはスクリーンショットのファイル名にプラットフォームを含めます。フォントやアンチエイリアスも、macOSとLinuxでは異なります。基準はCIと同じDockerイメージで作成してください。
ビジュアルテストで動的なコンテンツを無視するには?
日付や広告などの要素は、maskオプションで覆うか、stylePathのスタイルシートで隠します。変化する領域全体がある場合は、ページ全体ではなく、安定した要素のスクリーンショットを撮ってください。