Airtestは、NetEase(網易)が公開している画像認識ベースのUI自動テストフレームワークです。ライセンスはApache License 2.0で、Android・iOS・Windowsのアプリやゲームを、画面の見た目を手がかりにPythonで操作します。UIの部品を名前で指定したい場面は、同じAirtestProjectのPocoが受け持ちます。本記事では、2026年10月時点の最新版(airtest 1.4.3)を前提に、AirtestIDEとpipでの導入、画像認識の閾値と色の扱い、PocoでUnityのUIを操作する手順、airtest runによるCI組み込み、Appiumとの使い分けまでを、公式ソースと手元での実行結果に沿って説明します。
まとめ
- Airtest=画像認識、Poco=UIツリー。描画を自前で行うゲームは画像認識、ボタン名やテキストで指定できる画面はPocoで書き、1本のスクリプトで混在させる。
- 最新版はairtest 1.4.3(2025年12月4日)。iOS 17以上の端末は1.4.2から対応。GUIのAirtestIDEは1.2.17(2024年1月30日)で止まっており、同梱のAirtestは1.3.3と古い。
- 導入は
pip install -U airtest pocoui。Python 3.12・3.13で入るが、numpy<2.0などを固定するので専用の仮想環境に分ける。 - 画像認識の既定閾値は0.7。模擬画面では文字だけ違うボタンを0.779で誤検出し、色違いのボタンは
rgb=Trueを付けたときだけ区別できた。 - UnityはPoco-SDKの
PocoManager(既定ポート5001)を開発ビルドだけに入れ、Python側はUnityPoco()で接続する。 - CIでは
airtest runの終了コード(成功0・アサーション失敗20・その他の例外-1=シェルでは255)で合否を判定し、airtest reportでHTMLレポートを出す。
Airtestとは|画像認識でゲームとアプリを操作するフレームワーク
Airtestは、端末のスクリーンショットから「あらかじめ切り出した部分画像」の位置を探し、その座標をタップやスワイプする仕組みです。アプリにコードを埋め込まなくても動くため、独自描画で内部のUI構造を外から取れないゲームでも同じ書き方で自動化できます。GitHubのREADMEは、ゲームとアプリ向けのクロスプラットフォームUI自動化フレームワークと説明しています。
Airtest・Poco・AirtestIDEの役割分担
「Airtest Project」は複数のリポジトリの総称です。検索で「airtest github」と調べたときに行き先を迷わないよう、役割と公開場所を並べます。いずれもGitHubのAirtestProjectにあり、Airtest・Poco・Poco-SDKのLICENSEはApache License 2.0です。
| 名前 | 役割 | 入手先 |
|---|---|---|
| Airtest | 画像認識で操作するPythonライブラリ | PyPI airtest |
| Poco | UIツリーで操作するPythonライブラリ | PyPI pocoui |
| Poco-SDK | アプリ側に組み込むUIツリー公開用SDK | GitHub Poco-SDK |
| AirtestIDE | 記録・実行・レポートのGUI | airtest.netease.com |
| iOS-Tagent | iOS端末を操作するエージェント | GitHub iOS-Tagent |
有償のクラウド実機基盤として紹介されることがあるAirLabは、公式ドキュメントの商用ソリューションのページで「まだ外部には公開しておらず、将来商用化する」と書かれています。国内のチームが選ぶ前提にはしないほうが安全です。
2026年10月時点の最新版と更新状況
Airtest本体は更新が続いていますが、IDEとPocoは2024年以降のリリースがありません。どれを最新として扱うかで手順が変わるので、先に版を確認しておきます。
| コンポーネント | 最新版 | 公開日 |
|---|---|---|
| airtest(PyPI) | 1.4.3 | 2025-12-04 |
| pocoui(PyPI) | 1.0.94 | 2023-12-28 |
| AirtestIDE | 1.2.17(Airtest 1.3.3同梱) | 2024-01-30 |
| Poco-SDK(GitHub) | リリースなし・最終更新 | 2024-07-09 |
airtestの版と日付はPyPIのリリース履歴、IDEはAirtestIDEの変更履歴で確認できます。GitHubのリリースノートでは、1.4.2で「iOS 17以上の端末に対応」「iOSにscreen_sizeを追加しwindow_sizeを置き換え」、1.4.3で「facebook-wdaの版を固定」とされています。iOS 17以上の実機を扱うなら、IDE同梱の1.3.3ではなくpipで入れた1.4.2以降で実行してください。
AirtestIDEとPythonライブラリの導入手順
操作を記録してスクリプトの下書きを作るならAirtestIDE、CIで回すならpipで入れたライブラリを使います。IDEで作った.airフォルダは、そのままコマンドラインのairtest runで実行できます。
AirtestIDEの入手とAndroid端末の接続
AirtestIDEはAirtest Projectの公式サイトからWindows・macOS向けをダウンロードします。Android端末は、端末側で開発者向けオプションの「USBデバッグ」を有効にしてUSB接続し、IDEの接続パネルで端末を選んで接続します。画面のミラーリングが表示されれば、画面上を範囲選択するだけでtouch(Template(...))の行が生成されます。
端末の画面取得やタッチ入力の方式は、接続文字列で切り替えられます。公式ドキュメントの例はAndroid:///SJE5T17B17?cap_method=javacap&touch_method=adbで、取得方式はMINICAP・ADBCAP・JAVACAP、入力方式はMINITOUCH・MAXTOUCH・ADBTOUCHから選びます(定数の定義)。新しいAndroidでminicapが動かず画面が黒くなるときは、取得方式をJAVACAPに変えるのが最初の切り分けです。
pipによるairtest・pocouiの導入と依存関係
Pocoのパッケージ名はpocouiです。pip install pocoでは別のパッケージが入ります。
python3.12 -m venv .venv-airtest
source .venv-airtest/bin/activate
pip install -U airtest pocoui
# 以下はmacOS用。Linuxでは末尾のmacをlinuxに変更して実行する
chmod +x .venv-airtest/lib/python3.12/site-packages/airtest/core/android/static/adb/mac/adb
2026年10月18日にmacOS(Intel)で、Python 3.12と3.13の仮想環境それぞれにairtest==1.4.3とpocoui==1.0.94を入れたところ、どちらも導入に成功しました。入ったのはnumpy 1.26.4とopencv-contrib-python 4.6.0.66です。airtestは依存にnumpy<2.0とopencv-contrib-python<=4.6.0.66を固定しているため、numpy 2系を前提にする機械学習ライブラリと同じ環境に入れると衝突します。テスト専用の仮想環境に分けてください。PyPIの分類子はPython 3.9までしか書かれていないので、3.12以降は公式の動作保証外として扱うのが妥当です。
pip直後の同梱adb(mac)は実行権限が付いておらず、chmod +xのあとでadb versionを実行すると「1.0.41/Version 34.0.5」と表示されました。手順はREADMEのInstallationにもあります。Android Studioなど別のadbを同じPCで使う場合は、版の違うadbサーバーが互いに再起動し合わないよう、どちらか一方に揃えます。
iOS端末を接続する前提(iOS-Tagent・tidevice)
iOSは端末上で動くエージェントを経由して操作します。iOS-TagentのREADMEの手順は、MacのXcodeでiOS-Tagentのプロジェクトを開き、iPhoneをケーブルで接続して起動するものです。エージェントが待ち受けるポートへconnect_device("iOS:///http://127.0.0.1:8100")で接続します。導入済みのWebDriverAgentをXcodeなしで起動したい場合は、公式のiOS接続ガイドが案内するtideviceを使えます。Androidに比べて、署名とエージェント起動の手間がかかる点は見込んでおきます。
画像認識スクリプトの書き方(Template・touch・assert_exists)
スクリプトは普通のPythonです。端末に接続し、アプリを起動し、部分画像をTemplateで渡して操作と検証を並べます。
from airtest.core.api import *
connect_device("Android:///")
start_app("com.example.game")
# AirtestIDEで範囲選択すると、record_posとresolutionが自動で付く
touch(Template(r"start_btn.png", record_pos=(0.0, 0.481), resolution=(1080, 1920)))
wait(Template(r"home.png"), timeout=30)
assert_exists(Template(r"result.png", threshold=0.9), "結果画面が表示された")
keyevent("BACK")
touchやwaitは画像が見つかるまで照合を繰り返し、既定では20秒で諦めます。existsの待ち時間は3秒です(settings.pyのFIND_TIMEOUT・FIND_TIMEOUT_TMP)。
Templateの引数と既定値
| 引数 | 意味 | 既定 |
|---|---|---|
threshold |
この信頼度以上を一致とみなす | ST.THRESHOLD=0.7 |
record_pos |
記録時の画面内の位置(中心からの比率) | なし |
resolution |
記録時の画面解像度 | なし |
rgb |
色も含めて照合する | False(グレースケール) |
target_pos |
画像内のどこをタップするか(1〜9) | 5(中心) |
引数の意味は公式の画像認識ガイドとcv.pyのソースに沿っています。照合はST.CVSTRATEGYの順(mstpl→tpl→sift→brisk)に試し、最初に閾値を超えた結果を採ります。先頭のmstplはresolutionが無いと結果を返さず次の手法に回るため、手書きでTemplate("x.png")とだけ書くと、IDEで作った行とは照合の経路が変わります。
閾値0.7と色の扱いを模擬画面で確かめた結果
画像認識の信頼度は画面やテンプレートによって変わるため、条件を固定した模擬画面で比較しました。1080×1920の画面に緑の「START」ボタンを描き、ボタン部分を切り出したテンプレートで、条件を変えた画面ごとの信頼度を記録しています。
import time
import cv2
import numpy as np
from airtest.core.cv import Template
def make_screen(label="START", color=(40, 160, 40), size=(1080, 1920)):
img = np.full((1920, 1080, 3), 235, np.uint8)
cv2.rectangle(img, (340, 1400), (740, 1560), color, -1)
cv2.putText(img, label, (420, 1505), cv2.FONT_HERSHEY_SIMPLEX, 2.4, (255, 255, 255), 6)
return cv2.resize(img, size, interpolation=cv2.INTER_AREA)
cv2.imwrite("start_btn.png", make_screen()[1390:1570, 330:750])
IDE = {"record_pos": (0.0, 0.481), "resolution": (1080, 1920)} # AirtestIDEが付ける形
cases = [
("同じ画面", make_screen(), {}),
("720x1280・引数なし", make_screen(size=(720, 1280)), {}),
("720x1280・IDE形式", make_screen(size=(720, 1280)), IDE),
("赤いボタン・既定", make_screen(color=(40, 40, 200)), {}),
("赤いボタン・rgb=True", make_screen(color=(40, 40, 200)), {"rgb": True}),
("赤いボタン・rgb=True・IDE形式", make_screen(color=(40, 40, 200)), {"rgb": True, **IDE}),
("文字QUIT・既定0.7", make_screen("QUIT"), {}),
("文字STOP・0.9", make_screen("STOP"), {"threshold": 0.9}),
("文字STOP・0.95", make_screen("STOP"), {"threshold": 0.95}),
]
for name, screen, kw in cases:
t0 = time.perf_counter()
r = Template("start_btn.png", **kw)._cv_match(screen) # 信頼度を見るため内部メソッドを使う
ms = (time.perf_counter() - t0) * 1000
print(name, "未検出" if r is None else round(r["confidence"], 3), f"{ms:.0f}ms")
airtest 1.4.3・Python 3.12で2回実行し、信頼度は2回とも同じ値でした(時間はCPU負荷で前後するため目安です)。
| 画面の条件 | 信頼度 | 判定 | 時間 |
|---|---|---|---|
| 同じ画面 | 1.0 | 一致 | 約0.2〜0.3秒 |
| 720×1280・引数なし | 0.984 | 一致 | 約0.6〜0.9秒 |
| 720×1280・IDE形式 | 0.969 | 一致 | 約1.1〜1.5秒 |
| 赤いボタン・既定 | 1.0 | 一致(誤検出) | 約0.2〜0.3秒 |
| 赤いボタン・rgb=True | – | 未検出 | 約1.5〜2.0秒 |
| 赤いボタン・rgb=True・IDE形式 | – | 未検出 | 約4.0〜4.6秒 |
| 文字QUIT・既定0.7 | 0.779 | 一致(誤検出) | 約0.2〜0.3秒 |
| 文字STOP・0.9 | – | 未検出 | 約1.5秒 |
| 文字STOP・0.95 | – | 未検出 | 約1.5秒 |
結果から言えることは3つです。まず、既定の0.7では「QUIT」と書かれた同じ形のボタンを0.779で一致と判定しました。文字が違うだけのボタンを押し分けるなら、Pocoのテキスト指定を優先します。画像認識を使う場合は、正例と文字違いの負例を用意し、両者を区別できる閾値を実画面で確認します。次に、色だけを変えたボタンは既定のグレースケール照合で信頼度1.0のまま一致しました。有効と無効(グレーアウト)を見分ける検証ではrgb=Trueを付けたところ未検出になり、区別できました。最後に、見つからないときほど遅くなります。全手法を順に試すためで、touchなら20秒の待ち時間いっぱいまで照合を繰り返します。
この検証は模擬画面であり、実機のスクリーンショットにある圧縮ノイズやアニメーションは含みません。実機では模擬画面と信頼度の分布が変わるため、IDEの画像編集画面で正しい対象と誤検出候補の信頼度を確認し、閾値を決めてください。
PocoによるUnityのUI要素の名前指定操作
Pocoはアプリ内のUIツリー(GameObjectの親子関係や名前、テキスト)を取得し、poco("btn_start").click()のように要素を指定して操作します。座標や画像に依存しないので、解像度やデザイン変更に強いテストになります。その代わり、アプリ側にPoco-SDKを組み込む作業が要ります。
UnityへのPoco-SDKの組み込みと接続ポート
Poco-SDKのUnity3D向けREADMEの手順は次の4つです。
- Poco-SDKのリポジトリを取得し、
Unity3DフォルダをUnityプロジェクトのスクリプト用フォルダ(公式チュートリアルではAssets/Scripts)へコピーする。 - 使っているUIの系統に合わせ、
ugui・uguiWithTMPro・ngui・fairyguiのうち使わないフォルダを削除する。 PocoManager.csを任意のGameObject(通常はメインカメラ)にスクリプトコンポーネントとして追加する。- 端末で起動すると、
PocoManagerが既定ではポート5001で接続を待ち受ける。使用中なら5002〜5005も順に試すため、起動ログで実際のポートを確認し、Python側の接続先を合わせる。
ポート5001を開いたビルドは、同じネットワークや端末に接続できる人がUIツリーの取得と操作をできる状態です。リリースビルドに残さないよう、開発ビルドだけでPocoManagerを足すのが安全です。
using UnityEngine;
// メインカメラに付ける。リリースビルドではPocoManagerを追加しない
public class PocoBootstrap : MonoBehaviour
{
void Awake()
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
gameObject.AddComponent<PocoManager>();
#endif
}
}
この方法をとる場合は、PocoManager.csをシーンへ直接付けず、上のスクリプトだけを付けます。DEVELOPMENT_BUILDはBuild Settingsで「Development Build」にチェックを入れたときだけ定義されるシンボルです。
UnityPocoの接続設定と操作スクリプト
from airtest.core.api import connect_device
from poco.drivers.unity3d import UnityPoco
connect_device("Android:///")
poco = UnityPoco() # 既定の接続先は ("localhost", 5001)
poco("btn_start").wait_for_appearance(timeout=20)
poco("btn_start").click()
shell = poco("shell").focus("center")
for star in poco("star"):
star.drag_to(shell)
assert poco("scoreVal").get_text() == "100"
UnityPocoのソースと実行結果から、押さえておく点が3つあります。1つ目に、端末に未接続のままUnityPoco()を呼ぶと、airtest 1.4.3とpocoui 1.0.94ではNoDeviceErrorで止まります。ソースにはAndroid:///へ自動接続する分岐がありますが、その手前のcurrent_device()が例外を出すためです。掲載コードのようにconnect_deviceで先に端末へ接続してください。2つ目に、接続先がlocalhostでAndroidなら、StdPocoがadbのポート転送を自動で設定します。3つ目に、各操作の後には0.5秒の待ち(action_interval)が入ります。Windows上のUnityエディタで再生中のゲームを相手にするときはUnityPoco(unity_editor=True)を使います(エディタのウィンドウにWindows用の接続でつなぐ実装です)。Wi-Fi越しに端末へ直接つなぐならUnityPoco(("端末のIPアドレス", 5001))です。
Unity以外のエンジンとネイティブアプリ
pocoui 1.0.94のドライバは、Unity3D・Cocos(cocosjs)・UE4・Android・iOS・Windows・macOS・Qtなどに対応するものがあります。Androidのネイティブ画面はアプリへのSDK組み込みが要らないAndroidUiautomationPoco、iOSはiosPocoでUIツリーを取得できます。ゲーム内はUnityPoco、ログインのWebViewや権限ダイアログはAndroid用のPocoかAirtestの画像認識、という分担になります。
airtest run・airtest reportのCI連携と終了コード
IDEで作った.airフォルダは、コマンドラインで実行してHTMLレポートまで出せます。オプションはCLIのパーサと公式ドキュメントに一覧があります。
# 端末を指定して実行し、ログをlogフォルダへ保存
airtest run smoke.air --device Android:/// --log smoke.air/log --recording
# 実行ログからHTMLレポートを作る(--exportで持ち運べるフォルダに書き出す)
airtest report smoke.air --log_root smoke.air/log --outfile smoke.air/log/log.html --lang en
レポートの--langはreport.pyでzhとenだけを受け付け、jaなどそれ以外の値はエラーにならずenとして扱われます。日本語のレポートは出せません。
CIで合否を判定するのはairtest runの終了コードです。runner.pyは、アサーション失敗で20、それ以外の例外で-1を返します。macOSで確かめると、assert 1 + 1 == 3の失敗は20、端末未接続の例外はシェル上で255(-1)、成功は0でした。CIのジョブは「0以外なら失敗」と扱えば足ります。通知を分ける場合は、20をアサーション失敗、macOS・Linuxでの255をその他の例外として分類し、製品・テストコード・環境のどこに原因があるかはログで確認します。
端末未接続のままログとスクリーンショット保存を有効にすると、assert_equalのような端末と無関係な検証でも失敗します。Airtestは各操作のたびにスクリーンショットをログに残そうとするため、NoDeviceError: 'No devices added.'で止まるからです。--no-imageを付けると同じスクリプトが成功し、--logを付けずに実行した場合も成功しました。配管の確認には--no-imageを使い、実機で回す本番のジョブでは外します。
複数台で並列に回す仕組みは本体に無く、公式はmulti-device-runnerというサンプルを公開しています。リリース前の短い確認に絞った流し方はスモークテストのCI/CDへの組み込みで解説しています。
AirtestとAppium・Seleniumの使い分け
判断は「対象のUI部品を外から名前で取れるか」で決まります。
| ツール | 操作の手がかり | 向いている対象 |
|---|---|---|
| Airtest | 画像 | 独自描画のゲーム・Canvas |
| Poco+SDK | エンジン内のUIツリー | Unity・Cocos製のゲーム |
| Appium | OSのアクセシビリティ要素 | 標準UIのiOS・Androidアプリ |
| Selenium・Playwright | DOM | Webアプリ |
標準UIで作られた業務アプリなら、アクセシビリティIDで要素を掴めるAppiumを第一候補にします。画像テンプレートを用意せず、UI要素の識別子を使って操作できるためです。WebアプリはSeleniumかPlaywrightが標準です。Airtestを選ぶ理由が立つのは、ゲームエンジンやCanvasで描画されていて、AppiumやSeleniumが要素を取れない場合です。
逆に、Airtestを採用すべきでない場面もはっきりしています。画面のデザインや文言を頻繁に変える運用中のアプリで、画像認識だけのテストを主力にすると、変更のたびにテンプレート画像の撮り直しが発生します。先の実測のとおり既定の閾値では文字違いのボタンも一致するため、誤操作に気付きにくいのも難点です。UIツリーを取れるならPocoを主、画像認識は起動直後やシステムダイアログなど取れない箇所に限る、という配分にすると保守の手間が減ります。IDEとPocoの更新が2024年で止まっている点も、長期運用の判断材料に入れてください。
実機の台数を手元で揃えられない場合は、クラウドの実機で回すFirebase Test Labと役割を分ける構成も候補です。ゲームの企画・開発からテストの設計までまとめて相談したい場合は、ゲームアプリ開発の支援も行っています。
よくある質問
Airtestは無料で使えますか?
はい。Airtest・Poco・Poco-SDKはApache License 2.0のオープンソースで、商用のプロジェクトでも無料で使えます。AirtestIDEも公式サイトから無料でダウンロードできます。
AirtestIDEとairtestライブラリの違いは何ですか?
AirtestIDEはスクリプトを記録・実行し、レポートを表示するGUIです。airtestはPythonのライブラリで、CIや自前の環境からairtest runで実行するときに使います。IDE 1.2.17の同梱版は1.3.3なので、iOS 17以上の端末で回すときはpipで入れた1.4.2以降で実行してください。
AirtestとPocoはどちらを使えばよいですか?
アプリにPoco-SDKを組み込めるなら、ボタンやテキストの操作はPocoで書きます。SDKを入れられない画面(起動直後、OSのダイアログ、他社製のゲーム)はAirtestの画像認識で補います。1本のスクリプトで両方を混在させられます。
pocoをpipでインストールできないのはなぜですか?
PyPI上のパッケージ名がpocouiだからです。pip install pocouiで入れ、コードではfrom poco.drivers.unity3d import UnityPocoのようにpocoとして読み込みます。
iOSアプリのテストにも使えますか?
使えます。MacのXcodeでiOS-Tagentを端末に入れて起動し、iOS:///http://127.0.0.1:8100のような接続文字列で接続します。iOS 17以上はairtest 1.4.2以降が必要です。