訂正(2026-10-11): Playwrightのクリック判定、VRTと単画像レビューの違い、構造化出力、外部APIへの画像送信を修正しました。以下は設計案です。LLM連携を含む本番運用や費用削減を実証した記事ではありません。
PlaywrightのクリックはDOMの存在だけを見ていない
旧版は、ボタンの上にバナーが被っていても次のテストがPASSすると説明していました。通常のlocator.click()については誤りです。
await expect(page.locator('#submit-button')).toBeVisible();
await page.locator('#submit-button').click();
toBeVisible()だけでは、他の要素がクリック位置を覆うか確認できません。一方、通常のclick()はVisible、Stable、Receives Events、Enabled等を確認し、遮る要素がイベントを受ける状態が続けばタイムアウトします。force: trueを使うと一部のチェックを省くため、これを通常のユーザー操作の証明に使わないようにします。
ただし、クリックが成功しても文言の読みやすさ、余白、画像の欠け、配色まで保証するわけではありません。操作テストと視覚確認を組み合わせます。
三つの確認に責任を分ける
| 確認 | 主に調べること | それだけでは分からないこと |
|---|---|---|
| 操作・状態のE2E | クリック、遷移、入力、操作後の状態 | 意図した見た目か |
| 基準画像とのVRT | 前回承認した画像との差 | 変更が妥当か、機能が正しいか |
| Vision LLMのレビュー | 画像中の重なり・欠け等の指摘候補 | 実際のクリック可否、見落としがないこと |
一枚の画像だけを渡して「破綻しているか」と聞く処理は、一般的な視覚レビューです。回帰を調べるなら、承認済み基準と今回の状態を比較します。LLMを使う場合も、基準画像・期待する状態・変更目的を添え、差分を指摘候補として扱います。
透明なオーバーレイ、JavaScriptのイベント処理、画面外の内容などはスクリーンショットだけでは判定できません。AIのPASSを操作可能性の保証にしません。
基準画像の比較を先に作る
Playwright TestにはtoHaveScreenshot()があります。ブラウザ、OS、フォント、viewport、テストデータ等を揃え、アニメーションや変動データを管理して基準画像を作ります。意図した変更で基準を更新する際は、人が差分を確認します。
次はテスト対象アプリ側が用意するdata-testidを前提とした例です。固定URLやセレクタを実際のアプリに合わせます。新旧の画面状態が異なる画像は比較しません。
import { test, expect } from '@playwright/test';
test.use({ viewport: { width: 375, height: 812 } });
test('申込パネルの操作と表示', async ({ page }) => {
await page.goto('https://example.com/lp');
await page.getByTestId('open-application').click();
await expect(page.getByTestId('application-panel')).toBeVisible();
await page.evaluate(() => document.fonts.ready);
await expect(page).toHaveScreenshot('application-panel.png', {
animations: 'disabled',
});
});
フォント待機だけでは画像や非同期データの準備完了を保証しません。アプリの準備完了を表す条件を別に待ちます。重要な画像の読み込みも確認します。上の例は掲載用で、実アプリ・基準画像を使った実行は未検証です。
AI出力は未信頼の入力として検証する
プロンプトに「JSONのみ」と書くことと、APIの構造化出力設定は別です。モデルが対応するJSON Schema等をAPI設定に渡し、返却値を実行時にも検証します。構造が正しいJSONでも、画像についての判断が正しいとは限りません。
Geminiを使う場合、公式が推奨するJavaScript SDKは@google/genaiです。旧版の@google/generative-aiを前提にした例は取り下げました。利用可能な正式モデルID、対応する構造化出力設定、画像制限は、実装時に選ぶバージョンの公式資料で確認します。
LLM呼び出しと切り離した、判定値を受け取る関数の例です。passは「問題がないとAIが報告した」という意味に限定します。
function classifyVisualReview(value) {
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
return { state: 'review_required', reason: 'invalid_response' };
}
const { verdict, reason } = value;
if (!['pass', 'fail', 'uncertain'].includes(verdict) ||
typeof reason !== 'string' || !reason.trim() || reason.length > 2000) {
return { state: 'review_required', reason: 'invalid_response' };
}
if (verdict === 'fail') return { state: 'issue_candidate', reason };
if (verdict === 'uncertain') return { state: 'review_required', reason };
return { state: 'no_issue_reported', reason };
}
ローカルでは、pass/fail/uncertain、不正な列挙値、空の理由、null、配列の7ケースを確認しました。これで検証できるのは分岐と入力の扱いです。モデルの視覚精度、SDK接続、実ブラウザでの表示は未検証です。
通信エラー、タイムアウト、拒否、JSON解析失敗はこの関数の前段でreview_requiredとして記録します。失敗をPASSへ置き換えず、上限付きで再試行するか人へ渡します。理由の文字列はHTMLとして実行せず、画像中の指示もシステムの権限変更として扱いません。
リリース判定と費用を独立させる
採用案は、操作テストと画像差分を既存の品質ゲートに残し、AIは差分の説明や確認候補の整理を補助する形です。画像差分をAIがPASSしたことだけを理由に無視しません。異常候補と不確実な判定を人が確認し、見落とし・誤検知を評価用データへ残します。
「従来のテストの弱点を完全に克服する」「プロンプトに注力すればROIが高い」という断言は取り下げます。導入前には既知の表示不具合、正常画面、意図した変更を含むデータで、検出率・誤検知・判断の揺れ・レビュー時間を比較します。
API費用はモデル、画像サイズ・トークン数、呼び出し数、再試行で変わります。本稿には実測がないため「一回数円」と固定しません。使用量と待ち時間を記録し、上限超過時は処理を停止して確認待ちにします。
ローカル実行と外部送信を混同しない
Mac miniでPlaywrightを実行しても、Geminiの外部APIへ画像を送れば画像はローカル環境の外へ出ます。「ネットワーク隔離されたサーバーだから画像が外へ出ない」という説明にはなりません。
公開情報・架空データのテスト画面から始めます。機密画面は、画像、URL、ユーザー名、トークン、ブラウザ状態等の送信可否を確認し、許可されない場合は送信しません。マスキングしたつもりでも機密が残る可能性があります。外部通信なしが条件なら、その条件で動くモデルと実行構成を別に検証します。
本稿の設計を、実際に隔離サンドボックスや深夜バッチで運用しているという実績には扱いません。AIレビューの導入判断には、精度、扱う情報、費用、人が確認する範囲の証拠が必要です。
この記事を書いた人✏️@YushiYamamoto
ITPRODX.com代表 / AIアーキテクト
Next.js / TypeScript / Playwrightを活用した自律型テスト・アーキテクチャ設計を専門としています。
日々の自動化の検証結果や、ビジネス側の視点(ROI等)に関するより深い考察は、以下の公式サイトおよびnoteで発信しています。