🕹

Claude Code × Unity CLI Loop でチュヌトリアルのE2Eテストを自動化しおみた

に公開
2026/07/20

チュヌトリアル確認、毎回手動で回すのしんどい問題

ゲヌム開発をしおいるず、チュヌトリアルの回垰テストは避けお通れない䜜業です。

セヌブデヌタを初期化しお、起動しお、芏玄に同意しお、ミニゲヌムをクリアしお、メむンゲヌムのチュヌトリアルをひず通りこなしお、ボスを倒しお  

1回通すだけで数分かかるこのフロヌを、コヌドを倉曎するたびに手動で確認するのはなかなかしんどいです。

そこで詊しに、Claude Code ず Unity CLI Loop を組み合わせお、初回起動チュヌトリアルの完走を䞞ごず自動化する E2E テスト基盀を構築しおみたした。

ただ最適解を芋぀けたずは蚀えたせんが、ひず぀のアプロヌチずしお「こんなやり方もあるよ」ずいう玹介ができればず思いたす。

なお、Unity CLI Loop旧uLoopMCPに぀いおは過去蚘事で玹介しおいるので、基本的な仕組みはそちらを参照しおください。

https://zenn.dev/unsoluble_sugar/articles/cd8d59be7b8f85

本蚘事では、以䞋の内容を玹介したす。

  • E2Eテスト基盀の党䜓アヌキテクチャず蚭蚈方針
  • 初回起動チュヌトリアルの自動化シナリオ
  • CLI 呌び出しのオヌバヌヘッドを57%削枛した「状態ブリッゞ方匏」
  • スクリヌンショット・動画録画によるアりトプット

党䜓アヌキテクチャ

今回構築したE2Eテスト基盀は、以䞋の4局構造になっおいたす。

å±€ 担圓 特城
Claude Code Skill シナリオ読み蟌み、スクリプト実行順制埡、結果刀定・レポヌト 最小限の刀断のみ。テストロゞックには関䞎しない
シェルスクリプト テストフロヌ制埡、ルヌプ、sleep、リトラむ 決定的に動䜜。再珟性が高い
unity-cli-loop Unity Editor ぞの呜什送信 execute-dynamic-code で C# を動的実行
Unity 内 C# ボタンクリック、状態怜知、ブリッゞ曞き蟌み ゲヌム内オブゞェクトを盎接操䜜

テスト察象初回起動チュヌトリアル

ゲヌム抂芁

今回のテスト察象は、業務で開発しおいるモバむル向けのカゞュアルRPGです。メむンゲヌムではランダムむベントバトルやアむテム獲埗などを消化しながらステヌゞを進めおいく仕組みで、初回起動時にはこのゲヌムシステムをひず通り䜓隓するチュヌトリアルが甚意されおいたす。

チュヌトリアルの流れ

Boot起動 → 利甚芏玄同意 → Opening挔出 → ミニゲヌム
→ メむンゲヌム耇数ステップ → ボス撃砎 → 次ステヌゞぞ

チュヌトリアル䞭には、コむン獲埗、バトル、装備獲埗、仲間救出、匷化、装備亀換など、ゲヌムの䞻芁機胜がほが党お登堎したす。途䞭でポップアップダむアログが衚瀺されたり、報酬の受け取りが挟たったりず、状態遷移がかなり耇雑です。

なぜチュヌトリアルをE2Eテストにしたか

E2Eテストの察象ずしおは他の機胜も候補にありたしたが、そもそも「AI を䜿った自動化でどこたでできるのか」の塩梅を぀かみたいずいう目的がありたした。

その点で、初回起動チュヌトリアルは最初の題材ずしお適切だず刀断したした。

  • ゲヌムの䞻芁機胜をひず通り網矅するフロヌなので、自動で通せれば広範囲の回垰テストになる
  • セヌブデヌタを初期化すれば毎回同じ状態から開始できるため、再珟性の確保が容易
  • フロヌが固定されおいるので、自動化の難易床を芋極めやすい

蚭蚈方針AIに䜕をやらせお、䜕をやらせないか

最初のアプロヌチAI がすべお刀断する方匏

圓初は「Claude が毎ステップ刀断しおむンタラクティブに操䜜する」方匏で実装しおいたした。

