limrun-android-emulator について
limrun-android-emulator は、Linux、Windows、macOS、仮想マシン、コンテナーから Limrun のクラウド Android エミュレーター上のアプリを導入・操作するスキルです。アプリ操作、ログ、ファイル転送、音声入力、ネットワーク制限、通信検査を扱い、ビルドは別のスキルが担います。
主な機能
- 01ファイル、URL、アセットから APK を導入
- 02インストール済みアプリの起動、同期、操作
- 03UI ツリー、スクリーンショット、録画の確認
- 04アプリログの取得とファイル転送
- 05マイク入力の再現と Wi-Fi 帯域制限
- 06トンネル経由のアプリ通信検査
活用シーン
- 01クラウドエミュレーターでの Android アプリテスト
- 02更新後のアプリ UI 動作確認
- 03ログやアプリ出力によるクラッシュ調査
- 04音声入力と制限付きネットワークのテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ limrun-inc/ skills --skill limrun-android-emulatorインストール後、Claude Code で/limrun-android-emulatorを入力して読み込まれたか確認します。
詳細
Limrun Android エミュレーター
Linux、Windows、macOS、VM、コンテナーなど、あらゆる環境から、Limrun のクラウド Android エミュレーターで実行中のアプリを操作します。このスキルはビルド方式に依存しません。APK は通常 limrun-gradle によって、すでにビルドされていることを前提とします。ビルドに関する内容はそのスキルで扱い、このスキルでは実行中のエミュレーターを操作します。
ローカルエミュレーター、ローカル Android SDK、Android Studio は使用しないでください。
認証と CLI
必要に応じてインストールします:npm install --global lim。認証には lim login または
LIM_API_KEY を使用します(ユーザーの環境ですでに設定されていて、.env やシェルには表示されない場合があります。値を尋ねる前に確認してください)。CLI が正確な情報源です:
このスキル内のコマンドは検証済みですが、フラグでエラーが発生した場合や、ここに記載されていないフラグが必要な場合は、推測せずに lim android <subcommand> --help を確認してください。
アプリのインストール
Limrun のリモート Gradle サービスでビルドして、起動したばかりのエミュレーターに APK を自動インストールできます。または、ビルド済みのローカル APK や URL からインストールできます。まず知っておくべきデフォルト動作がひとつあります:lim android create は、別途指定がない限り、ADB トンネル(--connect)を開き、ライブストリームを表示するブラウザータブ(--open)を開きます。エージェントとして実行する場合は、常に --no-open を指定し、すぐに adb が必要な場合を除いて --no-connect を指定してください。
ビルドとインストール
limrun-gradle でビルドした APK を名前付きアセットとしてアップロードし、アプリをプリインストールした状態でエミュレーターを作成します:
lim gradle build . --upload myapp.apk
lim android create --install-asset myapp.apk --no-open --no-connect
作成コマンドは、インスタンスの操作準備が整うまで待機するため、起動を待つ必要はありません。作成結果には署名付きストリーム URL が含まれます。[Live emulator](<signed-stream-url>) のような Markdown リンクにして、ユーザーと共有してください。ユーザーに見えるブラウザーがあれば、そこで URL を開いて伝えてください。作成結果にはコンソール URL も表示されます。同じライブビューを開きますが、コンソールへのログインが必要なため、共有には署名付きストリーム URL を優先してください。
署名付きリンクを開くと、ログインなしで共有デバイスのワークスペースにアクセスできます。インスタンス全体のトークンでは、Inspect、Connect、およびログ、イベント、ネットワークインスペクターが表示されます。ストリーム専用トークンでは、これらのツールは表示されず、画面と入力コントロールのみが表示されます。停止とアプリの選択には、認証済みのコンソールが必要です。
便利な作成フラグ:--reuse-if-exists(同じラベルの実行中インスタンスを再利用)、--rm(CLI の終了時に削除)、--jurisdiction us|eu|as(インスタンスの実行場所。非推奨のため --region は使用しないでください)、--inactivity-timeout / --hard-timeout、--display-name、--label k=v(lim android list --label-selector k=v で後からラベル付きインスタンスを検索できます)。
ローカルからアプリをインストール
新しいエミュレーターを作成してから、ローカルファイルまたは URL をインストールします:
lim android create --no-open --no-connect
lim android install-app ./app-debug.apk
lim android install-app https://example.com/app.apk
ローカルパスの場合は、まず Limrun Asset Storage にアップロードされます。URL の場合はインスタンス自体が取得するため、大きな APK ではマシンからアップロードするよりはるかに高速です。install-app はアプリの送信直後に終了し、インストールは数秒以内にバックグラウンドで完了します。新しくインストールしたアプリはホーム画面ではなくアプリドロワーに表示されるため、アイコンを探さないでください。lim android launch-app <package> --detach でアプリを起動します(--detach を付けない場合は、アプリが終了するまで監視して待機します)。または、lim android adb-shell -- sh -c "pm list packages | grep <name>" で確認します。
APK の新しいバージョンをインストールする必要がある場合は、再インストールではなく同期してください:
lim android sync ./app-debug.apk
既存のインスタンスにある APK との差分のみを送信してから、再インストールします。--watch を指定すると、ファイルの変更時に同期が繰り返されます。--launch-mode ForegroundIfRunning|RelaunchIfRunning では、インストールのたびに実行中のアプリをどう扱うかを制御します。
適切なインスタンスを指定する
ほとんどの lim android コマンドは、デフォルトで最後に作成されたインスタンスを対象とし、cwd の git リポジトリ/ワークツリーから「現在の」インスタンスを特定します。別のディレクトリ(または git リポジトリ外)では、インスタンスが実行中でも、最近のインスタンスが見つからないとコマンドに表示されることがあります。確実な方法は次のとおりです。
lim android list # shows all instances and their IDs
lim android element-tree --id <that-id> # pass --id to EVERY lim android command
ID(形式は android_<region>_<ulid>)を確認したら、セッションの残りの間、すべての lim android 呼び出しに --id <android-instance-id> を指定してください。または、プロジェクトで git init を実行すると、ワークスペースが自動的に解決されます。複数のインスタンスを操作する場合は、必ず --id を指定してください。
アプリを起動する
パッケージ名を指定して、インストール済みアプリを起動および停止します。
lim android launch-app com.example.app --detach # launch and return
lim android launch-app com.example.app # launch and watch until it exits
lim android launch-app com.example.app --mode RelaunchIfRunning # restart for a clean state
lim android terminate-app com.example.app # stop it, e.g. to reset app state
--detach を指定しない場合、launch-app はアプリを監視するためにブロックします。アプリがクラッシュした場合、ANR が発生した場合、または停止された場合、コマンドは終了理由、スタックトレース付きのクラッシュ詳細、および最近のアプリログの末尾を表示してから終了します。このレポートを使えば、adb を使わずにアプリが終了した理由を確認できます。アプリの実行中にログを確認するには、lim android app-log(以下を参照)を使います。list-apps はありません。パッケージ名は lim android adb-shell -- pm list packages で確認するか、ビルドからアプリケーション ID を取得してください。
アプリログ
1 つのアプリのログを確認するのに、トンネルは必要ありません。
lim android app-log com.example.app --tail 100 # recent lines (app must be running)
lim android app-log com.example.app --follow # stream live lines until Ctrl+C; don't stream into context
シェルとファイル
単発のシェルコマンドやファイル転送にも、トンネルは必要ありません。
lim android adb-shell -- pm list packages -3 # like adb shell; args go after --
lim android adb-shell -- sh -c "dumpsys battery | grep level" # pipes need an explicit shell
lim android push-file ./fixture.json /sdcard/Download/fixture.json
lim android pull-file /sdcard/Download/out.json ./out.json
これらは adb シェルユーザーと同じ権限で実行され、adb-shell はコマンドの終了コードを返します。
トンネル経由の完全な logcat と対話型 adb
デバイス全体の logcat や、対話型の操作(Android Studio、scrcpy、ストリーミング)には、CLI のトンネル経由で通常の adb を使用します。バックグラウンドシェルでトンネルを起動し、そのまま稼働させておきます。
lim android connect # prints "Tunnel started on 127.0.0.1:<port>."
connect は adb connect を実行します(adb が PATH にない場合は --adb-path を使用してください)。表示される 127.0.0.1:<port> はデバイスのシリアル番号です。すべての adb 呼び出しに -s を付けて指定してください(adb devices にも表示されます)。
SERIAL=127.0.0.1:<port>
adb -s $SERIAL logcat -d | tail -100 # dump recent full-device logs, don't stream into context
トンネルは起動したプロセスとともに終了します。シェルを終了すると、インスタンスは実行し続けますが、adb devices ではシリアル番号が offline と表示されます。lim android connect を再度実行すると、新しいトンネルを取得できます(ポートは実行ごとに変わります)。古いオフラインのシリアル番号は問題ありません。adb disconnect で削除できます。
変更をテストする
タスクにエミュレーターの操作が含まれる場合は、インストールまたは同期のたびに、操作コマンドを使って新規または変更した機能をテストしてください。変更点に加えて、コアとなるフローも簡単にスモークテストします。操作を始める前に、まず要素ツリーを読み取り、画面の表示内容を確認してください。
lim android element-tree
出力は 1 行にまとめられた UIAutomator の XML 階層そのものなので、通常の grep ではドキュメント全体が表示されます。まず 1 ノードずつの行に分割してから、ツリー全体をコンテキストに出力するのではなく、必要な text、resource-id、content-desc、または bounds を grep してください。
lim android element-tree | sed 's/></>\n</g' | grep -i "save"
アプリの操作
まずリソース ID でタップし、次に表示テキストまたはコンテンツ説明を使い、 最後の手段として座標を使います。
lim android tap-element --resource-id com.example.app:id/startButton
lim android tap-element --text "Save"
lim android tap-element --content-desc "Open menu"
lim android tap 360 800
セレクターの値は部分一致ではなく、完全一致します。--text "Save" は
「Save draft」ボタンには一致しません。値は element-tree からそのままコピーしてください。
一致する要素がないセレクターは、数秒後に No element found for selector で失敗します。
その他のセレクターは、--class-name、--package-name、--index、--clickable、
--enabled、--focused、--bounds-contains-x/y です。これらを組み合わせて、
一致対象を絞り込めます。ブラウザー内の Web ページでは、リソース ID はページ独自の
DOM ID(searchIcon など)で、多くの場合空です。その場合は --text と
--class-name で選択するか、ノードの bounds に基づく座標を使います。タップせずに
一致する要素を調べるには、同じセレクターを指定して find-element を使います。
lim android find-element --text "Save" # table of matches with bounds
テキストを入力する際は、対象のフィールドを直接指定します。事前にフォーカスをタップする
必要はありません。type は tap-element と同じセレクターを受け取ります(リソース ID
のないフィールドには --class-name android.widget.EditText --focused が使えます)。
lim android type "hello world" --resource-id com.example.app:id/searchBox
lim android type "hello" --x 360 --y 400 # by coordinate
lim android press-key enter
lim android press-key backspace # --modifier shift/control/alt/command to combine
スクロールとナビゲーションについては、次のとおりです。
lim android scroll down --amount 600
lim android scroll up --amount 300
lim android press-key back
lim android press-key home
lim android open-url "https://example.com" # opens in the default browser; also fires deep links
操作のたびに element-tree を再実行して、UI の状態が遷移したことを確認してください。
タップと element-tree の間にスリープを入れる必要はありません。タップ処理は完了するまで
ブロックされます。ただし、open-url 後のページ読み込みは非同期です。期待するノードが
表示されるまで element-tree を再実行してください。子要素のない
android.webkit.WebView が表示されていても、ツリーの破損ではなく、ページの読み込み中です。
lim android element-tree
要素ツリーが空の場合
一部の React Native アプリや Expo アプリでは、アクセシビリティノードが一切公開されないため、
element-tree、tap-element、find-element では確認できません(システムダイアログでは
引き続きノードが公開されます)。その場合はピクセルを使って操作します。スクリーンショットを
撮り、対象の座標を確認してから、tap x y / type --x --y を使ってください。
スクリーンショットのピクセルとタップ座標は 1:1 で対応するため、画像内で(360, 1322)に
中央があるボタンは、lim android tap 360 1322 でタップできます。各操作の後に
スクリーンショットを撮り直して、結果を確認してください。
スクリーンショットと動画
スクリーンショットでは、-o ではなく位置引数でパスを指定します。
lim android screenshot screenshot.png
lim android screenshot screenshot.png --id <android-instance-id>
機能に関するアサーション(要素の有無、テキスト、状態変化)には要素ツリーを使い、 視覚的な特性の確認にのみスクリーンショットを使ってください。アニメーション、ゲームプレイ、 ストリーミング UI など、動きに関するものには動画を使うことをおすすめします。
lim android record start # non-blocking
lim android record stop -o /tmp/recording.mp4
record stop は --quality 5-10 を受け付けます。録画したフレームは
スクリーンショットの解像度の半分です。そのため、タップ座標は動画フレームではなく、
スクリーンショットから読み取ってください。UI の変更については、ユーザーが確認できるよう、
プルリクエストにデモ動画を含めてください。
音声ファイルでマイクをシミュレートする
音声による操作フロー(アシスタント、音声テキスト変換、音声通話)では、ローカルの 音声ファイルをエミュレーターのマイクとして再生します。アプリは通常のキャプチャ パイプラインを通じて音声を受け取ります。
lim android play-on-microphone ./fixtures/command.wav # loops by default
lim android play-on-microphone ./fixtures/command.mp3 --once
WAV と MP3 が使えます。ファイルは adb 経由で転送されるため、このコマンドにはローカルの
adb バイナリが必要です(PATH にない場合は --adb-path を指定してください)。また、
コマンドは短時間だけ有効なトンネルを独自に開きます。カメラ入力の注入は iOS 専用です。
カメラを使うテストフローには limrun-ios-simulator を使用してください。
ネットワーク帯域幅を調整する
インスタンスの Wi-Fi 帯域幅に上限を設定して、低速ネットワークでの動作をテストします。
lim android set-wifi-bandwidth --down-kbps 1000 --up-kbps 500
lim android set-wifi-bandwidth --down-kbps 0 --up-kbps 0 # 0 clears the limit
アプリのトラフィックをマシン経由でトンネルする
アプリから、マシンからのみアクセスできるサービス(ローカルの開発サーバーや、VPN 経由でのみアクセスできるステージング API など)に接続する必要がある場合や、HTTP トラフィックを確認したい場合は、宛先トンネルを開始します。選択した宛先のみが、lim を実行しているマシン経由に迂回されます。それ以外のトラフィックは、インスタンスから直接送信されます。
lim android tunnel --selector localhost:8080 --detach --id <android-instance-id>
- 完全一致セレクター(
localhost:portまたはIP:port、ポートは 1024 以上)はエミュレーター上のリスナーになり、10.0.2.2:<port>からもアクセスできます。アプリからの接続はマシンに到達し、そこで接続先にダイヤルされます。 - ドメインセレクター(
api.example.com、"*.corp.example")はエミュレーター上でインターセプトされ、マシンからダイヤルされるため、マシンの DNS と VPN が適用されます。アプリが HTTPS 経由で独自に DNS を解決する場合、ドメインのインターセプトは適用されません。 - トンネルはアプリの起動前に開始してください。それ以前に開かれた接続は、元のルートを維持します。インスタンスごとにトンネルは 1 つのみです。2 つ目を開始すると失敗します。
エージェントとして、必ず --detach を渡してください。トンネルが READY になるとコマンドが戻り、バックグラウンドプロセスでトンネルが実行され続けます。次のコマンドで管理できます。
lim android tunnel status --id <android-instance-id> # state, per-selector binds, last dial failure
lim android tunnel stop --id <android-instance-id>
HTTP トラフィックの検査、HAR のキャプチャ、ネットワークログの保存
検査はデフォルトで有効です。トンネル経由のすべての HTTP および HTTPS リクエストがデコードされ、リクエストごとに 1 行の要約が出力されます(デタッチ時はトンネルのログファイルに出力されます)。また、コンソールのネットワークパネルにリアルタイムで表示されます。
lim android tunnel --selector "*.api.example" --har ./traffic.har --detach # write HAR 1.2 with bodies
lim android tunnel --selector "*.api.example" --persist --detach # network log survives the instance
--persist を指定すると、トンネル停止時またはインスタンス終了時に、本文を含むネットワークログがセッションアーティファクトとしてアップロードされます。コンソールのインスタンスのセッションページに表示され、HAR をダウンロードできます(デフォルトの保存期間は 3 日、--ttl <seconds> で最長 30 日まで指定できます)。HTTPS はエミュレーターが信頼する CA を使ってデコードされるため、証明書ピンニングを使用するアプリは、検査対象のドメインセレクターを経由すると失敗します。ピンニングされたホストをセレクターから除外するか、--no-inspect を渡してバイト列をそのまま中継してください(要約、HAR、保存は行われません)。
プライベートトンネル経由でプライベートネットワーク内のサービスにアクセスする
組織はプライベートトンネルを実行できます。これはネットワーク内のコネクターで、管理者が lim tunnel create またはコンソールの Network から作成し、lim tunnel run で実行します。このトンネルを指定するすべてのインスタンスで利用できます。ユーザーがトンネル名を指定した場合は、自分でトンネルを開始する代わりに、作成時にその名前を渡してください。
lim android create --tunnel <tunnel-name>
アプリは、指定されたアドレスでトンネルのセレクターにアクセスできます。また、完全一致セレクターには 10.0.2.2:<port> からもアクセスできます。セレクターはあなたのマシンではなく、コネクターのマシン上で解決されます。Android インスタンスが接続されるのは、すべての完全一致セレクターでポートが 1024 以上の場合のみです。このトンネルがインスタンスの唯一の宛先トンネルになるため、そのインスタンス上で lim android tunnel を開始したり停止したりしないでください。
作成時にトンネルが検証され、利用できない場合は失敗します。
tunnel <name> does not exist:名前が間違っているか、トンネルが作成されていません。正しい名前をユーザーに確認してください。tunnel <name> is offline:コネクターが実行されていません。起動するようユーザーに依頼してください。did not attach within 30s:コネクターがインスタンスに接続できないか、完全一致セレクターで 1024 未満のポートが使われています。コネクターの出力を確認するか、サービスを 1024 以上のポートに移すようユーザーに依頼してください。
いずれの場合も、作成をループで再試行しても解決しません。
トンネルのトークンはコネクターのマシンに置いてください。ユーザーに会話へ貼り付けるよう求めてはいけません。
すべてのデバイストラフィックをプロキシ経由で送信する
トンネルが通すのは宣言された宛先のみです。すべての宛先を対象にするセレクターはありません。ユーザーがすべてのトラフィックをプロキシ経由で送信したい場合(録画用プロキシなど)は、エミュレーターのグローバル HTTP プロキシを、トンネルから到達できるプロキシに設定します。トンネルのインスペクションでは、プロキシ宛てのリクエストはそのまま送信されます。localhost のプロキシには 8080 以外のポートを使用してください。エミュレーターはすでに 8080 を使用しています。
lim android tunnel --selector localhost:3128 --detach --id <android-instance-id>
lim android proxy set localhost:3128 --id <android-instance-id>
# later, before stopping the tunnel:
lim android proxy clear --id <android-instance-id>
セレクターがプロキシになっているプライベートトンネルでは、lim android proxy set のみが必要です。ユーザーのネットワーク内の別の場所にあるプロキシも同様に動作します。
--selector proxy.example.internal と lim android proxy set proxy.example.internal:3128 を指定します。プロキシが設定されていてトンネルが停止している間、Chrome のリクエストは直接送信されず、失敗します。プロキシで HTTPS を読み取るには、その CA を信頼するように設定してください(下記参照)。
CA 証明書を信頼する
エミュレーターの信頼ストアに PEM CA 証明書を追加します。たとえば、ユーザーのテストサーバーに署名するプライベート CA のルート証明書を追加できます。
lim android ca add ./my-ca.pem --id <android-instance-id>
アプリ、WebView、Chrome は、再起動しなくても、インスタンスが存続する間、次回の接続からその証明書を信頼します。1 つのファイルにつき CA 証明書は 1 つのみです。リーフ証明書は拒否されます。証明書をピン留めしているアプリでは、引き続き拒否されます。アプリの HTTP および HTTPS トラフィックを確認するには、代わりにトンネルのインスペクション(上記)を使用してください。ユーザー独自の CA は必要ありません。
人が使うプレビュー URL
APK を Limrun Asset Storage にアップロードし、ユーザーがブラウザーで開いてアプリを手動でテストできるプレビュー URL を返します。
export ASSET_NAME=myapp.apk # can be any name
lim asset push ./app-debug.apk -n ${ASSET_NAME}
echo "https://console.limrun.com/preview?asset=${ASSET_NAME}&platform=android"
Limrun コンソールでリンクを開くと、エミュレーターが起動し、コンソールが APK をインストールする間にストリーミングが始まります。インストール中は Installing <asset name> オーバーレイが表示されます。起動後に URL またはアプリのディープリンクを開くには openUrl を追加します。たとえば、&openUrl=myapp%3A%2F%2Fcheckout のように指定します。完全な URL とそのクエリ文字列をエンコードするには、URLSearchParams.set("openUrl", url) を使用します。アプリのインストール、起動、URL の表示中もストリームは表示されたままです。プレビューの準備が整うと、オーバーレイが消えます。
クリーンアップ
作業が完了したら、エミュレーターを削除できます。delete は位置引数の ID を受け取ります(ほかのコマンドとは異なり、ここでは --id は有効なフラグではありません)。
lim android delete <android-instance-id>
注意事項
- ABI は混在させず、1 種類にしてください。 エミュレーターは x86_64 ホストで、
x86_64,arm64-v8aを報告し、変換を介して arm64 ネイティブライブラリを実行します。arm64-v8aライブラリのみを含む APK はインストールして実行できます。x86_64 ライブラリはネイティブに実行され、最速です。ベンダー SDK ライブラリの一部が arm64 のみで、残りが x86_64 というように ABI が混在する APK は、x86_64 としてインストールされた後、arm64 のみのコードが最初に読み込まれるとUnsatisfiedLinkErrorでクラッシュします。armeabi-v7aのみの APK はINSTALL_FAILED_NO_MATCHING_ABISで拒否されます。また、同梱された ARM コマンドラインバイナリを自身で exec するアプリは、変換下ではサポートされません。 - セレクターは完全一致です。
tap-element --textとfind-element --textには、element-treeに表示される文字列全体を正確に指定する必要があります。部分一致では何も見つかりません。 install-appはインストール完了前に戻ります。 アプリがインストールされるまで数秒かかります。起動する前に、find-elementまたはpm list packagesで確認してください。生のadb installより、install-app <URL>またはcreate --install-assetを優先してください。大きな APK をadb installで転送すると、進行状況がまったく表示されず、何分も停止しているように見えます。転送中に強制終了すると、インストールが破損します。- ADB トンネルはセッションに紐付いています。 インスタンスは実行し続けますが、トンネルは起動したシェルが終了すると切断されます。
lim android connectで再接続し、ポートを再確認してください。ポートは接続のたびに変わります。 createのパイプが失敗しても、インスタンスが残る場合があります。createの呼び出しがクライアント側のエラー(パイプ切断、JSON の解析エラー)で失敗した場合は、lim android listを確認してください。インスタンスが作成されている可能性があり、その場合は削除してください。- 要素ツリーが空の場合、通常はインスタンスの故障ではなく React Native アプリです。 上記の「要素ツリーが空の場合」を参照してください。
element-treeは大きくなる場合があります。 ツリー全体をコンテキストに出力するのではなく、grepにパイプして必要な情報を抽出してください。- Git 管理されていないディレクトリでは、インスタンスの特定に失敗する場合があります。 上記の「適切なインスタンスを指定する」を参照してください。判断に迷う場合は
--idを渡してください。 - ビルドエラーへの対処はビルドスキルの役割です。 APK をビルドできない場合、失敗の原因は上流にあります。limrun-gradle に戻ってください。