LINEヤフー Tech Blog

LINEヤフー株式会社のサービスを支える、技術・開発文化を発信しています。

デバイス操作はAIエージェントの時代へ。mobile-mcpを活用したAndroid UI/E2Eテストの挑戦

こんにちは。LINEでAndroidアプリの開発を担当しているMoriです。

この記事では、DroidKaigi 2026で発表した「デバイス操作はAIエージェントの時代へ。mobile-mcpを活用したAndroid UI/E2Eテストの挑戦」をもとに、AIエージェントによるモバイルデバイス操作の開発での活用方法を紹介します。

DroidKaigi 2026で発表する様子

AIエージェントにコードを書かせることは、すでに日常的になりつつあります。次の段階として、実装したアプリをエージェント自身が起動し、画面を読み取り、操作し、結果を確認できるようになってきました。一方で、これは従来のユニットテストや自動E2Eテスト、手動テストをそのまま完全に置き換えるものではありません。得意な領域と他の方法に任せるべき領域を見極めることが重要です。

AIエージェントにデバイスを操作させる

今回紹介するmobile-next/mobile-mcpは、AIエージェントからモバイルデバイスを操作するためのMCPサーバーです。画面上の要素の取得、タップ、スワイプ、テキスト入力などに対応し、AndroidとiOSを同じインターフェースで扱えます。CodexやClaude CodeなどMCP対応のエージェントに接続すると、ツール呼び出しを通じてデバイスやエミュレータを操作できます。

エージェントは画面情報を取得し、操作した結果を再確認するループを自律的に回すことができます。ここからは、普段の開発での活用例と、効果を高めるために開発者が準備できることを説明します。

活用しやすい3つの場面

1. 実装直後の動作確認

コードを書く工程はかなりAIエージェントに任せられるようになってきました。mobile-mcpを使えば、その後の動作確認もエージェントに任せられます。問題が見つかれば、修正と再確認まで自律的に繰り返せます。

実装、デバイステスト、修正を繰り返すフロー

私は、チケットの内容を渡すだけで、計画作成から実装、コードレビュー、デバイステスト、Pull Requestの作成までの一連の流れをエージェントに実行させています。もちろん、完了後に細部を調整したり、最初からやり直したりすることも多々あります。それでも、まず動くものを作り、デバイス上の問題を早い段階で見つけることには価値があります。

2. バグ調査

エージェントにバグ調査を任せることにも、大きな効果を感じています。再現手順と判明している事実を渡し、コードと実機の動作を行き来しながら調査させます。同じ操作を粘り強く繰り返せるため、多くのケースで原因にたどり着くことが可能です。

バグ調査、修正、確認を繰り返すフロー

バグ調査をエージェントに任せる際に、私が実践している2つの工夫を紹介します。

1つ目は、必要に応じて調査対象の周辺にログを追加することです。怪しい箇所にLog.dなどを加え、ログを監視しながら再現手順を実行させると、原因の箇所を絞り込みやすくなります。

2つ目は、実行時に実装を切り替えられるようにすることです。たとえば、新旧の実装の片方でのみ問題が起きる場合や、一部の機能を有効にしたときだけ発生するバグがあるとします。このようなとき、疑わしい処理を有効・無効にして比較すると、原因の特定に役立ちます。

ただし、そのたびにコードを変更してビルド・インストールし直すと、調査の速度が落ちます。デバッグ用に外部ファイルの有無で一部の処理を切り替える仕組みを使えば、再ビルドせずにADB経由で実装を切り替えられます。ちなみに、これはエージェントが教えてくれた方法です。

val disableCursorCache = File(
    context.getExternalFilesDir(null),
    "debug-flags/disable_cursor_cache"
).exists()

Score(useCursorCache = !disableCursorCache)
# 有効化
touch disable_cursor_cache
adb push disable_cursor_cache \
  /sdcard/Android/data/com.example/files/debug-flags/disable_cursor_cache

# 無効化
adb shell rm \
  /sdcard/Android/data/com.example/files/debug-flags/disable_cursor_cache

3. ストア用アセットの作成