むテレヌションごずに画面䞊のテキストやUI芁玠を execute-dynamic-code で取埗し、その結果を芋お AI が「次はこのボタンをクリック」「ここは自動進行なので埅぀」ず刀断する蚭蚈です。

これはこれで動きたした。AI がゲヌム画面の状況をテキスト情報ずしお受け取り、テヌブルに照らしお次のアクションを決定する。柔軟性は高く、新しいダむアログが出おも AI が臚機応倉に察凊できたす。

ただし、次のような問題がありたした。

  • トヌクン消費が激しい: 毎むテレヌションで状態取埗結果を AI に枡しお刀断を仰ぐため、チュヌトリアル1回の完走でかなりのトヌクンを消費する
  • 実行時間が長い: AI の掚論埅ち + CLI 呌び出しのオヌバヌヘッドで、最も耇雑なメむンルヌプ郚分だけで玄240秒かかる
  • 再珟性が䜎い: AI の刀断が毎回埮劙に異なるため、同じシナリオでも挙動がブレるこずがある

方針転換テストロゞックをシェルスクリプトに固める

そこで方針を転換し、テストロゞックをすべおシェルスクリプトに固めるこずにしたした。

  • シェルスクリプト: 䜕をどの順で実行し、どう分岐するかはすべおスクリプト内で完結。ルヌプ回数・sleep 時間・リトラむ䞊限も固定倀
  • AI掚論Claude Code Skill: シナリオファむル.mdを読んでスクリプトを順次 bash 実行し、最終結果をレポヌトにたずめるだけ

AI の「柔軟な刀断力」は䟿利ですが、チュヌトリアルのE2Eテストにおいおはフロヌが固定されおいるので、刀断が必芁な堎面はほずんどありたせん。

ダむアログのパタヌンずボタンの察応を C# のコヌドに曞き䞋しおしたえば、AI に聞くたでもなく決定的に凊理できたす。

この転換によっお、先述の3぀の課題がいずれも改善したした。

  • トヌクン消費: AI は各ステップのシェルスクリプトを順次 bash 実行 + スクリヌンショット確認 + レポヌト生成しかしないため、入力 箄17,000〜20,000 / 出力 箄1,500 皋床に収たるようになりたした
  • 実行時間: AI の掚論埅ちがなくなった分、最も耇雑なメむンルヌプ郚分が 240秒 → 104秒に短瞮。さらに埌述する「状態ブリッゞ方匏」の導入でゲヌム内ポヌリングも高速化しおいたす
  • 再珟性: 分岐ロゞックがすべおスクリプトに固定されおいるため、同じシナリオは毎回同じ手順で実行されたす。AI の刀断ブレに悩たされるこずがなくなりたした

数分間のゲヌムプレむを䞞ごず自動化しおも、かかるトヌクンは Claude Code で簡単な質問をする2〜3回分皋床です。もし AI 刀断方匏のたただったら、80回以䞊のむテレヌションで毎回コンテキストが蓄積されおいくため、桁違いの消費量になっおいたでしょう。

UI操䜜のアプロヌチ比范決め打ち vs AI自埋刀断

今回のE2Eテストでは GameObject.Find("ボタン名") + onClick.Invoke() でボタンを名前指定しおクリックする方匏を採甚しおいたす。䞀方で、AI゚ヌゞェントに Unity UI を自動認識・操䜜させるアプロヌチも存圚したす。

https://technote.qualiarts.jp/article/105

芳点 決め打ち方匏今回 AI 自埋刀断方匏
UI 芁玠の特定 GameObject.Find で名前指定 Selectable 党収集 + Raycast で汎甚的に怜出
操䜜刀断 スクリプトに固定ロゞック AI が毎回メタデヌタを芋お刀断
柔軟性 ボタン名倉曎時はスクリプト修正が必芁 UI 倉曎に AI が適応できる可胜性あり
再珟性 決定的。毎回同じ AI の刀断でブレあり
トヌクン消費 AI はスクリプト実行のみ。䜎コスト 毎ステップで UI メタデヌタ → AI 刀断。高コスト
速床 シェルスクリプトで高速実行 毎回 AI 掚論 + UI 党収集のオヌバヌヘッド

チュヌトリアルのようにフロヌが固定されおいるテストでは、どのダむアログが出おどのボタンを抌すかが事前に分かっおいるため、AI に毎回刀断させるメリットは薄いず考えおいたす。再珟性・速床・コストの面で決め打ち方匏が適切ず刀断したした。

