0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AIエージェントによるモバイルアプリ自動操作のための appium-cli の実装の試み

0
Posted at

はじめに

AIエージェントでアプリ操作を自動化する動きが広がっています。Web ブラウザなら Playwright MCP が有名ですが、モバイルアプリでは Appium が定番です。

GitHub を検索すると、Appium を MCP(Model Context Protocol)サーバーとしてラップした実装はいくつか見つかります。しかし、Playwright MCP のように CLI として実装したものは見当たりませんでした。そこで、appium-cli を作成してみました。

既存の MCP サーバー方式にはトークン消費の課題があります。MCP では、ツール呼び出しの結果がすべて LLM のコンテキストウィンドウに直接返されるため、UI ツリー全体(数千〜数万トークン)がそのまま会話コンテキストを消費します。

モバイルアプリの UI ツリーは Web ページよりも深くなりがちです。スナップショットを取るたびに巨大なツリーがコンテキストに積み上がり、トークン上限に達しやすくなります。

MCP 方式

CLI 方式(appium-cli)

appium-cli は UI ツリーをファイルに書き出し、LLM にはメタデータとパスだけ返すので、トークン消費を大幅に抑えられます。これは playwright-cli が採用している方針と同様の試みです。

技術要素

トークン数削減のための実装方法

appium-cli のキモは artifact bundle です。appium-cli snapshot を実行すると、以下の 5 つのファイルが .appium-cli/snapshots/ ディレクトリに生成されます。

ファイル 内容 用途
compact.yml 境界ボックスなしのコンパクトなYAMLツリー 人間/LLM が読みやすい形式
full.yml 境界ボックス付きの完全なYAMLツリー ref 解決・座標ベースの操作用
refs.json ref → ロケータ戦略のマッピング 要素の特定に使用
index.json ロール別集計・ref一覧・コンテナ情報 構造の俯瞰・検索用
meta.json snapshot_id、source、screen_id 等 アーティファクト管理

LLM の stdout に返るのはメタデータだけです:

snapshot_id: native-abc123
source: native
screen_id: d4f8e2...
artifacts:
  compact: .appium-cli/snapshots/native-abc123/compact.yml
  full: .appium-cli/snapshots/native-abc123/full.yml
  refs: .appium-cli/snapshots/native-abc123/refs.json
  index: .appium-cli/snapshots/native-abc123/index.json
  meta: .appium-cli/snapshots/native-abc123/meta.json

LLM が UI の詳細を知りたいときは、snapshot_searchsnapshot_refssnapshot_show --ref=xxx といった抽出コマンドで必要な部分だけ取り出せます。

Native の場合

Native スナップショットは、Appium が返す UIAutomator XML(Android)を元にしています。

appium-cli snapshot

内部では以下の 7 段パイプラインで XML を処理します:

  1. Parse — Appium の getPageSource で XML を取得・パース
  2. Infer semantics — Android のクラス名(android.widget.Button 等)からロール(buttontextbox 等)を推定
  3. Prune / Collapse — 不可視要素の除去、意味のないラッパーの折り畳み
  4. Detect containersRecyclerViewScrollView 等のスクロール可能コンテナを検出
  5. Assign refsresource-idcontent-desc から安定した ref 名を生成
  6. Action targets — タップ可能な要素にアクション対象を設定
  7. Locator strategies — ref ごとに複数のロケータ戦略(ID、accessibility_id、xpath、座標)を生成

生成されるコンパクト YAML はこのような形式です:

screen: Settings
screen_id: a3b2c1...

- heading "Settings" [ref:settings_title]
- list [ref:settings_list] [scrollable:vertical]
  - row "Wi-Fi" [ref:wifi] 
    - text "Connected"
  - row "Bluetooth" [ref:bluetooth]
    - text "Off"
  - row "Display" [ref:display]
- button "Search" [ref:search_button]

alerts: none
nav: back, home, recent

