モバイルアプリのE2Eテストでは、テストケースを考えること以上に、AndroidとiOSそれぞれの操作コードを書く作業に時間を取られます。
今回作った検証アプリでも、確認したいログインフローは同じなのに、AndroidではUiAutomator2、iOSではXCUITestを使うため、操作実装は別々に必要でした。
そこで、Gherkinのシナリオを起点にOS別のBehave stepを生成する方法を試すため、Microsoft AutoGenesisを使って検証しました。生成したstepはOS別に保存し、動作確認後は生成AIやAutoGenesisのMCPサーバーを呼び出さず、ローカルのAppium環境で繰り返し実行できる構成を目指しました。
この記事では、AndroidとiOSでシナリオを共有するために採用した構成と、実際に試してから決めた運用ルールを紹介します。
先に結論
AutoGenesisを使っても、テスト設計は自動では片付きません。代わりに、次の三つをきちんと決めておくと、OSごとの操作実装を始める負担はかなり下がります。
- シナリオはGherkinで一つにする
- 画面要素は表示文言ではなく、両OS共通のaccessibility IDで特定する
- 生成したstepはOS別に保存し、同じシナリオをそれぞれで実行する
この構成にすると、「どの振る舞いを確認するか」と「各OSでその操作をどう実装するか」を分けて扱えます。
今回の成果物はGitHubのMobileAutoGenesisSampleで公開しています。
今回の構成
検証用アプリは、Kotlin + Jetpack ComposeのAndroid版と、Swift + SwiftUIのiOS版を並べたモノレポにしました。今回確認したいのは、AutoGenesisで生成したstepによって、AndroidとiOSで同じユーザーフローを再現できるかという点です。そのため、実バックエンドとの通信は対象に含めず、両方ともダミーデータを扱う MockRepository を用意しました。対象は、ログイン、一覧、詳細、設定などを含む5画面です。
E2Eテストの流れは次のとおりです。
AutoGenesis公式のモバイル向け手順ではBrowserStackを利用する構成が紹介されていますが、今回はローカルで起動したAppiumサーバー(http://127.0.0.1:4723)を使用しました。実機クラウドでの実行に進む前に、まずローカル環境で再現できることを優先したためです。
シナリオは一つ、操作コードはOS別
ログイン成功のシナリオは、次のように書きました。
Feature: Login
@smoke @android @ios
Scenario: Successful login with valid credentials
Given I have launched the sample app
When I input "demo@example.com" in element "login_email_field"
And I input "password" in element "login_password_field"
And I tap element "login_submit_button"
Then I should see element "item_list_screen"
ここにはAndroid固有、iOS固有の記述はありません。テストとして必要なのは「有効な認証情報を入力し、ログインすると一覧が見える」という振る舞いだからです。
一方で、Appiumを使う操作コードはOS別になります。このOS別の操作コードを、Copilot ChatとAutoGenesisを使って生成しています。
/generate-mobile-steps platform=android scenario_name="Successful login with valid credentials"
/generate-mobile-steps platform=ios scenario_name="Successful login with valid credentials"
生成先も分けています。
- Android:
behave-demo/features/android_steps/ - iOS:
behave-demo/features/ios_steps/
当初はstepの共通化も考えましたが、ドライバの違いや待機処理を吸収するための分岐が増えました。今回はAndroid用とiOS用に分けたほうが、生成後のコードが単純になり、失敗箇所も追いやすいと判断しました。
文言ではなくaccessibility IDを契約にする
最初に決めたのは、テストで画面の文字列を探さないことでした。
ログイン画面の各要素には、AndroidとiOSで同じaccessibility IDを付けています。
| 役割 | accessibility ID |
|---|---|
| メールアドレス入力 | login_email_field |
| パスワード入力 | login_password_field |
| ログインボタン | login_submit_button |
| 一覧画面のルート | item_list_screen |
Androidでは Modifier.testTag(...)、iOSでは .accessibilityIdentifier(...) を使いますが、設定する値は共通です。KotlinとSwiftで定数名の付け方が異なっていても、Appiumから参照するIDは共通にできます。
このID一覧は docs/TESTID_CATALOG.md を単一情報源にしました。画面を作る人とテストを作る人が別でも、何を固定すべきかが曖昧になりにくいです。
ここはAutoGenesis以前の話でもあります。テストが表示文言に依存すると、翻訳、文言修正、デザイン調整のたびに壊れます。テスト用の識別子を画面の公開インターフェースとして扱うほうが、後で効いてきます。
実行前に環境を確認する
stepを生成する前に、アプリ、Appiumサーバー、AutoGenesisのMCPサーバーが利用できるかを確認するpreflightスクリプトを用意しました。
scripts/autogenesis/preflight.sh android
scripts/autogenesis/preflight.sh ios
AndroidではエミュレータとAPK、iOSでは起動済みのSimulatorとビルド済みアプリが必要です。Appiumも別のターミナルで起動しておきます。
appium
セットアップ後は、生成済みのstepをplatform別のBehave stageで実行します。
cd behave-demo
AUTOGENESIS_PLATFORM=android uv run python -m behave \
--stage android --name "Successful login with valid credentials"
AUTOGENESIS_PLATFORM=ios uv run python -m behave \
--stage ios --name "Successful login with valid credentials"
「生成できた」だけで終わらせず、同じ名前のシナリオが両OSで通るところまでを一連の確認にしました。生成物は出発点であって、動作確認の代わりにはなりません。
試してから決めた運用ルール
今回のテスト範囲を固定する
今回の検証では、Copilot ChatとAutoGenesisを使って生成したstepが、AndroidとiOSで同じ画面操作を再現できるかに対象を絞りました。実バックエンドとの通信は検証範囲に含めず、MockRepository を使ってログインの成功・失敗や、一覧・詳細画面に表示するデータを固定しています。
状態を固定しても、画面描画や遷移の完了を待つ処理は必要です。そこで、データと初期状態を固定したうえで、目的の要素が表示されるまで待つ構成にしました。
sleep は便利に見えて、あとで困る
シナリオの待機は固定秒数ではなく、目的の要素が見えることを条件にします。テスト方針にも wait_for_element_visible を使い、sleep を使わないことを決めました。
端末やCIの負荷で処理時間は変わります。固定待機は、たまたま通る時間を積み上げるだけになりがちです。画面遷移の完了を表す要素、今回なら item_list_screen のような画面ルートIDを置くと、待機条件も読みやすくなります。
生成ファイルは手で直さない
AutoGenesisの出力先である android_steps と ios_steps は、手動編集しないルールにしました。必要な設定やアダプタは config/ と scripts/autogenesis/ に置きます。
生成物をその場しのぎで修正すると、次の生成で消えます。何度も同じ修正が必要なら、シナリオ、識別子、環境設定のどこが不足しているかを見直します。生成物を触りたくなったときほど、元の契約を疑う場面でした。
この構成が向いている場面
- Android/iOSで同じユーザーフローを確認したい
- 手順はGherkinでレビューしたい
- Appiumの操作コードをゼロから書き始める時間を減らしたい
- 外部クラウドに依存せず、開発端末で再現できるE2E環境がほしい
逆に、画面要素の識別子が未整理なアプリでは、先にAutoGenesisを入れても効果は出にくいです。まず画面側に安定したIDを置き、テスト可能な状態を作る必要があります。
まとめ
今回の検証では、Gherkinのシナリオとaccessibility IDをAndroid/iOSで共有し、BehaveのstepだけをOS別に生成する構成にしました。
AutoGenesisを導入しても、テストケースの設計や画面要素の識別、テストデータの固定が不要になるわけではありません。一方、これらを先に決めておけば、Appiumの操作コードをゼロから書き始める作業は減らせました。
また、生成したstepをリポジトリに保存することで、再実行時には生成AIやAutoGenesisのMCPサーバーを呼び出さず、生成済みのstepをローカルのAppium環境で実行できます。現時点では、テスト実行時の判断をAIに任せるよりも、設計済みのシナリオに対する操作実装の生成に限定するほうが運用しやすいと感じています。
リポジトリには、ログインのほか、一覧から詳細への遷移、お気に入りの切り替え、サインアウトのシナリオも含めています。今後は失敗系のシナリオを追加し、stepを再生成した際の差分をレビューしやすくする方法を試す予定です。