Appium、むずいですよね。
「テストを作りたい」だけなのに、まず「テストが動く環境」を作るところで消耗する感じがあります。
実際、Appium本体だけではなく、OS別のドライバーが必要で、AndroidならSDK・JDK・端末接続の準備まで出てきます。テストを書く前の登場人物が多いんです。
複数OS対応・きれいな共通化・CI連携までいくのは、遠い道のり。。。
この記事は、まずは、「1台の端末で、1つのアプリを起動し、画面の情報を取る」ところを目指します。
Appiumって何なのか
Appiumは、スマホアプリの画面を、人間の代わりにプログラムから操作するためのオープンソースの自動化ツールです。 AndroidとiOSの両方に対応し、アプリの画面操作を伴うテストなどに使います。
公開されたのは2012年、バージョン1.0の登場は2014年5月です。10年以上の歴史があります。
そもそも、何をするもの?
例えば、ログイン機能を確認するとき、人間ならスマホを手に取り、次のように操作します。
アプリを開く
→ ユーザー名を入力する
→ パスワードを入力する
→ ログインボタンを押す
→ ホーム画面が表示されたことを確認する
このような処理をテストコードとして書き、実際のアプリに対して繰り返し実行するための仕組みがAppiumです。画面内の要素を探し、操作し、その状態を取得できます。
ただし、役割は分けて考える必要があります。
| 役割 | 担当するもの |
|---|---|
| 何を、どの順番で操作するかを決める | テストコード |
| アプリの画面を操作し、結果の情報を取得する | Appiumと、そのドライバー |
| 取得した結果が期待どおりか判定する | テストコード・テストフレームワーク |
つまり、Appiumを導入しただけで「何をテストすべきか」「何が正しい結果か」まで自動で決まるわけではありません。 本来は「操作の自動化」を担うもので、テストの実行管理や合否判定は別の仕組みと組み合わせます。
なぜ、いろいろなツールが登場して難しくなるのか?
Appium自身が、AndroidとiOSのすべての操作を直接実装しているわけではないからです。
OSごとの操作は「ドライバー」という部品が担当します。
代表的な構成を単純化すると、こうなります。
あなたが書くテストコード
↓
Appium:共通の操作指示を受け付ける
↓
OSごとのドライバー
├─ Android:UiAutomator2 Driver
└─ iOS :XCUITest Driver
↓
実際のアプリを操作する
AndroidとiOSでは、利用するドライバーも、その下で使われる自動化技術も異なります。Appiumはその違いを共通の操作インターフェースで扱えるようにしますが、OSごとに必要な環境構築まで、すべて同じにしてくれるわけではありません。
最初の関門
あなたが今しんどいのは、環境構築でそもそも動かないところですか? それとも、動くけれど要素取得・画面操作が安定しないところですか?両方ですか?いや、前者だろ。そうなんだろ?
Appiumの環境構築
まずは、「1台の端末で、1つのアプリを起動し、画面の情報を取る」ところを目指します。
環境構築を小さな確認に分けます。
この記事では、macOS+Androidエミュレーター+Pythonを例に進めます。Appiumは3系を使います。以下のコマンドはmacOSのターミナル向けです。
まず、登場人物を整理する
名前が多いので、役割を先に並べておきます。
| 用意するもの | 今回の役割 |
|---|---|
| Node.js・npm | Appiumをインストールし、動かす |
| Appium | テストコードからの操作指示を受け付ける |
| UiAutomator2 Driver | Androidを操作する |
| Android SDK・JDK | Androidとの接続や、自動化に必要なツールを提供する |
| Androidエミュレーター | 操作対象となる仮想端末 |
| Appium Inspector | 画面の要素や属性を調べる |
| Python・Appium Python Client | 操作と確認の処理を書く |
Android Studioは、SDKやエミュレーターを管理するために使います。Appium Inspectorは、画面を調べるときの補助ツールです。
なお、Pythonでテストを書く場合も、Appium本体を動かすためのNode.jsは必要です。「Pythonを使うのにNode.jsも?」となるところですが、動かしているものが別なんですね。
1.Node.js、JDK、Pythonを用意する
ここでは、次の組み合わせを使うことにします。最低要件の一覧ではなく、記事で使う構成例です。
- Node.js:24系
- npm:10以上
- JDK:21系
- Python:3.12系
Appium 3の公式要件には、Node.jsの対応範囲とnpm 10以上が指定されています。Appium Python ClientにもPython・Seleniumとの互換条件があります。
未導入の場合は、Node.js、Eclipse Temurin、Pythonから、利用するMacに対応したものをインストールします。
ターミナルで次を確認します。
node --version
npm --version
java -version
javac -version
python3 --version
ここでコマンドが見つからない場合は、そのツールの導入やパス設定を確認します。
この段階では、まだAppiumのエラーではありません。 エラーの担当者を一人ずつ決めていく感じです。
2.Android SDKとエミュレーターを用意する
Android Studioをインストールし、SDK Managerで次を用意します。
| 項目 | 今回の選択例 |
|---|---|
| Android SDK Platform | Android 15/API 35 |
| Android SDK Platform-Tools | インストールする。adbが含まれる |
| Android SDK Build-Tools | API 35に対応する35系 |
| Android Emulator | インストールする |
| エミュレーターのシステムイメージ | API 35のGoogle APIsイメージ |
その後、Device Managerから仮想端末を1台作ります。システムイメージは、Apple SiliconならARM系、Intel Macならx86_64系など、ホストに対応したものを選びます。作成後は起動し、ホーム画面まで進めておきます。
次に、SDKとJDKの場所を環境変数で指定します。
macOSでSDKを標準の場所にインストールし、JDK 21が登録されている場合の例です。SDKの実際の場所は、SDK Managerの「Android SDK Location」で確認できます。
export ANDROID_HOME="$HOME/Library/Android/sdk"
export JAVA_HOME="$(/usr/libexec/java_home -v 21)"
export PATH="$JAVA_HOME/bin:$ANDROID_HOME/platform-tools:$PATH"
ANDROID_HOMEは「Androidの道具はここ」、JAVA_HOMEは「Javaの道具はここ」という案内です。PATHには、コマンドを探す場所を追加しています。
ターミナルで設定した変数は、そのターミナルを閉じると失われます。zshを使っている場合は、確認できた設定を~/.zshrcに保存し、新しいターミナルでも読み込まれるようにします。既存の設定がある場合は、同じ変数が重複しないよう整理します。
そして、端末が見えているかを確認します。
adb devices -l
一覧に端末IDとdeviceが表示されれば、ADBとの接続はできています。例えば、端末IDがemulator-5554なら、後の手順でもその値を使います。番号は環境によって変わります。
offlineの場合や、一覧が空の場合は、まずエミュレーターの状態を確認します。なお、device表示だけではOSの起動完了までは保証しないため、画面でもホーム画面まで起動していることを確認します。
ここでの到達点は「PCからAndroid端末が見えること」です。
3.Appiumとドライバーを入れる
ここで、ようやくAppiumの出番です。
npm install -g appium@3
appium driver install uiautomator2
Appium本体とドライバーは、別々にインストールします。UiAutomator2 Driverは5系以降、Appium 3との組み合わせが前提です。古い記事の手順と混ぜると、このあたりでつまずきます。
導入後は、バージョンとドライバーを確認します。
appium --version
appium driver list --installed
続いて、必要な環境がそろっているかを診断します。
appium driver doctor uiautomator2
必要な修正と、任意機能のための修正を区別して読みます。まず対応するのは必須条件です。使わない任意機能の警告まで一度に解消しようとすると、また登場人物が増えます。
準備できたら、サーバーを起動します。
appium --address 127.0.0.1 --port 4723
このターミナルは開いたままにします。Appiumはここで、テストコードやInspectorからの接続を待ち受けます。
起動ログが出た段階で確認できたのは、Appiumサーバーが起動したことまでです。 アプリを操作できるかは、次で確かめます。
4.Inspectorから接続する
ここでは、テストコードを書く前に、Appium Inspectorから接続してみます。
公式の導入手順に沿って、デスクトップ版をインストールします。接続先は次のとおりです。
| 設定 | 値 |
|---|---|
| Remote Host | 127.0.0.1 |
| Remote Port | 4723 |
| Remote Path | / |
| SSL | 無効 |
続いて、「どの端末で、どのアプリを動かすか」を指定します。この設定をCapabilitiesと呼びます。
最初は、Android標準の「設定」アプリを使います。自社アプリのビルドやログイン環境の準備を含めずに、接続を確認するためです。
InspectorのJSON入力欄に、次を指定します。emulator-5554は、先ほど確認した端末IDに置き換えてください。
{
"platformName": "Android",
"appium:automationName": "UiAutomator2",
"appium:udid": "emulator-5554",
"appium:appPackage": "com.android.settings",
"appium:appActivity": ".Settings",
"appium:noReset": true,
"appium:forceAppLaunch": true
}
appPackageはアプリの識別子、appActivityは起動先の画面を指定する値です。この例は、Android Studioで作成した標準的なエミュレーターを対象にしています。
「Start Session」を押して、設定アプリの画面と要素のツリーが取得できれば成功です。[11]
ここまで来ると、Appium、ドライバー、端末、アプリがつながったことを確認できます。環境構築の大きな山を一つ越えました。
5.同じ接続をPythonから試す
次は、Inspectorで確認した設定を、そのままPythonに移します。
先にInspectorのセッションを終了します。同じ端末を複数のセッションから同時に操作しないようにするためです。Appiumサーバーとエミュレーターは起動したままにしておきます。
別のターミナルで作業用フォルダーを作り、Pythonの仮想環境を用意します。
mkdir appium-first-step
cd appium-first-step
python3 -m venv .venv
source .venv/bin/activate
python -m pip install Appium-Python-Client
Appium Python Clientは、PythonからAppiumに接続するためのライブラリです。依存関係としてSeleniumもインストールされます。
次の内容をcheck_connection.pyとして保存します。ここでも、端末IDは自分の環境に合わせて変更します。
from pathlib import Path
from appium import webdriver
from appium.options.android import UiAutomator2Options
from selenium.webdriver.support.ui import WebDriverWait
options = UiAutomator2Options().load_capabilities({
"platformName": "Android",
"appium:automationName": "UiAutomator2",
"appium:udid": "emulator-5554", # adbで確認した端末ID
"appium:appPackage": "com.android.settings",
"appium:appActivity": ".Settings",
"appium:noReset": True,
"appium:forceAppLaunch": True,
})
driver = webdriver.Remote(
"http://127.0.0.1:4723",
options=options,
)
try:
WebDriverWait(driver, 15).until(
lambda d: d.current_package == "com.android.settings"
)
if not driver.save_screenshot("settings.png"):
raise RuntimeError("スクリーンショットを保存できませんでした")
Path("settings.xml").write_text(
driver.page_source,
encoding="utf-8",
)
print("接続確認に成功しました")
finally:
driver.quit()
実行します。
python check_connection.py
「接続確認に成功しました」と表示され、settings.pngとsettings.xmlが作られれば、Pythonからアプリを起動し、スクリーンショットと画面構造を取得できています。
これは自動化の疎通確認です。ログイン機能や業務処理の正しさを検証できたわけではありません。それでも、テストを書くための足場はできました。
「人間には見えるのに、Appiumには見つからない」
環境が動くと、次に出てくるのが要素取得です。
人間には、ログインボタンが見えています。でも、テストコードには「そのボタンを何で識別するか」を教える必要があります。
ここでInspectorを使います。対象アプリの画面を取得し、ボタンを選んで、resource-idやcontent-descなどの属性を確認します。AndroidのUiAutomator2では、これらをIDやAccessibility IDによる検索に使えます。
最初に次の3点を確認します。
| 確認すること | 判断のポイント |
|---|---|
| 対象を一意に識別できるか | 同じIDや説明が複数の要素に付いていないか |
| 値が安定しているか | データや表示言語が変わっても、同じ要素を指せるか |
| 操作できる状態になっているか | 表示途中、無効状態、別画面の背後などではないか |
画面上の座標や、画面階層を丸ごとたどる長いXPathを使う前に、安定した識別方法を探します。
識別できる属性がない場合は、アプリ側の修正も選択肢です。テストコードだけで頑張り続ける必要はありません。特に、自社で開発するアプリなら、「テストから安定して識別できること」を画面実装の条件に入れると、後の保守が楽になります。
「3秒待つ」から、「操作できるまで待つ」へ
もう一つの壁が、タイミングです。
画面が切り替わる前にボタンを探したり、処理が終わる前に次の操作を始めたりすると、実行のたびに成功したり失敗したりします。
そこで使うのが、条件を指定する待機です。例えば「ボタンが表示され、有効になるまで最大10秒待つ」と書きます。条件を満たせば、10秒を待ち切らずに先へ進みます。
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
# 対象アプリのセッション内で使う例です。
# IDはInspectorで確認した実際の値に置き換えます。
login_button = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable(
(AppiumBy.ID, "com.example.app:id/login_button")
)
)
login_button.click()
この例は書き方の説明です。設定アプリ用のスクリプトに、そのまま追加して動くものではありません。また、表示・有効状態の確認だけで、オーバーレイやアニメーションによる操作失敗をすべて防げるわけではありません。
操作後も、「ホーム画面固有の要素が表示された」などの期待結果を確認します。ボタンを押せたことと、処理が正しく完了したことは別の確認項目です。
動かないときは、最後に成功したところまで戻る
トラブル時には、設定を片端から変えたくなります。でも、一度に変えると、何が効いたのか分からなくなります。
次の順番で確認すると、調べる範囲を絞れます。
| 止まったところ | 最初に確認すること |
|---|---|
adb devices -lに端末が出ない |
エミュレーターの起動状態とSDKの設定 |
| Doctorで必須項目が失敗する | 指摘されたSDK・JDK・環境変数 |
| Inspectorからサーバーにつながらない | Appiumの起動状態、接続先、ポート、パス |
| 接続要求は届くがセッションが作れない | Appiumログ、端末ID、Capabilities、ドライバーの互換性 |
| アプリは開くが要素が見つからない | 現在の画面、要素の属性、表示までの待機 |
| 1回目は動くが2回目は失敗する | ログイン状態、前回のデータ、ダイアログ、開始画面 |
特に最後の「2回目は失敗する」は、自動化らしい難しさです。前回の実行によって、アプリの状態が変わっているからです。
最初の1本が動いたら、同じ開始状態に戻して、続けて実行してみます。毎回手でログアウトしたり、データを消したりしているなら、その作業もテストの準備手順として書き出します。
さいごに
Appium、やっぱり登場人物は多いです。ただ、「端末が見えた」「接続できた」「画面を取れた」「操作できた」と分けると、少なくとも次に調べる場所は分かります。
まずは、1台、1画面、1操作。落ち着いていこう。
参考資料
- Appium:UiAutomator2 Driverの導入
- Appium Inspector:導入手順
- Appium 3:システム要件
- Appium Python Client:導入・互換性
- Android Developers:仮想端末の作成と管理
- Android Developers:環境変数
- Android Developers:Android Debug Bridge
- Appium:インストールと起動
- UiAutomator2 Driver:要件・Doctor・Capabilities・要素検索
- Appium:サーバーのコマンドライン引数
- Appium Inspector:セッションの設定と開始
- Appium:Pythonによるテスト作成
- Selenium:待機