アプリをリリースするたびに、公開後にユーザーから「ボタンが切れてる」「ダークモードで文字が消える」と報告が届く——そういう経験を何度か重ねてきました。
わたしが個人で運営している壁紙・癒し系アプリは、2014年頃から少しずつ積み上げて、今では累計5,000万ダウンロードを超えるくらいのサイズになっています。機能はシンプルですが、iPhone の機種ごとに解像度が微妙に異なるため、スクリーンショットの見た目チェックがリリース工程の中で思いのほか時間を取るようになりました。
最初は Xcode の Simulator を手動で回して目視確認していたのですが、機種が増えるにつれて現実的でなくなってきた。かといって、スナップショットテストのライブラリを導入するほどの規模でもありません。
そのちょうど中間を埋めてくれたのが、Gemini 3.2 Flash のマルチモーダル機能です。スクリーンショットの画像を渡して「何か問題があるか判断して」と聞くと、驚くほど的確に答えが返ってきます。
何をチェックしたかったのか
わたしが自動化したかったのは、以下の3パターンです。
- 文字の切れ・はみ出し: フォントサイズがデバイスの設定で大きくなっていたとき、ラベルがボタンの外にはみ出していないか
- ダークモードの視認性: ライトモードで白い背景に白いテキストにはなっていないか(これを本番でやらかしたことがある)
- レイアウト崩れ: セーフエリアを無視して要素が重なっていないか
これらを人間が目で確認しようとすると、機種 × ライト/ダーク × ロケールの組み合わせで数十枚のスクリーンショットを見ることになります。Gemini にまとめて渡して「おかしいところはあるか」と聞く方が、確実に早い。
基本的な実装
最初に作ったのは、スクリーンショット1枚を渡してレビューを返すシンプルなスクリプトです。
import google.generativeai as genai
from pathlib import Path
genai.configure(api_key="YOUR_GEMINI_API_KEY")
model = genai.GenerativeModel("gemini-3.2-flash")
def review_screenshot(image_path: str, context: str = "") -> dict:
"""
スクリーンショットのUIを自動レビューする。
context に画面の説明(例: 設定画面、ダークモード)を渡すと精度が上がる。
"""
image_data = Path(image_path).read_bytes()
prompt = f"""
以下はモバイルアプリのスクリーンショットです。
{f"画面の説明: {context}" if context else ""}
UIの品質を以下の観点でチェックしてください:
1. テキストの切れ・はみ出しがないか
2. 要素の重なり・レイアウト崩れがないか
3. テキストと背景のコントラストが十分か(特にダークモード)
4. ボタンやタップ領域が適切なサイズか
問題があれば箇条書きで、なければ「問題なし」と返してください。
出力はJSON形式: {{"status": "ok" or "issues_found", "issues": ["..."]}}
"""
response = model.generate_content([
prompt,
{"mime_type": "image/png", "data": image_data}
])
# レスポンスからJSONを抽出
import json, re
json_match = re.search(r'\{.*\}', response.text, re.DOTALL)
if json_match:
return json.loads(json_match.group())
return {"status": "parse_error", "issues": [response.text]}
# 使い方
result = review_screenshot(
"screenshots/settings_dark_iphone15pro.png",
context="設定画面、ダークモード、iPhone 15 Pro"
)
print(result)
# {"status": "issues_found", "issues": ["第3行のラベルテキストが枠外にはみ出しています"]}これだけで動きます。gemini-3.2-flash は gemini-2.0-flash や gemini-2.5-flash より視覚的な説明の精度が上がっていて、具体的にどの要素が問題かを指摘してくれます。
複数スクリーンショットをまとめてチェックする
単体のチェックが動いたら、次はリリース前のスクリーンショット一式をまとめて処理するスクリプトを作りました。
import google.generativeai as genai
from pathlib import Path
import json, re, time
from typing import NamedTuple
genai.configure(api_key="YOUR_GEMINI_API_KEY")
model = genai.GenerativeModel("gemini-3.2-flash")
class ScreenshotCheckResult(NamedTuple):
file: str
status: str
issues: list[str]
def batch_review(screenshot_dir: str, config_json: str = None) -> list[ScreenshotCheckResult]:
"""
ディレクトリ内のスクリーンショットをまとめてレビュー。
config_json: {"ファイル名": "画面の説明"} の形式で各ファイルに説明を付与できる
"""
configs = {}
if config_json:
with open(config_json) as f:
configs = json.load(f)
screenshots = sorted(Path(screenshot_dir).glob("*.png"))
results = []
for i, path in enumerate(screenshots):
context = configs.get(path.name, "")
print(f"[{i+1}/{len(screenshots)}] {path.name} をチェック中...")
image_data = path.read_bytes()
prompt = f"""
モバイルアプリのスクリーンショットのUIレビューを行ってください。
{f"画面の説明: {context}" if context else ""}
チェック項目:
- テキストの切れ・はみ出し
- 要素の重なり・レイアウト崩れ
- テキストと背景のコントラスト不足
- ボタン/タップ領域のサイズ問題
JSON形式で返してください: {{"status": "ok" or "issues_found", "issues": ["..."]}}
"""
try:
response = model.generate_content([
prompt,
{"mime_type": "image/png", "data": image_data}
])
json_match = re.search(r'\{.*\}', response.text, re.DOTALL)
if json_match:
data = json.loads(json_match.group())
results.append(ScreenshotCheckResult(
file=path.name,
status=data.get("status", "unknown"),
issues=data.get("issues", [])
))
except Exception as e:
results.append(ScreenshotCheckResult(
file=path.name,
status="error",
issues=[str(e)]
))
# レート制限対策: 1秒待機
if i < len(screenshots) - 1:
time.sleep(1)
return results
def print_report(results: list[ScreenshotCheckResult]):
"""チェック結果をサマリーレポートとして出力"""
ok_count = sum(1 for r in results if r.status == "ok")
issue_count = sum(1 for r in results if r.status == "issues_found")
print(f"\n{'='*50}")
print(f"スクリーンショットQAレポート")
print(f"{'='*50}")
print(f"✅ 問題なし: {ok_count}件")
print(f"❌ 問題あり: {issue_count}件")
for r in results:
if r.status == "issues_found":
print(f"\n📱 {r.file}")
for issue in r.issues:
print(f" → {issue}")
# 実行
results = batch_review("./release_screenshots/", "./screenshot_configs.json")
print_report(results)screenshot_configs.json の中身はこういう形式です:
{
"home_light_iphone15.png": "ホーム画面、ライトモード、iPhone 15",
"home_dark_iphone15pro.png": "ホーム画面、ダークモード、iPhone 15 Pro",
"settings_light.png": "設定画面、ライトモード",
"settings_dark.png": "設定画面、ダークモード"
}実際に使ってみて気づいたこと
3ヶ月ほど実際のリリースフローに組み込んで、いくつかのことが見えてきました。
精度は意外と高い: 「ラベルが2px はみ出している」のような微妙なケースは見逃すこともありますが、「ダークモードで白背景に白テキスト」のような致命的な問題は高確率で拾ってくれます。わたしの場合、本番事故の9割はこのレベルの見落としだったので、実用的には十分です。
コンテキストの説明が肝: 画面の説明を入れると精度が上がります。特に「ダークモード」「ライトモード」の情報は必ず入れてください。入れないと Gemini がライトモードとして解釈して「テキストのコントラストは問題なし」と誤判定するケースがありました。
Gemini 3.2 Flash を選ぶ理由: 2.5 Flash でも動きますが、3.2 Flash の方が「どの要素が、どのように問題か」の説明が具体的でした。スクリーンショット内の座標を含む回答が返ってきたこともあります。コストも Flash なので1回あたり数円程度で収まります。
1回のリクエストで複数画像を渡す場合の注意: Gemini API は1回のリクエストに複数の画像を渡せますが、「どの画像の話をしているか」が混在して精度が下がります。1画像1リクエストにした方が確実です。
CI/CD への組み込み
GitHub Actions への組み込みは、こういうステップで動かしています:
# .github/workflows/ui-qa.yml
name: UI Screenshot QA
on:
workflow_dispatch:
push:
branches: [release/*]
jobs:
screenshot-qa:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Build and capture screenshots
run: |
xcodebuild test \
-scheme "MyApp" \
-destination "platform=iOS Simulator,name=iPhone 15" \
-testPlan "ScreenshotCapture"
- name: Run Gemini Vision QA
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
run: |
pip install google-generativeai --quiet
python scripts/qa_screenshots.py ./screenshots/ --config screenshot_configs.json
- name: Upload QA report
uses: actions/upload-artifact@v4
with:
name: qa-report
path: qa_report.txtpush で自動実行すると、問題があったときだけ Artifact にレポートが上がります。
限界と向き合う
正直に書くと、このアプローチが向かないケースもあります。
アニメーションやインタラクションの問題は静止画では判断できません。スクロール途中の状態や、タップ後の遷移に問題がある場合は、XCTest や Espresso の方が確実です。また、アクセシビリティの VoiceOver 対応は、スクリーンショットでは確認できません。
「スクリーンショットを目で見て確認する工程」の一部を自動化する——そういう位置づけで使うのが現実的です。全てのUIテストを置き換えるものではありません。
次のステップ
このスクリプトを動かせる状態になったら、screenshot_configs.json に検証したい画面を追加していくところから始めてみてください。最初は5〜10枚程度から試すと、Gemini が何を「問題あり」と判断するクセが掴めてきます。
わたし自身、まだプロンプトを改善している途中ですが、個人開発の規模感では十分に実用的なツールになっています。
実装の参考になれば幸いです。
関連記事: