VRT(Visual Regression Testing、ビジュアルリグレッションテスト)は、画面のスクリーンショットを前回承認した基準画像と比べ、コード変更で生じた意図しない見た目の崩れを見つけるテストです。ボタンが押せるか、値が正しいかを確かめる機能テストでは、1pxの余白ずれやフォントの置き換わりは合格してしまいます。VRTはその穴を埋めます。
なお「VRT」は米Vertiv社の株式ティッカーや、労働政策研究・研修機構の職業レディネス・テスト(Vocational Readiness Test)の略称にも使われます。本記事はソフトウェアテストのVRTを扱います。職業レディネス・テストについてはレディネスの意味と分野別の使い分けで触れています。
まとめ:VRTの要点と導入判断
- VRTは「撮る→基準画像と比べる→差分を人が承認して基準を更新する」の繰り返しです。差分の良し悪しは機械では決まらず、承認する人が要ります。
- Playwrightを使っているチームなら、標準の
toHaveScreenshot()から始めると比較ツールを別途導入せずに済みます。比較・差分画像・HTMLレポートまで標準で揃い、ツールのライセンス料は無料です。CIの実行や画像・レポートの保存には、利用環境に応じた費用がかかります。 - 既定の閾値(threshold 0.2)は、検証したページでは1pxの余白ずれを検出した一方、近い色への変更は見逃しました。色を厳密に守りたい部品だけ閾値を下げます。
- 誤検知の原因の一つが実行環境の違いです。macOSで撮った基準画像をLinuxで比べると、見た目が同じでも約1,000pxの差分が出ました。基準画像はCIと同じDockerイメージで撮ります。
- Storybookがあるならコンポーネント単位で撮るChromatic、差分レポートを自前で共有したいならreg-suit、多ブラウザの描画をまとめて任せたいならPercyを検討します。
VRTの仕組みと機能テストで見逃す不具合
基準画像との比較の流れ
Playwrightで基準画像をGit管理する場合、運用は撮影・比較・承認の3段階に整理できます。まず変更前の画面を撮り、基準画像(ベースライン)としてリポジトリに置きます(reg-suitのように外部ストレージに置くツールもあります)。次にコードを変えたあと同じ条件で撮り直し、画素単位で比べます。差分があればテストは失敗し、期待画像・実画像・差分画像の3枚が出力されます。最後に人が差分を見て、意図した変更なら基準画像を更新し、意図しない崩れならコードを直します。
この「人が承認する」段階がVRTの運用コストの中心です。撮影対象を増やすほど、デザイン変更のたびに承認する画像も増えます。
機能テスト・回帰テストとの違い
回帰テストは、変更で既存の機能が壊れていないかを確かめるテスト全般を指します。VRTはその中で「見た目」だけを検証対象にした種類です。範囲の決め方や自動化の判断はリグレッションテスト(回帰テスト)の目的と範囲選定で整理しています。
| テスト・検証方式 | 代表的な検証例 | その検証だけでは見逃す例 |
|---|---|---|
| 単体テスト | 関数の戻り値 | CSSの変更は対象外 |
| E2Eテスト | 操作と表示テキスト | ボタンが重なっても押せれば合格 |
| スナップショットテスト | DOMやJSONの文字列 | 同じDOMでも描画が崩れる場合 |
| VRT | 描画結果の画素(余白・色・フォント・折り返し) | 閾値未満の色差、操作や値の誤り |
これらは排他的な分類ではなく、E2Eテストの途中にVRTの比較を組み込むこともできます。DOMを文字列で比べるスナップショットテストはVRTと名前が似ていますが、検出対象が違います。JestやVitestでの書き方はスナップショットテストの実装と更新運用を参照してください。
比較方式:ピクセル比較と知覚的比較
主流は画素ごとの色差を数えるピクセル比較で、Playwrightも内部でpixelmatchを使います。速く依存も少ない反面、アンチエイリアスやフォント描画のわずかな違いにも反応します。この雑音を減らすため、人の見え方に近い指標で比べる知覚的比較(SSIMやGMSDなど)や、AIで「意味のある差分」だけを残す商用ツール(後述のApplitools Eyesなど)もあります。Vitestは組み込み比較器をpixelmatchに限り、知覚的比較器は差し替えで足す設計にしています。
Playwrightの toHaveScreenshot によるVRTの導入手順
Playwright Testは比較・差分画像・レポートを標準で持つため、追加ツール無しでVRTを始められます。2026年10月12日時点のnpmの最新版は1.64.0(2026年10月7日リリース)です。インストールでつまずく場合はPlaywrightのインストール方法とできない時の対処を参照してください。
インストールと最初のテスト
以下は、テスト対象アプリをlocalhostの3000番ポートで起動できるプロジェクトを前提にした例です。初回実行前にアプリを起動するか、後述のwebServer設定を追加してください。URL・起動コマンド・マスク対象のセレクタは対象アプリに合わせます。
npm init playwright@latest
生成された tests/ にテストを書きます。旧版の記事で紹介していた toMatchSnapshot() は画像やテキストの比較用で、ページを撮るなら撮影と安定待ちまで面倒を見る toHaveScreenshot() を使います。
// tests/top.spec.js
const { test, expect } = require('@playwright/test');
test('トップページの見た目', async ({ page }) => {
await page.goto('http://localhost:3000/');
await expect(page).toHaveScreenshot('top.png', {
fullPage: true,
mask: [page.locator('#now')], // 時刻など毎回変わる要素は塗りつぶす
});
});
toHaveScreenshot() は描画が落ち着くまで撮り直し、連続2回同じ画像が撮れてから比較します。既定では CSSアニメーションを止め(animations: 'disabled')、テキストカーソルを隠します(caret: 'hide')。
初回実行でのベースライン生成と保存先
基準画像が無い状態で npx playwright test を実行すると、撮った画像を基準として書き出したうえでテストを失敗させます。1.64の既定モード default は「足りない基準画像は作るが、CIで黙って合格させない」挙動です。2回目の実行から比較が始まります。
基準画像はテストファイルの隣の <テストファイル名>-snapshots フォルダに保存されます。ファイル名には撮影したプロジェクト名(npm init playwright の既定設定では chromium などのブラウザ名)とOS名が付き、macOSなら top-chromium-darwin.png のようになります。projects を定義しない構成で試したところ、top-darwin.png でした。このフォルダはGitにコミットします。
差分が出たときの読み方と基準画像の更新
比較に失敗すると test-results/ に top-expected.png(基準)、top-actual.png(今回)、top-diff.png(差分を赤で示した画像)が出力され、npx playwright show-report のHTMLレポートで並べて確認できます。差分が意図した変更なら、基準画像を更新します。
# 一致しなかった画像と足りない画像だけを更新(値を省略すると changed)
npx playwright test --update-snapshots
# 全画像を撮り直す
npx playwright test --update-snapshots=all
更新した画像はコードの変更と同じプルリクエストに含め、レビュアーが差分画像を見て承認します。all は一致していた画像まで撮り直すため、環境の差で全画像が書き換わり、レビューで見るべき差分が埋もれます。普段は値なし(changed)で十分です。
既定設定で拾える差分・拾えない差分(実測)
既定の閾値がどの程度の変化を拾うのかは、公式ドキュメントに具体例がありません。Playwright 1.64.0(Chromium、ビューポート800×600)で同じページに小さな変更を加え、toHaveScreenshot() の結果を確かめました(2026年10月12日)。検証ページは、見出し・ボタン・回転する正方形・時刻表示だけを置いた1画面のHTMLを page.setContent() で読み込んだものです。差分の画素数はこのページと撮影環境で得た値で、別のページや環境で同じ値になるとは限りません。
| 変更内容 | 設定 | 結果 | 差分px |
|---|---|---|---|
| 見出しの余白を16px→17px | 既定 | 失敗 | 1,056 |
| 文字色を#222→#232323 | 既定(threshold 0.2) | 合格 | 0 |
| 文字色を#222→#232323 | threshold 0 | 失敗 | 1,219 |
| 表示時刻が毎回変わる | 既定 | 失敗 | 185 |
| 表示時刻が毎回変わる | mask で塗りつぶし | 合格 | 0 |
| 回転し続けるCSSアニメーション | 既定(disabled) | 合格 | 0 |
| 回転し続けるCSSアニメーション | animations: ‘allow’ | 5秒で打ち切り | – |
| macOSの基準画像をLinuxで比較 | 既定 | 失敗 | 1,015 |
読み取れることは3つあります。1つ目に、今回の1pxの余白ずれは1,056pxの差分として検出された一方、近い色への変更は通りました。thresholdは「同じ画素の色差をどこまで同じとみなすか」の値で、既定0.2は近い色を同色として扱います。位置ずれでも、ずれた部分と背景の色差が小さければ見逃すことがあります。ブランドカラーの取り違えを止めたい部品だけ、テスト単位で閾値を下げます。
2つ目に、アニメーションは既定で止まるので、animations: 'allow' にすると、撮影中に描画が変わり続ける場合は連続2回の画像が安定せずタイムアウトします。動きを含めて検証したいなら、VRTではなく操作のテストで見ます。
3つ目に、実行環境の差です。Linuxのコンテナ(mcr.microsoft.com/playwright:v1.64.0-noble)では基準画像のファイル名が top-linux.png になるため、macOSで作った top-darwin.png は参照されず「基準画像が無い」で失敗しました。macOSの画像を top-linux.png にコピーして比べると、同じページなのに1,015pxの差分が出ました。今回の1pxずれ(1,056px)と近い値で、差分の画素数だけでは本物の崩れと環境差を判別できません。
誤検知を減らす設定と運用
OS・フォント差の対策とDockerによる撮影環境の統一
Playwright公式も、描画はOS・ブラウザの版・設定・ハードウェア・ヘッドレスかどうか、さらには電源の状態でも変わるため、基準画像を作った環境と同じ環境で実行するようスナップショットの解説で注意しています。手元のmacやWindowsで基準画像を作らず、CIと同じ公式イメージの中で撮ります。
# 手元で、CIと同じイメージを使って基準画像を更新する
docker run --rm --ipc=host -v "$PWD":/work -w /work \
mcr.microsoft.com/playwright:v1.64.0-noble \
bash -c "npm ci && npx playwright test --update-snapshots"
イメージのタグは @playwright/test の版と一致させます。版を上げるとブラウザも入れ替わり描画が変わることがあるため、Playwrightの更新は基準画像の撮り直しと同じプルリクエストで行います。なお、この例は手元の node_modules をLinux用で上書きします。気になる場合はリポジトリを別ディレクトリに複製して実行してください。
動的な値と広告:mask と stylePath
時刻・乱数・ユーザー名・外部の広告や地図は、表示内容が変わると差分の原因になります。要素単位なら mask で塗りつぶし、まとめて隠すなら撮影時だけ読み込むCSSを stylePath に指定します。次のCSSを保存し、テスト側で toHaveScreenshot('top.png', { stylePath: './screenshot.css' }) のように渡します。
/* screenshot.css:撮影時だけ適用される */
.ad-slot, iframe[src*="maps"] { visibility: hidden; }
APIの応答が変わる画面は、page.route() で固定のJSONを返すようにしてから撮ると安定します。
閾値の決め方:threshold と maxDiffPixels
許容値は2つあります。threshold(0〜1、既定0.2)は画素ごとの色差の許容、maxDiffPixels は違ってよい画素の数で、既定では未設定(1pxでも違えば失敗)です。比率で指定する maxDiffPixelRatio もあります。全体に緩めると本物の崩れまで通るため、設定ファイルでは既定のままにしておき、雑音が出る画面だけテスト単位で緩めます。
// playwright.config.js
const { defineConfig } = require('@playwright/test');
module.exports = defineConfig({
reporter: [['html', { open: 'never' }]], // playwright-report/ に出力
use: { baseURL: 'http://localhost:3000', viewport: { width: 1280, height: 720 } },
webServer: {
command: 'npm run start', // 3000番で起動するアプリの例
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});
// 雑音が出る画面だけ、原因を問わず50画素までの差を許容する
await expect(page).toHaveScreenshot('dashboard.png', { maxDiffPixels: 50 });
webServer を書いておくと、テストの前にアプリを起動して応答を待ちます。Dockerの中やCIで実行するときも、コンテナ内でアプリが立ち上がるので、ホスト側で起動したサーバーに頼らずに済みます。
VRTツールの選び方
ツールの違いは「どこで撮るか」「差分を誰がどこで承認するか」「費用」の3点に集約されます。2026年10月12日時点の公開情報で整理しました。
| ツール | 撮る単位 | 承認の場所 | 費用 |
|---|---|---|---|
| Playwright 1.64 | ページ・要素 | PRの画像差分 | 無料(OSS) |
| Vitest 5 Browser Mode | コンポーネント | PRの画像差分 | 無料(OSS) |
| reg-suit 0.14 | 撮影は別ツール | S3等のHTMLレポート | 無料+保存先の費用 |
| BackstopJS 6.3 | ページ・セレクタ | ローカルのHTMLレポート | 無料(OSS) |
| Chromatic | ストーリー・E2Eのページ | Chromaticの画面 | 無料枠は月5,000スナップショット |
| Percy | ページ・ストーリー | Percyの画面 | 無料枠は月5,000スクリーンショット |
| Applitools Eyes | ページ・コンポーネント | Applitoolsの画面 | 有料(無料トライアルあり) |
Playwright・Vitestの標準機能で運用できる条件
ページ単位ならPlaywright、Reactなどの部品単位ならVitest 4で追加されたBrowser Modeの toMatchScreenshot() が候補です。どちらも基準画像をリポジトリに置き、PRの画像差分で承認します。Vitestの導入はVitest Browser Modeの使い方で解説しています。チームが小さく、差分の承認者がPRのレビュアーで足りるなら、ここで止めて構いません。
差分レポートをチームで共有するなら reg-suit
reg-suitは比較とレポートに特化したOSSで、撮影はPlaywrightやStorycapなど別のツールに任せます。プラグインで基準画像の置き場(S3またはGoogle Cloud Storage)とGitHubへの通知(コミットステータスとPRコメント)を足す構成で、基準画像をリポジトリに入れずに済み、デザイナーなどGitHubに慣れない人もブラウザでレポートを見られます。GitHubのリリースは2026年3月のv0.14.6が最新ですが、npmのlatestは2025年8月の0.14.5のままです。採用前に、npmの公開版とGitHubのリリース履歴、利用するプラグインの更新状況を確認します。
Storybook中心なら Chromatic、多ブラウザなら Percy
Storybookの開発元が提供するChromaticは、ストーリーごとに撮影し、TurboSnapでGitの変更から影響を受けるストーリーだけを撮り直します。料金表ではFreeが月5,000、Starterが月179ドルで月35,000の課金対象スナップショット(billed snapshots)です。Storybook自体の導入はStorybookの使い方とテストを参照してください。
BrowserStackのPercyは、DOMとアセットをアップロードしてサーバー側で複数ブラウザに描画し直すため、手元やCIの環境差の影響を受けにくい方式です。Percyの料金表では、無料プラン(Free)が月5,000スクリーンショット・ユーザー数無制限です。有料プランの枠と単価は撮影枚数に応じて変わるため、契約前に見積もりで確認してください。
環境差の雑音を比較方式で減らすなら Applitools Eyes
Applitools Eyesは、画素の閾値ではなく「マッチレベル」で比較の厳しさを選ぶ商用ツールです。公式ドキュメントでは、Strictは文字・フォント・色・画像・要素の位置といった人の目に見える違いを検出し、描画ソフトやハードウェアに由来する環境依存の画素値の違いは無視することを狙ったレベルと説明されています。Layoutは要素の相対位置だけを比べ、1つの基準画像を複数のOS・ブラウザ・画面サイズで使い回す場合に向くとされます。ほかに色の変化を除くIgnore Colors、日付・メールアドレス・URLなどの文字列パターンを認識して値の変化を差分にしないDynamicがあり、Webテストでは、JavaScriptのeyes-playwright 1.47.5以降など、公式に示された最低対応版以降のSDKでDynamicが既定です。
前章の「macOSの基準画像をLinuxで比べると約1,000pxの差分」のような環境差を、閾値の調整ではなく比較方式で吸収したいときの候補になります。料金表ではStarterが年払いで月667ドル(ページ単位なら1,000、コンポーネント単位なら100,000チェックポイント)で、無料トライアルがあります。
CIへの組み込みとNxモノレポでの運用
CIでは、基準画像を撮ったのと同じイメージの中で、対象アプリを起動してからテストを実行します。前掲の設定のように webServer に起動コマンドを書いておけば、npx playwright test だけでアプリの起動まで行われます。GitHub Actionsならジョブ自体をコンテナで動かし、失敗時に差分を確認できるようレポートを成果物として残します。
# .github/workflows/vrt.yml
name: VRT
on: pull_request
jobs:
vrt:
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.64.0-noble
options: --user 1001
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: lts/*
- run: npm ci
- run: npx playwright test
- uses: actions/upload-artifact@v5
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 14
Nxのモノレポでは、旧版の記事にあった @nrwl/react などの @nrwl スコープは19.8.4で更新が止まっており、現行は @nx/react や @nx/playwright(いずれも23.3.0)です。@nx/playwright でE2Eプロジェクトを作り、nx affected -t e2e で変更の影響を受けたアプリのVRTだけを走らせると、撮影枚数と承認の手間を抑えられます。CIの組み立てごと相談したい場合はDevOps・CI/CD導入支援も扱っています。
VRTを入れないほうがいい場面と撮る対象の絞り方
全ページを全ブラウザで撮る運用は失敗しやすい典型です。デザイン変更のたびに数百枚の承認が発生し、レビュアーが中身を見ずに更新を通すようになった時点で、VRTは検出力を失います。
- 入れないほうがいい場面:週単位でデザインが変わるキャンペーンLP、外部の広告や埋め込みが画面の大半を占めるページ、承認者を決められないチーム。
- 優先して撮る対象:デザインシステムの共通部品、申し込み・決済などの重要な画面、過去に表示崩れを出した画面。
- ブラウザ:まずChromium 1つに絞り、利用者の多いSafari系はWebKitを後から足す。
テスト全体の配分の中でVRTをどこに置くかはテストピラミッドと配分の考え方が参考になります。
よくある質問
VRTは何の略ですか?
ソフトウェア開発ではVisual Regression Testing(ビジュアルリグレッションテスト)の略です。株式ティッカーのVRT(Vertiv)や、職業レディネス・テスト(Vocational Readiness Test)とは別物です。
PlaywrightだけでVRTはできますか?
できます。toHaveScreenshot() が撮影・比較・差分画像の出力まで行い、HTMLレポートで差分を確認できます。基準画像の外部保存やデザイナー向けの承認画面が必要になった段階で、reg-suitやChromaticを足します。
ベースライン(基準画像)はどう更新しますか?
差分を確認して意図どおりなら npx playwright test --update-snapshots を、CIと同じDockerイメージの中で実行します。更新した画像はコード変更と同じプルリクエストに入れ、レビュアーが差分画像を見て承認します。
手元では合格するのにCIだけ失敗するのはなぜですか?
主な原因には、OS・フォント・ブラウザの違い、基準画像の不足、動的データや読み込みタイミングの差があります。今回、macOSで撮った基準画像をLinuxで比べると、同じページでも約1,000pxの差分が出ました。基準画像の作成とCIの実行を同じ公式イメージにそろえます。
VRTのツールは無料で使えますか?
Playwright、Vitest、reg-suit、BackstopJSはOSSで無料です(reg-suitは画像の保存先の費用が別にかかります)。Chromaticは月5,000スナップショット(課金換算)、Percyは月5,000スクリーンショットまでの無料枠があります。Applitools Eyesは有料で、無料トライアルがあります。