単体テストは通ったのに、なぜUIが壊れたのか?ビジュアルリグレッションテストのすすめ
「グリーンビルド」の幻想
金曜日の午後4時55分です。チェックアウトコンポーネントのリファクタリングを終えたところです。npm test を実行します。
PASS components/Checkout.test.tsx
✓ renders checkout button (23ms)
✓ handles submit action (15ms)
✓ displays success message (12ms)ターミナルは心を落ち着かせる緑色のチェックマークの海です。本番環境にプッシュし、チームとハイタッチをして、帰宅します。
午後6時に電話が振動します。「モバイルでチェックアウトボタンが見えないぞ。」
パニックになります。テストを確認します。まだパスしています。なぜ? 論理的には、ボタンはDOMに存在します。正しいクリックハンドラーを持っています。正しいテキストを持っています。
しかし視覚的には? 不正なCSS z-index: -1 またはオーバーフローの問題により、フッターの後ろに隠れてしまいました。Jestには目がありません。
なぜコードベースのテストだけでは不十分なのか
Jest、React Testing Library、あるいは標準的なE2Eテスト(Cypress/Playwright)のような従来のテストツールは、多くの場合、要素の外観ではなく、存在と機能を断言します。
それらは以下をあなたに伝えることができます:
- ✅
<h1>が1つだけ存在する。 - ✅ APIが200 OKを返す。
- ✅ 変数
isOpenが true である。
それらは以下をあなたに伝えることが できません:
- ❌ CSSグリッドが崩壊した。
- ❌ テキストの色が背景色と一致している(見えないテキスト)。
- ❌ iPhone SEでレイアウトが壊れている。
このギャップこそが、ビジュアルリグレッションテスト が介入する場所です。
ビジュアルリグレッションテストの登場
ビジュアルテストは、実際にレンダリングされたUIのスクリーンショットを撮り、「ベースライン」(期待される正しいバージョン)とピクセル単位で比較します。ボタンが5px下にずれるといったわずかな変更でもあれば、アラートを出します。
仕組み
- キャプチャ: ヘッドレスブラウザがサイトにアクセスし、スクリーンショットを撮ります。
- 比較: ツールは新しいスクリーンショットを古いものの上に重ねます。
- 差分: 違いを明るい赤色で強調表示します(しばしば「差分(Diff)」と呼ばれます)。
expect(button).toHaveStyle('margin-top: 10px') のようなテストを書く代わりに、単に画像を見ます。間違っているように見えれば、それは 間違っています。
「偽陽性」ノイズの解決
ビジュアルテストに関する最大の不満は ノイズ です。 「時間が10:00から10:01に変わったから、テストが失敗した!」
スマートなビジュアルテストツール(SiteSnapshotのような)は、以下によってこれを解決します:
- 動的領域のマスキング: 日付、広告、ランダムなIDを自動的に無視します。
- しきい値設定: 大きなレイアウト崩れを捉えつつ、わずかなアンチエイリアスのずれに対して1%のピクセル差を許容します。
結論:より良い睡眠を
単体テストなしでコードを出荷することはないでしょう。なぜビジュアルテストなしでUIを出荷するのですか?
パイプラインにビジュアルリグレッションチェックを追加することで、ユーザーが 見る ものが、まさにあなたが意図したものであることを保証します。もう「z-index」の驚きはありません。
Is your site visually healthy?
Don't guess. Run a deeper visual scan right now and catch hidden bugs before your users do.