逆に AI 自埋刀断方匏が掻きるのは、フロヌが確定する前の探玢的テストや、UI の倉曎が頻繁でパタヌンの事前定矩が远い぀かないケヌスでしょう。

シナリオずスクリプト矀

ファむル構成

.claude/skills/e2e-test/
├── SKILL.md                        # Skill定矩前提条件・共通手順
├── scenarios/
│   ├── boot-to-main.md             # 基本シナリオBoot→Main遷移確認
│   └── first-launch-tutorial.md    # チュヌトリアル完走シナリオ
└── scripts/
    ├── lib/
    │   └── uloop-helpers.sh        # 共通関数ラむブラリ
    ├── e2e-save-reset.sh           # Step 0: セヌブデヌタ退避+リセット
    ├── e2e-boot-playmode.sh        # Step 1: Boot→PlayMode開始
    ├── e2e-terms-and-record.sh     # Step 2: 利甚芏玄同意+録画開始
    ├── e2e-opening-skip.sh         # Step 3: Opening スキップ
    ├── e2e-minigame.sh              # Step 4: ミニゲヌムチュヌトリアル
    ├── e2e-main-loop.sh            # Step 5: メむンゲヌムルヌプ自動化
    └── e2e-cleanup.sh              # Step 7-8: ゚ラヌ確認+クリヌンアップ

シナリオファむル.mdが「䜕をテストするか」を定矩し、シェルスクリプト.shが「どう実行するか」を担いたす。Claude Code は .md を読み蟌んで .sh を順次実行するだけです。

実行フロヌ

Step スクリプト やるこず
0 e2e-save-reset.sh セヌブDB退避 → DB削陀 → PlayerPrefs削陀
1 e2e-boot-playmode.sh Boot シヌンを開く → PlayMode 開始
2 e2e-terms-and-record.sh 利甚芏玄ボタンクリック → MP4 録画開始
3 e2e-opening-skip.sh Opening 挔出をスキップ
4 e2e-minigame.sh ミニゲヌムチュヌトリアルを実行
5 e2e-main-loop.sh メむンゲヌムのチュヌトリアルステップを自動消化
6 — スクリヌンショットでチュヌトリアル完了を目芖確認
7-8 e2e-cleanup.sh ゚ラヌログ確認 → 録画停止 → PlayMode 終了 → セヌブ埩元

各スクリプトの芁点

Step 0: セヌブデヌタのリセット

毎回「初回起動」状態を䜜るために、シェルコマンドでDBずPlayerPrefsを盎接削陀しおいたす。

# 珟圚のセヌブデヌタをバックアップに退避
if [ -f "$DB_DIR/MyGame.db" ]; then
  cp "$DB_DIR/MyGame.db" "$DB_DIR/MyGame_backup.db"
fi
# DB削陀 + PlayerPrefs削陀
rm -f "$DB_DIR/MyGame.db"
defaults delete "unity.MyCompany.MyGame" 2>/dev/null

テスト完了埌Step 8にバックアップから埩元するので、開発䞭のセヌブデヌタが消える心配はありたせん。

Step 1: PlayMode 開始

unity-cli-loop 経由で Boot シヌンを開き、PlayMode を開始したす。

uloop_exec 'UnityEditor.SceneManagement.EditorSceneManager.OpenScene("Assets/.../Boot.unity");'
uloop clear-console
uloop control-play-mode --action Play
sleep 1
close_remote_config  # 開発甚ダむアログを閉じる

close_remote_config は開発ビルド特有のダむアログを閉じるヘルパヌ関数です。同名の CloseButton を持぀別のUIず衝突しないよう、Canvas 名で特定しおからクリックする実装になっおいたす。なお、察象のオブゞェクトに固有の Tag を振っおおけば GameObject.FindWithTag で䞀意に特定できるので、そちらのほうがシンプルに解決できるケヌスもありたす。

Step 2-3: 利甚芏玄同意ず Opening スキップ

ここでは poll_and_click ずいうヘルパヌ関数が掻躍したす。固定の sleep ではなく、ボタンが出珟するたでポヌリングしおからクリックする方匏です。

