画像処理の自動テスト手法 - Visual Regression Testing の実践ガイド
Visual Regression Testing とは - 画像の変化を自動検出する仕組み
Visual Regression Testing (VRT) は、Web ページやコンポーネントのスクリーンショットを撮影し、以前のバージョンと比較することで意図しない視覚的変化を検出するテスト手法です。画像処理パイプラインにおいては、圧縮・リサイズ・フォーマット変換後の画像品質を自動的に検証する用途で活用されます。
VRT が解決する問題:
- CSS 変更による画像レイアウト崩れ:
object-fitやaspect-ratioの変更が画像表示に与える影響を検出 - 画像処理パイプラインの品質劣化: 圧縮設定の変更やライブラリアップデートによる品質低下を検出
- レスポンシブ画像の表示不具合: 特定のビューポートサイズで画像が正しく表示されない問題を検出
- ダークモード対応の不備: モード切り替え時に画像が正しく切り替わらない問題を検出
VRT の基本フロー:
- ベースラインとなるスクリーンショットを撮影・保存する
- コード変更後に同じ条件でスクリーンショットを撮影する
- ベースラインと新しいスクリーンショットをピクセル単位で比較する
- 差分が閾値を超えた場合にテストを失敗させる
- 意図的な変更の場合はベースラインを更新する
VRT は単体テストや結合テストでは検出できない視覚的な問題を捕捉できるため、画像を多用するサイトでは特に価値が高いテスト手法です。
Playwright による VRT の実装 - スクリーンショット比較の基本
Playwright は Microsoft が開発するブラウザ自動化ツールで、組み込みのスクリーンショット比較機能を持っています。追加のライブラリなしで VRT を実装できるため、最も手軽な選択肢です。
基本的なスクリーンショットテスト:
import { test, expect } from '@playwright/test';test('hero image renders correctly', async ({ page }) => { await page.goto('/products/123'); await page.waitForLoadState('networkidle'); const heroImage = page.locator('.hero-image'); await expect(heroImage).toHaveScreenshot('hero-image.png', { maxDiffPixelRatio: 0.01, });});
maxDiffPixelRatio: 0.01 は、全ピクセルの 1% まで差分を許容する設定です。画像圧縮のわずかな差異やアンチエイリアシングの違いを吸収するために、ある程度の許容値が必要です。
複数ビューポートでのテスト:
const viewports = [ { width: 375, height: 667, name: 'mobile' }, { width: 768, height: 1024, name: 'tablet' }, { width: 1440, height: 900, name: 'desktop' },];for (const vp of viewports) { test(`image gallery at ${vp.name}`, async ({ page }) => { await page.setViewportSize({ width: vp.width, height: vp.height }); await page.goto('/gallery'); await expect(page).toHaveScreenshot(`gallery-${vp.name}.png`); });}
ダークモードのテスト:
test('images in dark mode', async ({ page }) => { await page.emulateMedia({ colorScheme: 'dark' }); await page.goto('/blog/article-1'); await expect(page).toHaveScreenshot('article-dark.png');});
Playwright のスクリーンショット比較は、初回実行時にベースラインを自動生成し、__snapshots__ ディレクトリに保存します。ベースラインの更新は npx playwright test --update-snapshots で実行します。
画像処理パイプラインのテスト - 圧縮品質と出力検証
画像処理パイプライン (リサイズ、圧縮、フォーマット変換) の出力を自動テストする方法を紹介します。VRT とは異なり、ここでは画像ファイル自体のプロパティと品質を検証します。
出力画像のプロパティ検証:
import sharp from 'sharp';import { describe, it, expect } from 'vitest';describe('Image pipeline output', () => { it('generates correct dimensions', async () => { const metadata = await sharp('output/hero-800.webp').metadata(); expect(metadata.width).toBe(800); expect(metadata.format).toBe('webp'); }); it('file size is within budget', async () => { const stats = await fs.stat('output/hero-800.webp'); expect(stats.size).toBeLessThan(100 * 1024); // 100KB 以下 });});
SSIM による品質検証: 圧縮後の画像品質が閾値を下回っていないことを自動検証します。
import { ssim } from 'ssim.js';it('maintains quality above threshold', async () => { const original = await loadImageData('input/photo.jpg'); const compressed = await loadImageData('output/photo.webp'); const { mssim } = ssim(original, compressed); expect(mssim).toBeGreaterThan(0.95);});
バッチ処理の整合性テスト: 全入力画像に対して出力が生成されていること、ファイル名の命名規則が正しいこと、必要なフォーマット (AVIF, WebP, JPEG) が全て生成されていることを検証します。
it('generates all required formats', async () => { const inputs = await glob('src/images/*.{jpg,png}'); for (const input of inputs) { const base = path.basename(input, path.extname(input)); expect(fs.existsSync(`dist/${base}.avif`)).toBe(true); expect(fs.existsSync(`dist/${base}.webp`)).toBe(true); }});
CI/CD への統合 - GitHub Actions での自動実行
VRT と画像テストを CI/CD パイプラインに統合し、プルリクエストごとに自動実行する構成を紹介します。
GitHub Actions ワークフロー例:
name: Visual Regression Testson: [pull_request]jobs: vrt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 - run: npm ci - run: npx playwright install --with-deps - run: npm run build - run: npx playwright test - uses: actions/upload-artifact@v4 if: failure() with: name: vrt-diff path: test-results/
ベースラインの管理: スクリーンショットのベースライン画像は Git リポジトリにコミットします。LFS (Large File Storage) の使用を推奨しますが、画像数が少ない場合は通常の Git 管理でも問題ありません。ベースラインの更新は専用のコミットで行い、コードの変更と混ぜないようにします。
差分レポートの生成: テスト失敗時に差分画像 (元画像、新画像、差分ハイライト) をアーティファクトとしてアップロードし、プルリクエストのレビューで確認できるようにします。Playwright は自動的に差分画像を test-results ディレクトリに生成します。
並列実行による高速化: Playwright の --shard オプションで複数のワーカーに分散実行できます。画像の多いサイトでは、ページごとにシャードを分割して並列実行することで、テスト時間を大幅に短縮できます。
フレーキーテスト対策: VRT はフォントレンダリングやアニメーションのタイミングにより、同じコードでも結果が異なる場合があります。animations: 'disabled' オプションでアニメーションを無効化し、Web フォントの読み込み完了を待ってからスクリーンショットを撮影してください。
Percy と reg-suit - クラウドベースの VRT サービス
自前で VRT インフラを構築する代わりに、クラウドベースの VRT サービスを利用する選択肢もあります。ベースライン管理、差分レビュー UI、ブラウザ間比較などの機能が提供されます。
Percy (BrowserStack): BrowserStack が提供するクラウド VRT サービスです。スクリーンショットをクラウドにアップロードし、ブラウザ上で差分をレビューできます。
import percySnapshot from '@percy/playwright';test('product page', async ({ page }) => { await page.goto('/products/123'); await percySnapshot(page, 'Product Page');});
Percy の利点は、複数ブラウザ (Chrome, Firefox, Safari) でのレンダリング差異を一括で検出できる点と、チームでの差分承認ワークフローが組み込まれている点です。無料プランでは月 5,000 スクリーンショットまで利用可能です。
reg-suit: オープンソースの VRT ツールで、スクリーンショットの差分を S3 や GCS にアップロードし、GitHub のプルリクエストにレポートをコメントします。クラウドサービスに依存せず、自前のインフラで運用できる点が特徴です。
npx reg-suit run
reg-suit は reg-keygen-git-hash-plugin でベースラインのコミットハッシュを自動決定し、reg-publish-s3-plugin で差分レポートを S3 にアップロードします。GitHub Actions との統合も容易で、プルリクエストに差分サマリーを自動コメントできます。
選定基準: チーム規模が小さく、テスト対象が限定的な場合は Playwright の組み込み機能で十分です。複数ブラウザでの検証やチームでの承認フローが必要な場合は Percy、コストを抑えつつ柔軟な運用をしたい場合は reg-suit が適しています。
画像パフォーマンステストの自動化 - Lighthouse CI との統合
画像の最適化状態をパフォーマンスの観点から自動テストする方法を紹介します。Lighthouse CI を使えば、画像関連のパフォーマンス指標を継続的に監視できます。
Lighthouse CI の設定:
// lighthouserc.jsmodule.exports = { ci: { collect: { url: ['http://localhost:3000/', 'http://localhost:3000/gallery'] }, assert: { assertions: { 'uses-webp-images': ['error', { minScore: 1 }], 'uses-responsive-images': ['warn', { minScore: 0.9 }], 'offscreen-images': ['warn', { minScore: 0.9 }], 'unsized-images': ['error', { minScore: 1 }], } } }};
画像固有のアサーション:
uses-webp-images: 次世代フォーマット (WebP/AVIF) が使用されているかuses-responsive-images: 表示サイズに対して過大な画像が配信されていないかoffscreen-images: ビューポート外の画像が遅延読み込みされているかunsized-images: 画像に width/height が指定されているか (CLS 防止)
カスタムメトリクスの追加: Lighthouse のカスタム監査を作成し、画像の総転送量が予算 (例: 500KB) を超えていないか、画像リクエスト数が上限 (例: 30 リクエスト) を超えていないかを検証できます。
パフォーマンスバジェットの設定:
{ "resourceSizes": [{ "resourceType": "image", "budget": 500 }], "resourceCounts": [{ "resourceType": "image", "budget": 30 }] }
これらのテストを CI/CD に統合することで、画像の最適化が退行していないことを継続的に保証できます。新しい画像を追加する際に、最適化されていない画像がデプロイされることを防ぐ安全網として機能します。