ref 名は要素の属性から導出されます:

  • resource-id="com.android.settings:id/wifi"wifi
  • content-desc="Search"search_button
  • 重複する resource-id には _2_3 のサフィックスが付きます

Web の場合

Web スナップショット(Chrome / WebView)は、DOM を元にしています。

appium-cli web_snapshot

DOM の取得は 2 段階です:

  1. JavaScript 抽出(優先) — ページ内で JS を実行し、アクセシビリティツリーを構築
  2. HTML パーサーフォールバック — JS が使えない場合は HTML を直接パース

Web の ref 名は HTML/ARIA 属性から導出されます:

  • id="login-form"web_login_form
  • data-testid="submit-btn"web_submit_btn
  • aria-label="Search"web_search
  • <a> タグのテキスト → web_link_xxx

ロケータ戦略も Web に特化しています:

  • CSS selector(最優先)
  • link text / partial link text
  • xpath
  • tag name
  • 座標(フォールバック)

2 つの違いはなぜ発生するのか?

Native と Web でスナップショットの仕組みが異なるのは、元データの構造が根本的に違うからです。

観点 Native Web
元データ UIAutomator XML DOM / HTML
要素の識別 resource-idcontent-desc iddata-testidaria-label、CSS
ロール推定 Android クラス名から推定 HTML タグ + ARIA ロールから取得
コンテナ RecyclerViewScrollView 等を自動検出 <ul><table><form>
スクロール ネイティブスクロール方向を検出 ブラウザ標準スクロール
ref プレフィックス なし(loginsearch web_ 付き(web_loginweb_search

しかし、LLM から見た使い方は同じです。snapshotweb_snapshot を呼べばアーティファクトが生成され、返った ref を使って taptype_textscroll_down で操作できます。内部のスナップショット方式の違いは appium-cli が吸収します。

典型的な使い方

LLM エージェントが appium-cli を使う典型的なワークフローは、「観察 → 判断 → 操作 → 確認」 のループです。

Snapshot で取得できる内容

appium-cli snapshot は、画面上のすべてのインタラクティブ要素をアクセシビリティツリーとして返します。各要素には以下の情報が含まれます:

  • ロール(button、textbox、link、heading 等)
  • 名前(ボタンのラベル、テキストフィールドのヒント等)
  • ref(操作に使う安定した識別子)
  • 状態(checked、disabled、selected 等)
  • (テキストフィールドの入力値等)

LLM の解釈と操作の流れ

具体的な例で説明します。LLM が「Settings アプリで Wi-Fi をオンにする」タスクを実行する場合:

ステップ 1: appium-cli snapshot → メタデータが返る

ステップ 2: appium-cli snapshot_refs latest --role=switch → スイッチ要素の一覧を取得

wifi_switch: switch "Wi-Fi" [off]
bluetooth_switch: switch "Bluetooth" [on]

ステップ 3: LLM が判断 — 「wifi_switch が off なので tap する」

ステップ 4: appium-cli tap wifi_switch → タップ実行

ステップ 5: 操作後に自動的にスナップショットが取られ、結果を確認

このように、LLM はテキストベースの UI 情報だけで判断と操作を行えるため、スクリーンショット画像の解析(Vision API)が不要です。トークン効率がさらに良くなります。


機能一覧

appium-cli は以下のコマンドグループを提供します。

環境・ライフサイクル

コマンド 説明
doctor 環境診断(Node.js、Appium、SDK 等の状態確認)
devices 接続デバイスの列挙
server start/stop/status Appium サーバーの起動・停止・状態確認
session start/stop/status WebDriver セッションの開始・終了・状態確認

観察(Observation)

コマンド 説明
snapshot ネイティブ UI のアクセシビリティスナップショット
web_snapshot WebView / Chrome の DOM スナップショット
snapshot_search "text" スナップショット内のテキスト検索
snapshot_refs アクション可能な ref の一覧
snapshot_show --ref=xxx 特定 ref の詳細表示
describe ref 要素の詳細情報
find_by_text "text" テキストで要素を検索
screenshot スクリーンショット画像の取得
web_query "selector" CSS セレクタで Web 要素を検索
generate_locator ref ref のロケータ戦略を表示

基本アクション

コマンド 説明
tap ref 要素をタップ
type_text ref "text" テキスト入力
click ref Web 要素のクリック
fill ref "text" Web フォーム要素への入力
scroll_down [ref] 下にスクロール(ref 省略で全画面)
scroll_up [ref] 上にスクロール
swipe_left [ref] 左にスワイプ
press_key back キー入力(back、home、enter 等)
wait 2 指定秒数待機
wait_for --text "Welcome" テキスト出現まで待機

ジェスチャー

コマンド 説明
long_press ref 長押し
double_tap ref ダブルタップ
drag ref x y ドラッグ
fling_up [ref] 高速フリック
pinch_open ref ピンチオープン(拡大)
pinch_close ref ピンチクローズ(縮小)

アプリ管理・デバイス情報

コマンド 説明
get_device_info デバイス情報の取得
get_current_app 現在のアプリを取得
activate_app pkg アプリの起動
terminate_app pkg アプリの終了
list_apps インストール済みアプリ一覧
is_locked 画面ロック状態の確認
get_orientation 画面の向きを取得

WebView / Chrome 操作

コマンド 説明
list_contexts 利用可能なコンテキスト一覧
webview_switch WebView コンテキストに切替
native_switch ネイティブコンテキストに戻る
goto "url" URL に遷移
tabs list/switch/new/close タブ操作
web_eval "js" ref JavaScript 実行
console_messages ブラウザコンソールログ

コンテナ・検証

コマンド 説明
list_containers コンテナ要素の一覧
find_container "name" コンテナの検索
within_container ref コンテナ内の要素一覧
assert_visible --text "text" テキストの表示確認

動作例

ここでは、appium-cli を使って Android 端末上の Chrome ブラウザで Web サイトを操作する 2 つの実例を紹介します。いずれも AI エージェント(Claude Sonnet 4.6)が自律的に実行した結果です。

Test 1: GitHub VSCode Issues の自動操作

タスク: GitHub の microsoft/vscode リポジトリの Issues ページを開き、以下を自動実行する

  1. Issues 一覧ページを開く
  2. スナップショットを取り、最初の 5 件の Issue タイトルを抽出
  3. label:bug でフィルタする
  4. フィルタ後のスナップショットから Bug Issue 5 件のタイトルを抽出
  5. 最初の Bug Issue をクリックして詳細を開く
  6. 詳細ページのスナップショットからタイトル・作成者・ラベルを取得
  7. 結果をまとめて報告

テスト結果

LLM エージェントは以下の手順を自律的に実行し、結果を構造化して報告しました

ブラウザの画面:

test2_ブラウザ終了時の画面.png

LLM エージェントの出力結果:

📊 実行結果レポート

1. Issues一覧ページ

URL: https://github.com/microsoft/vscode/issues

2. 最初に表示されるIssue 5件(フィルタなし)

┌───┬────────────────────────────────────────────────────────────────────┬───────────┐
│ # │ タイトル                                                           │ Issue番号 │
├───┼────────────────────────────────────────────────────────────────────┼───────────┤
│ 1 │ Copilot is unaware of deferred tools schema, makes them uncallable │ #315526   │
├───┼────────────────────────────────────────────────────────────────────┼───────────┤
│ 2 │ bag                                                                │ #315524   │
├───┼────────────────────────────────────────────────────────────────────┼───────────┤
│ 3 │ Feature Request: Custom Background Colors for Chat Sessions        │ #315522   │
├───┼────────────────────────────────────────────────────────────────────┼───────────┤
│ 4 │ Ability to restore selection in explorer                           │ #315520   │
├───┼────────────────────────────────────────────────────────────────────┼───────────┤
│ 5 │ Auto Updates Problems                                              │ #315519   │
└───┴────────────────────────────────────────────────────────────────────┴───────────┘

3. label:bug でフィルタ

URL: https://github.com/microsoft/vscode/issues?q=is%3Aissue+is%3Aopen+label%3Abug

4. Bug Issue 5件(フィルタ後)

┌───┬────────────────────────────────────────────────────────────────────────────────────────┬───────────┐
│ # │ タイトル                                                                               │ Issue番号 │
├───┼────────────────────────────────────────────────────────────────────────────────────────┼───────────┤
│ 1 │ Better indication when a user is in overage                                            │ #315379   │
├───┼────────────────────────────────────────────────────────────────────────────────────────┼───────────┤
│ 2 │ Suggest widget doesn't work at all when inline suggestion from Copilot are enabled     │ #315373   │
├───┼────────────────────────────────────────────────────────────────────────────────────────┼───────────┤
│ 3 │ agent sessions: spinning if commit button causes error                                 │ #315363   │
├───┼────────────────────────────────────────────────────────────────────────────────────────┼───────────┤
│ 4 │ In chat warning shows in every new window                                              │ #315361   │
├───┼────────────────────────────────────────────────────────────────────────────────────────┼───────────┤
│ 5 │ Spurious 'this workspace requires agent plugin' notification with Claude model (local) │ #315347   │
└───┴────────────────────────────────────────────────────────────────────────────────────────┴───────────┘

5 & 6. 最初のBug Issue 詳細

🔗 URL: https://github.com/microsoft/vscode/issues/315379

┌──────────────┬──────────────────────────────────────────────────────────────────────────────┐
│ 項目         │ 内容                                                                         │
├──────────────┼──────────────────────────────────────────────────────────────────────────────┤
│ タイトル     │ Better indication when a user is in overage #315379                          │
├──────────────┼──────────────────────────────────────────────────────────────────────────────┤
│ 作成者       │ cwebster-99                                                                  │
├──────────────┼──────────────────────────────────────────────────────────────────────────────┤
│ ラベル       │ bug(Issue identified by VS Code Team member as probable bug)、              │
│              │ UBB(Issues related to UBB)                                                  │
└──────────────┴──────────────────────────────────────────────────────────────────────────────┘

実行の流れ:

LLM は appium-cli web_snapshot で Web ページのアクセシビリティツリーを取得し、テキスト情報からIssueタイトルやラベルを読み取りました。label:bug フィルタは URL パラメータを構築して appium-cli goto でナビゲーションしています。Issue 詳細のクリックは appium-cli tap で ref を指定して実行しました。

すべての操作がスナップショットの ref ベースで行われており、座標指定やスクリーンショット解析は一切使っていません。

Test 2: DemoQA フォーム自動入力と検証

タスク: https://demoqa.com/automation-practice-form を開き、以下を自動実行する

  1. ページを開きスナップショットを取る
  2. 全フィールドを入力する(名前、メール、性別、電話番号、生年月日、科目、趣味、住所、州、市)
  3. 各入力後にスナップショットで入力値を確認する
  4. Submit ボタンを押す
  5. 送信後のモーダルのスナップショットを取り、表示内容を読み取る
  6. 入力値と確認内容が一致するか検証して報告する

テスト結果

LLM エージェントは全 10 項目のフォーム入力を正確に行い、Submit 後のモーダル内容と入力値の完全一致を確認しました。

ブラウザの画面:

test1_テスト終了時の画面.png

LLM エージェントの出力結果:

実行結果報告

ステップ1〜3: フォーム入力と確認

すべてのフィールドを正常に入力し、入力値をスナップショットで確認しました。

ステップ4: Submit完了

ステップ5〜6: モーダル確認内容と入力値の検証

┌────────────────┬──────────────────┬──────────────────┬──────┐
│ Label          │ 入力値           │ モーダル確認値   │ 一致 │
├────────────────┼──────────────────┼──────────────────┼──────┤
│ Student Name   │ Taro Yamada      │ Taro Yamada      │ ✅   │
├────────────────┼──────────────────┼──────────────────┼──────┤
│ Student Email  │ taro@example.com │ taro@example.com │ ✅   │
│                │ (                │ (                │      │
│                │ mailto:taro@exam │ mailto:taro@exam │      │
│                │ ple.com)         │ ple.com)         │      │
├────────────────┼──────────────────┼──────────────────┼──────┤
│ Gender         │ Male             │ Male             │ ✅   │
├────────────────┼──────────────────┼──────────────────┼──────┤
│ Mobile         │ 0901234567       │ 0901234567       │ ✅   │
├────────────────┼──────────────────┼──────────────────┼──────┤
│ Date of Birth  │ 15 May 1990      │ 15 May,1990      │ ✅   │
├────────────────┼──────────────────┼──────────────────┼──────┤
│ Subjects       │ Computer Science │ Computer Science │ ✅   │
├────────────────┼──────────────────┼──────────────────┼──────┤
│ Hobbies        │ Sports           │ Sports           │ ✅   │
├────────────────┼──────────────────┼──────────────────┼──────┤
│ Picture        │ (未入力)         │ (空欄)           │ ✅   │
├────────────────┼──────────────────┼──────────────────┼──────┤
│ Address        │ Tokyo, Japan     │ Tokyo, Japan     │ ✅   │
├────────────────┼──────────────────┼──────────────────┼──────┤
│ State and City │ NCR / Delhi      │ NCR Delhi        │ ✅   │
└────────────────┴──────────────────┴──────────────────┴──────┘

全10項目が一致。入力値とモーダルの確認内容は完全に一致しています。

 ※ Date of Birthはモーダルに 15 May,1990(カンマあり)と表示されますが、
   これはdemoqaサイト側の表示フォーマットであり、日付値 15 May 1990 と同一です。

実行の流れ:

このテストは appium-cli の Web 操作能力を総合的に検証するものです。LLM は以下のように各種コマンドを使い分けました:

  • web_snapshot : フォームの構造を把握し、入力対象の ref を特定
  • fill ref "text" : テキストフィールドへの入力
  • click ref : ラジオボタン(Gender: Male)やチェックボックス(Hobbies: Sports)の選択
  • fill ref "text" --slowly : React Select コンポーネント(Subjects)は 1 文字ずつ入力してサジェスト表示を待ち、候補をクリック
  • select_option ref "value" : ドロップダウン(State, City)の選択
  • tap ref : Submit ボタンの送信
  • web_snapshot(送信後) : モーダルの内容を読み取り、入力値と照合

特に注目すべきは、日付ピッカーや React Select のような複雑な UI コンポーネントも、スナップショットで状態を確認しながら操作できている点です。

まとめ

CLI アプローチの利点

appium-cli は「MCP ではなく CLI」という設計判断により、以下の利点を実現しています:

  1. トークン効率 — UI ツリーをファイルに書き出し、LLM のコンテキストにはメタデータのみ返す。必要な情報だけをピンポイントで取得できる。

  2. シンプルなインターフェースappium-cli snapshotappium-cli tap ref という直感的な CLI コマンド。MCP のプロトコル層が不要。

  3. エージェント非依存 — 特定の LLM フレームワークに依存しない。シェルコマンドが実行できるエージェントなら何でも使える(Claude Code、Copilot CLI、GPT Agent 等)。

  4. デバッグ容易性 — すべてのスナップショットがファイルとして残るため、操作の再現や問題の特定が容易。

Playwright MCP との比較

Playwright MCP(Web ブラウザ自動化)と appium-cli(モバイル自動化)は、同じ「LLM エージェントによる UI 自動化」の異なるアプローチです。

観点 Playwright MCP appium-cli
プロトコル MCP(Model Context Protocol) CLI(シェルコマンド)
対象 Web ブラウザ Android(+ WebView / Chrome)
UI ツリー コンテキストに直接返す ファイルに保存、メタデータのみ返す
トークン効率 ツリーサイズに比例して消費 ほぼ一定(メタデータ数行)
要素指定 スナップショットの ref スナップショットの ref

ソースコード

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?