少しニッチですが、プロモーション用スクリーンショットの撮影もエージェントに任せやすい作業です。特に多言語アプリでは、言語を切り替えながら必要な画面を集めるだけでも手間がかかります。AIエージェントにアプリの操作とスクリーンショットの保存を任せ、後からスクリプトでラベルや背景を合成すれば、Google Play ストアやLP向けアセットの作成を効率化できます。

AIエージェントに任せないほうがよいこと

AIエージェントによるデバイス操作には向かない用途もあります。現時点では、次の4つをAIエージェントだけで完結させるのは難しいと考えています。

リリース前の最終判定

エージェントは手順を飛ばしたり、結果を誤解したまま成功と判断したりする可能性があります。リリースの可否は、人間による最終確認と信頼性の高いE2Eテストを組み合わせて判断すべきです。

反復的な回帰テスト

エージェントによる操作は、画面を読み取って判断しながら進めるため、時間も費用もかかります。実行結果の再現性も、スクリプトで固定したテストには及びません。繰り返し実行するテストには、ユニットテストやE2Eテストが適しています。

精密なレイアウト検証

エージェントは、余白や整列、デザインガイドラインとの細かなずれを見逃すことがあります。画面を拡大してピクセル数を数えさせることも可能ですが、人間が確認したほうが早く確実な場合が多いです。繰り返し確認するコンポーネントには、スクリーンショットテストの導入が効果的です。

主観的なUXの評価

アニメーションが心地よいか、操作感が自然かといった主観的な評価は、実際に人が触って判断する必要があります。

関連ツール

mobile-mcpのほかにも、エージェントによるデバイス操作やUI状態の取得に役立つツールがあります。GoogleのAndroid CLIと、LINEヤフーが公開しているsim-useです。

ツール向いている場面特長
mobile-mcpまずデバイス操作を試したいMCP経由で操作できる
Android CLIAndroidのUI状態を詳しく調べたい状態情報や差分を取得できる
sim-useエージェントに渡す情報量を抑えたいコンパクトなUIアウトラインを出力できる

mobile-mcp

mobile-mcpは、画面要素をJSON形式で取得し、タップ、スワイプ、文字入力などを実行できるMCPサーバーです。画面要素には、型、表示テキスト、アクセシビリティラベル、識別子、座標が含まれます。MCPを接続するだけで、エージェントに利用可能な操作を伝えられます。そのため、まずデバイス操作を試したい場合に扱いやすい選択肢です。

Android CLI

Android CLIは、エージェントによるAndroid開発を支援するコマンドラインツール群です。新規プロジェクトの作成、エミュレータの管理、Android Studioと連携したCompose Previewの画像レンダリングなど、開発で使う便利なコマンドが用意されています。

そのうち、デバイスの状態確認に関連するのが、UI状態を取得するlayoutコマンドです。UI階層をJSONとして取得でき、ラベルや座標に加えて、選択状態・トグル状態などのinteraction情報も確認できます。--diffオプションでは、前回取得したレイアウトからの差分だけを返せます。状態の変化を追跡するときに、エージェントへ渡すトークンを節約できます。

現時点では、Android CLIにタップやスワイプを実行するコマンドはありません。デバイスを操作する場合は、ADBを直接実行するか、他のツールと組み合わせて使うと良いでしょう。

sim-use

sim-useは、iOS SimulatorとAndroidデバイス/エミュレータをCLIから操作するためのツールです。画面要素を長いJSONの列ではなく、上部・コンテンツ・下部のようにグループ化したUIアウトラインとして出力します。さらに、要素には@1、@2のような別名が付与され、その別名を指定して操作できます。

[Top y<280]
  @1  Button  "Back"
  @2  Button  "Undo"
  @3  Button  "Redo"  disabled

[Bottom y>=2144]
  @31 Button  "Note"
  @32 Button  "Rest"
  @33 Button  "Other"

座標を毎回指定せずに要素を操作でき、出力もコンパクトです。エージェントに渡す情報量を抑えたい場合に、特に有用です。