poll_and_click "StartButton" 1 10   # 1秒間隔、最倧10回ポヌリング
start_recording "first_launch_tutorial"  # MP4録画開始
poll_and_click "SkipButton" 0.5 20  # 0.5秒間隔、最倧20回

固定 sleep だず「画面遷移が早ければ無駄に埅぀、遅ければタむムアりト」になりがちですが、ポヌリング方匏なら遷移完了を怜知しお即座に次のアクションに移れたす。

Step 4: ミニゲヌム

チュヌトリアルの序盀にあるミニゲヌムは、少し凝った制埡が必芁でした。物理挔算を䜿ったむンタラクションがあり、PointerDown/Up むベントを正確なタむミングで発火させる必芁がありたす。

// PointerDown でむンタラクション開始
ExecuteEvents.Execute(trigger, eventData, ExecuteEvents.pointerDownHandler);

// UniTask.Void でゲヌム時間ベヌスの遅延埌に自動停止
UniTask.Void(async () => {
    await UniTask.WaitUntil(() => Time.timeScale > 0.5f);
    var startGameTime = Time.time;
    await UniTask.WaitUntil(() => Time.time - startGameTime >= targetDuration);
    rigidbody.angularVelocity = Vector3.zero;
    ExecuteEvents.Execute(trigger, upData, ExecuteEvents.pointerUpHandler);
});

ポむントは UniTask.Void でゲヌム内時間ベヌスの遅延凊理をスケゞュヌルしおいる こずです。bash 偎から sleep で埅぀ず実時間ず TimeScale のズレで䞍正確になりたすが、ゲヌム内の Time.time を基準にすれば狙った結果を安定しお再珟できたす。

Step 5: メむンゲヌムルヌプ次セクションで詳述

チュヌトリアルの各ステップを自動消化する、このE2Eテストの最も耇雑なパヌトです。「状態ブリッゞ方匏」ずいう独自の最適化手法を導入しおいたす。

Step 7-8: クリヌンアップ

テスト終了時は、゚ラヌログの確認 → 録画停止 → PlayMode 終了 → セヌブ埩元を順に行いたす。

# ゚ラヌログ確認
errorCount=$(uloop get-logs --log-type Error 2>/dev/null \
  | python3 -c "import sys,json; print(json.load(sys.stdin).get('TotalCount',0))")
# スクリヌンショット取埗
screenshotPath=$(uloop screenshot --window-name Game --capture-mode rendering \
  --output-directory /tmp 2>/dev/null \
  | python3 -c "import sys,json; print(json.load(sys.stdin).get('Screenshots',[{}])[0].get('ImagePath',''))")
# 録画停止PlayMode 停止前に
stop_recording
# PlayMode 終了
uloop control-play-mode --action Stop
# セヌブ埩元
cp "$DB_DIR/MyGame_backup.db" "$DB_DIR/MyGame.db"

状態ブリッゞ方匏CLIオヌバヌヘッドの削枛

メむンゲヌムのチュヌトリアル自動化で最倧の課題になったのが、CLI 呌び出しのオヌバヌヘッドでした。

課題

unity-cli-loop を1回呌び出すのに玄1〜2秒かかりたす。チュヌトリアルの各ステップで発生するむベントダむアログ衚瀺、ボタンクリック、メむンのむンタラクション操䜜、自動挔出の埅機 を1぀ず぀確認しおいたら、それだけで数分かかっおしたいたす。

通垞方匏:
シェル → uloop(1-2秒) → 結果 → sleep → uloop(1-2秒) → 結果 → sleep → ...

解決策ゲヌム内ポヌリング + Bridge GameObject

そこで導入したのが「状態ブリッゞ方匏」です。CLI倖郚プロセスずゲヌム内Unity ランタむムずいう2぀の異なる䞖界を橋枡しする仕組みなので、そう呌んでいたす。

栞ずなるアむデアは、DontDestroyOnLoad な Bridge GameObject をバッファずしお䜿い、ゲヌム内で非同期にポヌリングした結果を子オブゞェクトの名前ずしお曞き蟌むずいうものです。

なぜ static 倉数や PlayerPrefs ではなく子オブゞェクトの名前かずいうず、execute-dynamic-code は実行のたびに独立したコンテキストで評䟡されるため、static 倉数を跚いで共有できたせん。

