🧪 自動化テストのリアル:スクリーンショット比較でUIテストに挑む
1. はじめに:UIテスト、本当に機能していますか?
WebアプリケーションのUIは、頻繁な変更やデザインリファクタリングの影響を受けやすい領域です。たとえユニットテストやE2Eテストを通過しても、「ボタンの色が変わっていた」「アイコンが消えていた」など、目に見えるUIの不具合がユーザーから報告されることがあります。
こうした“目視確認が必要な”部分をテストでカバーするにはどうすればよいのでしょうか?
本記事では、**スクリーンショット比較(Visual Regression Testing)**というアプローチに焦点を当てて、実践的な導入手順から、現場で得た知見・落とし穴までを、エンジニア目線で深掘りします。
2. スクリーンショット比較とは?〜技術概要と主要ツール〜
📷 Visual Regression Testing(VRT)とは?
VRTとは、ある時点でのUIのスクリーンショットを「ゴールデン画像(基準)」として保存し、その後のスクリーンショットと比較することで意図しないUIの変化を検出するテスト手法です。
- ✅ 意図しないスタイル崩れ、CSSの副作用を検出できる
- ✅ 「コード変更なしでもUI崩れが起きる」ような事象に有効
- ✅ デザインチームとの連携もスムーズに
🛠️ 主なツールと選定理由
ツール名 | 特徴 | 備考 |
---|---|---|
Playwright + Pixelmatch | 高速・柔軟、Node.js対応 | 本記事の主役 |
Puppeteer + Resemble.js | 単体利用でも可能 | 画像比較処理に難あり |
Cypress + Percy | クラウド連携が強力 | 有料プランあり |
Storybook + Chromatic | コンポーネント単位で便利 | デザインシステム向き |
3. 実装編:Playwright × Pixelmatchでスクリーンショット比較
ここでは、PlaywrightとPixelmatchを使ったVRTの最小構成を紹介します。
📁 プロジェクト構成
project-root/
├── tests/
│ ├── visual.spec.ts
│ └── snapshots/
│ ├── current/
│ └── baseline/
├── scripts/
│ └── compare-screenshots.ts
├── package.json
└── playwright.config.ts
🔧 セットアップ
npm install --save-dev playwright pixelmatch pngjs
npx playwright install
✍️ テストコード:UIを撮影する
// tests/visual.spec.ts
import { test, expect } from '@playwright/test';
import fs from 'fs';
import path from 'path';
test('UI snapshot test', async ({ page }) => {
await page.goto('https://example.com');
const screenshot = await page.screenshot();
const filename = 'homepage.png';
fs.writeFileSync(`tests/snapshots/current/${filename}`, screenshot);
expect(fs.existsSync(`tests/snapshots/baseline/${filename}`)).toBe(true);
});
🧠 スクリーンショット比較ロジック
// scripts/compare-screenshots.ts
import fs from 'fs';
import path from 'path';
import { PNG } from 'pngjs';
import pixelmatch from 'pixelmatch';
const baseline = PNG.sync.read(fs.readFileSync('tests/snapshots/baseline/homepage.png'));
const current = PNG.sync.read(fs.readFileSync('tests/snapshots/current/homepage.png'));
const { width, height } = baseline;
const diff = new PNG({ width, height });
const mismatches = pixelmatch(
baseline.data,
current.data,
diff.data,
width,
height,
{ threshold: 0.1 }
);
fs.writeFileSync('tests/snapshots/diff/homepage-diff.png', PNG.sync.write(diff));
console.log(`📸 差分ピクセル数: ${mismatches}`);
これで、差分が検出された場合には diff
フォルダに画像が出力され、視覚的な確認ができます。
4. 実務Tips:導入・運用のコツとよくある落とし穴
✅ 実務的なポイント
- CI/CD統合がカギ:GitHub Actionsなどと連携して、PRごとにスクリーンショットを自動生成・比較する仕組みを構築すると効果的
-
diff閾値の調整:小さなアンチエイリアスの差を検知しすぎないように
threshold
を適切に調整 - マスク処理の導入:時計や日付など「毎回変わる部分」を除外する仕組みを検討
⚠️ よくある罠
症状 | 原因 | 対策 |
---|---|---|
差分が出すぎる | フォント・環境差異 | CI上でも同一ブラウザ・解像度を使う |
テストが壊れやすい | 画像ファイルの管理ミス | スナップショットをGit管理 or S3などに保存 |
無駄な検出が多い | コンテンツの変動 | スクロールやホバーアニメーションを無効化 |
5. 応用編:Storybook × VRTでコンポーネント単位テストも可能
チーム開発では、Atomic Designやコンポーネント駆動開発(CDD)の流れで、UIの単位をより小さく保ちたいケースも多くなってきました。
そのような場合は、以下のような構成でStorybook + Playwrightを組み合わせて、UIコンポーネントごとにVRTを実施することも可能です。
-
storybook-static/
を自動ビルド - 各コンポーネントStoryのURLに対して
page.goto()
を実施 - CI/CDで自動実行し、PRに差分をコメントで通知
6. まとめ:VRTは"目視確認の自動化"という力強い武器
✅ メリット
- UIのビジュアル品質を維持できる
- 手動確認の時間を大幅削減
- バグの早期発見、デザインとエンジニア間の齟齬の解消
❌ デメリット
- スナップショット管理の手間
- テストの安定化に工夫が必要
- すべてをVRTに頼るのはNG(他のテストと併用が前提)
🚀 今後に向けて:VRTはまだ進化中
近年では、AIベースの画像比較やクラウド連携による差分レビュー支援といった高度な機能も登場しています。VRTはまだまだ進化中であり、「自動テストの最終フロンティア」として注目される分野です。
ぜひ一度、自分のプロジェクトにVRTを導入してみてください。「UIが壊れてる!」というユーザークレームが減る日も、そう遠くありません。
📚 参考リンク
ご質問やコメントがあれば、ぜひお気軽にどうぞ!
この記事が少しでもあなたの開発の一助になれば嬉しいです 🙌