sim-useについて詳しくは、以下のブログ記事も参考にしてください。

より効果的に使うためのTips

ここからは、AIエージェントによるデバイス操作の効果を高めるために、私が実践している工夫を紹介します。

スクリーンショットよりView Treeを優先する

AIエージェントが画面の状態を把握する方法は、大きく2つあります。

  • スクリーンショット
  • レイアウト情報

スクリーンショットの解析にはコストがかかり、座標を正確に特定しにくいという課題もあります。基本は、View Treeを取得 → 操作 → View Treeで結果を確認というループで進めるのがおすすめです。

View Treeを取得して操作し、結果を確認するフロー

スクリーンショットは、一通りの動作が終わったタイミングで撮影するのがおすすめです。エージェントが期待通り動作したかを視覚的に確認できるだけでなく、後から人間がチェックする際にも役立ちます。また、Pull Requestの説明欄に添付する画像としても最適です。

画面ごとの操作リファレンスを用意する

画面ごとにリファレンスファイル(参考資料)を用意し、次の情報を記録しておきます。

  • 画面を開く方法
  • 操作方法
  • 成功と判断できる状態

複雑なジェスチャーなど、ツールの標準操作だけでは表現できない操作もここに記録します。

たとえば、細かいドラッグ操作のためにmobile_swipe_on_screenではなく、ADBのinput motioneventを直接利用する必要がある場合があります。開始点や移動量だけでなく、完了後にView Treeで確認すべき項目まで記録しておくと、エージェントが操作を再現しやすくなります。

# 指定した移動量だけ、縦方向へドラッグする
DEV=emulator-5554
X=546
Y0=950
STEP_PX=32     # 1ステップあたりの移動量
DIRECTION=-1   # +1: 下、-1: 上
Y1=$((Y0 + DIRECTION * STEP_PX))

adb -s $DEV shell input motionevent DOWN$X $Y0
adb -s $DEV shell input motionevent MOVE$X $(( (Y0 + Y1) / 2 ))
adb -s $DEV shell input motionevent MOVE$X $Y1
adb -s $DEV shell input motionevent UP$X $Y1

Screen Mapを用意する

画面を横断して作業するために、Screen Mapを用意します。Screen Mapには、次の情報を記載します。

  • 画面名
  • 画面の概要
  • リファレンスファイルへのリンク
  • 関連するActivityやNavigation key、主要なComposable関数

主要コードを記載しておくことで、変更した箇所から関連する画面と操作方法をすばやくたどることが可能になります。1画面につき1行にまとめると、エージェントが検索しやすいです。

画面概要リファレンスファイル関連コード
Home譜面を閲覧・作成するdocs/screens/home.mdMainActivity / HomeNavKey / HomeScreen()
Score Editor音符と譜面の内容を編集するdocs/screens/editor.mdEditorActivity / EditorNavKey / ScoreEditor()
Settingsアプリの設定を変更するdocs/screens/settings.mdSettingsNavKey / SettingsScreen()

リファレンスファイルを最新に保つ

リファレンスの更新もAIエージェントに任せるのがおすすめです。デバイス操作の完了後、「今回の知識をもとにリファレンスを更新して」と依頼します。ただし、説明が冗長になったり誤った内容が含まれたりすることも多いため、最終的な更新内容は必ず人間がレビューしましょう。

実装を進めると、リファレンスファイルと実装が乖離することもあります。各ファイルに最終レビュー日を記録し、古くなったものを定期的に実装と照合する仕組みも有効です。この考え方はデバイス操作のガイドに限らず、アーキテクチャーガイドなど、実装との乖離が問題になるドキュメントにも応用できます。

セマンティクスを整備する

アイコンボタンなど、画面に表示される文字だけでは意味が分からない要素には、contentDescriptionを必ず設定します。これはエージェント向けの特別な情報ではなく、スクリーンリーダーを利用する人にも必要なアクセシビリティ情報です。多言語対応も含め、人間にとって正しい説明を付けることが前提になります。

IconButton(
    onClick = { /* Undo */ }
) {
    Icon(
        imageVector = Icons.AutoMirrored.Filled.Undo,
        contentDescription = "Undo"
    )
}