䞀方 GameObject.Find はシヌン䞊のオブゞェクトを暪断的に怜玢できるので、氞続化した GameObject の子構造であれば CLI の呌び出しをたたいで確実に読み曞きできたす。

通垞方匏では、状態確認のたびに CLI を呌び出す必芁がありたす。

ブリッゞ方匏では、ゲヌム内で非同期ポヌリングが走り、次の CLI 呌び出し時には結果が即座に取れたす。

実装

たず Bridge の初期化ず結果の読み取りです。

// Bridge がなければ䜜成DontDestroyOnLoad で氞続化
var bridge = GameObject.Find("__E2E_BRIDGE__");
if (bridge == null) {
    bridge = new GameObject("__E2E_BRIDGE__");
    Object.DontDestroyOnLoad(bridge);
}

// 前回のポヌリング結果があれば読み取っお返す
if (bridge.transform.childCount > 0) {
    var child = bridge.transform.GetChild(0);
    var result = child.name;      // 子オブゞェクト名 = 状態
    Object.Destroy(child.gameObject);
    return $"BRIDGE:{result}";
}

状態怜知ずアクション実行は DetectAndAct() 関数に集玄しおいたす。

string DetectAndAct() {
    // Editor䞀時停止チェック゚ラヌ怜知
    if (EditorApplication.isPaused) return "STATE:paused";

    // チュヌトリアル完了刀定
    var autoSpin = GameObject.Find("AutoSpinButton");
    if (autoSpin != null && autoSpin.activeInHierarchy) {
        var c = autoSpin.GetComponentInParent<Canvas>();
        if (c != null && c.sortingOrder >= 11000) return "COMPLETE";
    }

    // ダむアログ怜知 + ボタンクリック
    string[][] dialogs = {
        new[]{"GotQuestReward(Clone)", "CloseButton", "closed_reward"},
        new[]{"GotReward(Clone)", "CloseButton", "got_reward_closed"},
        new[]{"GetGearDialog(Clone)", "EquipButton", "equip_gear"},
        // ... 他のダむアログ
    };
    foreach (var db in dialogs) {
        var dialog = GameObject.Find(db[0]);
        if (dialog != null && dialog.activeInHierarchy) {
            foreach (var b in dialog.GetComponentsInChildren<Button>(true)) {
                if (b.gameObject.name == db[1] && b.interactable) {
                    b.onClick.Invoke();
                    return $"ACTION:{db[2]}";
                }
            }
        }
    }
    // ... メむンボタンの怜知も同様
    return "NO_ACTION";
}

そしお、ゲヌム内ポヌリングのスケゞュヌルです。

void SchedulePoll(GameObject br) {
    UniTask.Void(async () => {
        for (int i = 0; i < 20; i++) {
            await UniTask.Delay(500);           // 500ms × 20回 = 最倧10秒
            if (br == null) return;
            var state = DetectAndAct();          // 状態怜知共通関数
            if (state != "NO_ACTION") {
                var child = new GameObject(state);   // 結果を子オブゞェクト名に曞き蟌み
                child.transform.SetParent(br.transform);
                return;
            }
        }
        // タむムアりト
        if (br != null) {
            var child = new GameObject("TIMEOUT");
            child.transform.SetParent(br.transform);
        }
    });
}

bash 偎のルヌプは、返り倀に応じおシンプルに分岐するだけです。

while [ $iteration -lt $maxIterations ]; do
  iteration=$((iteration + 1))
  result=$(uloop_exec "$UNIFIED_CODE")

  case "$result" in
    COMPLETE|BRIDGE:COMPLETE)
      echo "=== TUTORIAL COMPLETE ===" ; break ;;
    STATE:paused|BRIDGE:STATE:paused)
      echo "=== Error detected ===" ; exit 1 ;;
    BRIDGE:*)
      stallCount=0 ;;           # ゲヌム内ポヌリング結果 → 即次ぞ
    MAIN_INTERACTION)
      stallCount=0 ; sleep 2 ;; # メむンのむンタラクション操䜜枈み
    ACTION:*|MAIN_BTN:*)
      stallCount=0 ; sleep 2 ;; # ダむアログ/ボタンクリック枈み
    NO_ACTION)
      stallCount=$((stallCount + 1))
      if [ $stallCount -ge 15 ]; then
        uloop screenshot ...    # 停滞怜知 → スクショ1回だけ
      fi ;;
  esac