エージェントにだけ詳細な状態を伝えたい場合は、ComposeのtestTagsAsResourceIdを有効にします。これにより、Modifier.testTagをAndroidのresource IDとして公開できます。リリースビルドには不要な情報なので、デバッグビルドだけで有効にするのが安全です。

testTagsAsResourceIdは親要素で設定できます。

Box(
    modifier = Modifier.semantics {
        testTagsAsResourceId = BuildConfig.DEBUG
    }
) {
    EditorCanvas()
}

個々の要素には、次のようにtestTagを付けられます。

Box(
    modifier = Modifier.testTag(
        "score.cursor;" +
            "staff=$staff;" +
            "measure=$measure;" +
            "index=$index;" +
            "enters=chord;" +
            "pitches=D4;" +
            "noteType=quarter;" +
            "dots=0"
    )
)

現在開いている画面を取得できるようにする

似た画面が複数あると、エージェントが別の画面を操作し、成功したと誤認することがあります。そのため、現在開いている画面をエージェントが認識できることは重要です。

Activityベースの画面では、次のように現在resumed状態のActivityを取得できます。

adb shell dumpsys activity activities | grep mResumedActivity

Navigationを使う画面では、画面遷移時にログを出力します。次のように最後に表示した画面を確認することが可能です。

adb logcat -d -s Screen

これらの情報を確認してから操作するようエージェントに伝えておくと良いです。

ADB経由で設定を変更できるようにする

フィーチャーフラグ、ログイン状態、課金状態など、アプリ内の設定を頻繁に切り替えながら操作したい場面は多々あります。検証の本質ではない操作は、UIを経由せずADBから変更できるようにすると効率が上がります。Androidでは、デバッグビルド限定のBroadcastReceiverを用意し、ADB broadcastで設定の変更や取得を受け付けることが可能です。

class DebugSettingsReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val name = intent.getStringExtra("name") ?: return
        when (intent.action) {
            ACTION_SET -> FeatureFlags.set(
                name,
                intent.getBooleanExtra("enabled", false)
            )
            ACTION_GET -> resultData = "$name=${FeatureFlags.get(name)}"
        }
    }

    companion object {
        const val ACTION_SET = "com.example.SET_FEATURE_FLAG"
        const val ACTION_GET = "com.example.GET_FEATURE_FLAG"
    }
}

以下のように操作することができます。

# フィーチャーフラグを有効化・無効化・取得する
adb shell am broadcast -a com.example.SET_FEATURE_FLAG \
  --es name new_editor --ez enabled true
adb shell am broadcast -a com.example.SET_FEATURE_FLAG \
  --es name new_editor --ez enabled false
adb shell am broadcast -a com.example.GET_FEATURE_FLAG \
  --es name new_editor

公開ビルドにこの経路を含めると、セキュリティ上の問題になり得ます。必ずデバッグビルドだけに限定してください。

broadcastを扱うADBコマンドは複雑になりがちです。そのため、以下のようなシェルスクリプトを用意しておくと、人にもエージェントにも扱いやすくなります。

#!/bin/sh
case "$1" in
  set) adb shell am broadcast -a com.example.SET_FEATURE_FLAG \
         --es name "$2" --ez enabled "$3" ;;
  get) adb shell am broadcast -a com.example.GET_FEATURE_FLAG \
         --es name "$2" ;;
esac

呼び出し例:

./feature-flag.sh set new_editor true
./feature-flag.sh get new_editor

まとめ

AIエージェントがデバイスを操作できるようになったことで、実装後の簡易確認、バグ調査といった作業を、コードの理解と実機操作を往復しながら進められるようになりました。一方で、エージェントが操作しやすいようにするには、エージェント向けのドキュメントと、読み取りやすく操作しやすいアプリを設計することが重要です。

まだベストプラクティスが定まった分野ではありません。だからこそ、AIエージェントと一緒に自分たちのアプリを触りながら、どこまで任せ、どこを人が担うべきかを探っていきたいと思います。