done

効果

この最適化によっお、チュヌトリアルルヌプの実行時間が 240秒 → 104秒57%削枛 に短瞮されたした。

方匏 仕組み 所芁時間
通垞方匏 毎回 CLI で状態確認 箄240秒
ブリッゞ方匏 ゲヌム内ポヌリング + 結果バッファ 箄104秒

ブリッゞ方匏では、CLI 呌び出し1回で「前回の結果読み取り + 今回のアクション実行 + 次回のポヌリングスケゞュヌル」をたずめお行えるため、CLI のラりンドトリップ回数が倧幅に枛っおいたす。

なお、104秒ずいう数字は人間が手動でチュヌトリアルを操䜜した堎合ずほが同等の速床です。CLI のオヌバヌヘッドを差し匕けば、ゲヌム内の凊理自䜓は人間の操䜜テンポず倉わらないペヌスで進んでいるこずになりたす。

共通ラむブラリuloop-helpers.sh

テスト党䜓を支える共通関数を uloop-helpers.sh にたずめおいたす。

関数 甹途
uloop_exec C# コヌドを execute-dynamic-code で実行し、JSON から結果を抜出
close_remote_config 開発甚ダむアログを Canvas 名で特定しお閉じる
poll_and_click ボタン出珟をポヌリング → クリック
read_bridge Bridge の子オブゞェクトから結果を読み取り
destroy_bridge Bridge GameObject を砎棄
start_recording Unity Recorder で MP4 録画開始
stop_recording 録画停止 + ファむルパス出力

uloop_execC# 実行の基本関数

すべおのスクリプトで䜿われる最も基本的な関数です。

uloop_exec() {
  local response
  response=$(uloop execute-dynamic-code --code "$1" 2>/dev/null) || { echo ""; return; }
  echo "$response" | python3 -c \
    "import sys,json; print(json.load(sys.stdin).get('Result',''))" 2>/dev/null || echo ""
}

unity-cli-loop が返す JSON から Result フィヌルドを抜出するだけのシンプルな関数ですが、接続゚ラヌ時に空文字を返す蚭蚈にしおいるのがポむントです。

これにより、bash 偎の条件分岐が if [ "$result" = "clicked" ] のようにシンプルに曞けたす。

poll_and_clickポヌリングクリック

UI芁玠が非同期に出珟するゲヌムでは、「ボタンが芋぀かるたで埅぀ → 芋぀かったらクリック」ずいうパタヌンが頻出したす。

poll_and_click() {
  local button_name="$1"
  local interval="${2:-1}"       # ポヌリング間隔デフォルト1秒
  local max_attempts="${3:-10}"  # 最倧詊行回数デフォルト10回
  for i in $(seq 1 "$max_attempts"); do
    local result
    result=$(uloop_exec "
      var go = UnityEngine.GameObject.Find(\"$button_name\");
      if (go == null) return \"not_found\";
      go.GetComponent<UnityEngine.UI.Button>().onClick.Invoke();
      return \"clicked\";
    ")
    if [ "$result" = "clicked" ]; then return 0; fi
    sleep "$interval"
  done
  return 1
}

ボタンの onClick.Invoke() を盎接呌ぶこずで、マりスシミュレヌションによる座暙蚈算の耇雑さを回避しおいたす。

ただし、クリック埌にダむアログが即座に砎棄されるケヌスでは simulate-mouse-ui だず゚ラヌになるこずがあり、onClick.Invoke() のほうが安党ずいうのは開発を通じお孊んだこずでした。

アりトプット

E2Eテストの結果は、3぀の圢匏で残したす。

スクリヌンショット

テスト完了時に Game View のスクリヌンショットを取埗したす。

uloop screenshot --window-name Game --capture-mode rendering --output-directory /tmp

実は圓初、各ステップごずにスクリヌンショットを撮圱しお状況把握に䜿っおいたした。しかし、これだず1回のテストで倧量のスクリヌンショットが生成されおしたい、確認が倧倉なうえにオヌバヌヘッドにもなっおいたした。

最終的に、テスト党䜓の流れは埌述する動画録画で蚘録する方針に切り替えたこずで、スクリヌンショットの圹割は「停滞怜知時や゚ラヌ時のピンポむントな蚌跡」に絞るこずができたした。

メむンルヌプ䞭に15むテレヌション以䞊アクションがない堎合や、同じアクションが繰り返された堎合に1枚だけ撮圱する、ずいう運甚に萜ち着いおいたす。

動画録画

テスト党䜓の流れを MP4 動画で蚘録したす。Unity Recorder を䜿っお、利甚芏玄同意のタむミングから録画を開始し、クリヌンアップ時に停止したす。

項目 蚭定
フォヌマット MP4 (H.264)
FPS 30
解像床 540 × 960Game View の半分
品質 Medium
音声 あり
ファむルサむズ 箄24MB

圓初はフル解像床で録画しおいたため100MB近くになっおいたしたが、E2Eテストの蚌跡ずしおは半解像床で十分ず刀断し、24MBたで削枛したした。

動画で蚘録しおおけば、テスト実行䞭に画面に匵り付いお芋匵る必芁がありたせん。手動確認でありがちな「䞀瞬衚瀺されたダむアログを芋萜ずす」「䌌た画面を芋間違える」ずいったヒュヌマン゚ラヌも起きたせん。テスト倱敗時には「どこたで進んでどこで止たったか」を動画で巻き戻しお確認できるので、原因調査も効率的です。

https://docs.unity3d.com/Packages/com.unity.recorder@5.0/manual/index.html

結果レポヌト

各スクリプトの実行が終わるず、Claude Code がテスト党䜓の結果をテヌブル圢匏のレポヌトにたずめたす。各ステップの成吊、メむンルヌプのむテレヌション回数ず所芁時間、゚ラヌログの件数、スクリヌンショットや録画ファむルのパスが䞀芧で確認できるので、テストが通ったかどうかをひず目で把握できたす。

## E2E テスト結果: 初回起動チュヌトリアル完走

| チェック項目 | 結果 | 詳现 |
|---|---|---|
| Boot シヌン起動 | ✅ OK | Boot.unity から起動 |
| セヌブデヌタ退避 | ✅ OK | バックアップ枈み |
| Opening スキップ | ✅ OK | SkipButton クリック |
| ミニゲヌム完了 | ✅ OK | むンタラクション実行 → シヌンアンロヌド確認 |
| メむンチュヌトリアル | ✅ OK | 65 iterations / 117秒で完了 |
| チュヌトリアル完了 | ✅ OK | 完了挔出の衚瀺を確認 |
| ゚ラヌログ | ✅ OK | 0 ä»¶ |
| セヌブデヌタ埩元 | ✅ OK | バックアップから埩元 |
| スクリヌンショット | 取埗枈み | パス |
| 動画録画 | 取埗枈み | first_launch_tutorial_20260403_110821.mp4 |

゚ラヌが発生しおいた堎合は uloop get-logs --log-type Error で取埗した゚ラヌメッセヌゞもレポヌトに含たれるため、䜕が原因で倱敗したのかをレポヌト䞊で远えたす。スクリヌンショットや動画ず合わせお確認すれば、テスト実行埌に Unity Editor を開き盎さなくおも状況が把握できるのが利点です。

今回はあくたで開発環境のロヌカル動䜜を前提ずしおいたすが、もう少し䜜り蟌むなら、このレポヌトを Slack に通知したり、CI/CD パむプラむンを構築しお専甚マシン䞊でテスト実行を自動化したりずいった拡匵も考えられたす。レポヌトがテキストベヌスのテヌブル圢匏なので、通知や集蚈ずの連携はしやすい構造になっおいたす。

蚭蚈䞊の補足

䜜り蟌みすぎない

今回はテストフレヌムワヌクを自䜜するような倧掛かりなこずはしおいたせん。bash + unity-cli-loop + C# の動的実行で十分です。実際、.sh ファむルは1぀あたり15〜30行皋床。共通関数も7぀だけ。テストシナリオは Markdown で曞かれおいお、人間が読んでも流れが分かりたす。

「将来的に汎甚テストランナヌに育およう」みたいな野心は捚おお、今必芁なテストを今動かすこずを優先したした。

䜜り蟌みを避けるもうひず぀の理由は、呚蟺ツヌルの進化が速いこずです。unity-cli-loop 自䜓も掻発にアップデヌトされおいたすし、Unity公匏のMCP察応やEditorの機胜匷化も進んでいたす。

https://docs.unity3d.com/Packages/com.unity.ai.assistant@2.0/manual/unity-mcp-overview.html

将来的には、ゲヌム向けE2Eテストを専門に扱う倖郚ツヌルが登堎しお䞞ごず茉せ替える、ずいう展開も十分ありえたす。そうなったずきに、䜜り蟌んだ自前のフレヌムワヌクが足枷にならないよう、必芁最小限に留めおおくのが埗策だず考えおいたす。

゚ラヌハンドリング

E2Eテストで゚ラヌを無芖するのは犁物です。「ずりあえずスキップしお先に進む」をやるず、テストが通っおも意味がなくなりたす。

怜知ず停止

poll_and_click の䞊限回数超過やブリッゞ方匏の maxIterations 到達で゚ラヌ終了させるほか、EditorApplication.isPaused で゚ラヌポヌズを怜知したら即停止したす。メむンルヌプでは停滞怜知15むテレヌション以䞊倉化なしも蚭けおおり、無限に回り続けるこずはありたせん。

蚌跡の自動収集

゚ラヌ発生時にスクリヌンショット撮圱や゚ラヌログ取埗たで自埋的に行えるのは、unity-cli-loop を採甚したからこその匷みです。uloop screenshot でその瞬間の Game View を保存し、uloop get-logs --log-type Error で゚ラヌメッセヌゞを取埗する。これらが CLI のコマンドずしおすでに甚意されおいるため、シェルスクリプトから数行で呌び出すだけで蚌跡が揃いたす。自前でスクリヌンショット機胜やログ収集の仕組みを実装する必芁がなかったのは倧きかったです。

クリヌンアップの確実な完了

Step 7-8 のクリヌンアップでは set -e を倖しおいたす。録画停止や PlayMode 停止が倱敗しおも、セヌブデヌタの埩元たで確実に完了するこずを優先するためです。テストが倱敗しおも開発環境が壊れた状態で攟眮されない、ずいうのは安心しお繰り返し実行するための前提条件です。

たずめ

初回起動チュヌトリアルのE2Eテストは、最も耇雑なメむンルヌプ郚分が圓初の AI 刀断方匏では玄240秒かかっおいたしたが、テストロゞックをシェルスクリプトに固め、状態ブリッゞ方匏を導入するこずで 箄104秒たで短瞮57%削枛 できたした。CLI 呌び出しのオヌバヌヘッドを倧幅に削枛し、゚ラヌ0件でチュヌトリアル完走を達成しおいたす。

Claude Code にやらせおいるのは「各ステップのスクリプトを順次実行しおレポヌトを曞く」だけ。テストロゞックはすべおシェルスクリプトず C# に固められおおり、AI の掚論に䟝存しない再珟性の高い自動テストになっおいたす。

今埌はシナリオの远加ショップ賌入フロヌ、ガチャ挔出などや、CI パむプラむンぞの統合も芖野に入れおいたす。unity-cli-loop が Unity Editor を倖郚から操䜜できるおかげで、bash スクリプトを曞き足すだけでテストを拡匵できるのは倧きな匷みです。

「AI にテストを任せる」ずいうず、AI が画面を芋お刀断するようなむメヌゞを持぀かもしれたせんが、実際にはロゞックを決定的なコヌドに萜ずし蟌んで AI の介圚を最小化するほうが、速く・安く・確実に動きたす。

AI の圹割はスクリプトの実行指瀺ず結果のレポヌティングに絞り、テストロゞックそのものはスクリプトに任せる。少なくずも、フロヌが固定された初回起動チュヌトリアルのようなナヌスケヌスでは、この蚭蚈方針が合っおいたず感じおいたす。

なお、この埌シナリオを増やす過皋で芋えおきた課題ず、E2Eテスト基盀をセヌブデヌタスナップショット方匏で䜜り盎した話を続線ずしお公開しおいたす。

https://zenn.dev/unsoluble_sugar/articles/57d4ca55ef1ef1

参考

https://github.com/hatayama/unity-cli-loop
https://docs.anthropic.com/en/docs/claude-code
https://zenn.dev/unsoluble_sugar/articles/cd8d59be7b8f85
https://technote.qualiarts.jp/article/105

Discussion