Claude Media
Claude CodeでAppiumのモバイルE2Eテストを書く — 検証範囲の線引き

Claude CodeでAppiumのモバイルE2Eテストを書く — 検証範囲の線引き

Claude CodeにAppium(UiAutomator2)のセッション設定とロケータを書かせる手順です。エミュレーターなし・実機なしで確かめられる範囲と、動かして初めて分かる範囲を分けます。

Claude CodeでAppiumのテストを書くとき、任せやすいのはセッション設定(capabilities)とロケータの書き分けです。どちらも「書式の決まり」と「画面の実物」の2つに分かれ、前者は手元で確かめられ、後者は端末で動かすまで分かりません。

この記事はAndroid(UiAutomator2ドライバー)とWebdriverIOの組み合わせで、Claudeに書かせる範囲と人が確かめる範囲の線引きを示します。

実機がなくても確かめられる範囲を先に分ける

Appiumのテストは、コードが正しくても端末が無ければセッションが始まりません。そこで検証を3段に分け、どの段までClaudeに閉じさせるかを決めておきます。

段階必要なものClaudeに確かめさせられること
1. 端末なし必要なものNode.jsとAppiumのみClaudeに確かめさせられること構文、capabilitiesの組み立て、appium:接頭辞の付け忘れ、appium driver doctorの結果
2. エミュレーター必要なものAVD(Android Virtual Device)Claudeに確かめさせられることセッション開始、画面遷移、ロケータが要素に当たるか
3. 実機必要なものUSBデバッグを有効にした端末Claudeに確かめさせられること機種固有の挙動、adb devices -lにonlineで出るか

段階1だけでも、Claudeに任せる価値は十分あります。capabilitiesの書き間違いは、端末につないでから初めてセッション開始のエラーとして出ます。書く段階でClaudeに潰させておけば、端末を用意してから書き直す手間を1回分省けます。

一方、ロケータが実際の画面に当たるかは、段階2以降でないと判定できません。ここを「書けたから通るはず」と読むのが、モバイルE2Eで最も多い手戻りの原因です。ロケータの当たり外れを端末なしで保証する手段は、Appiumの資料に載っていません。

前提を揃える — Appium 3とUiAutomator2ドライバー

環境の要件は、Claudeに書かせる前に人が揃えます。Appiumはnpmでグローバルに入れ、ドライバーは別に入れます。

npm install -g appium
appium driver install uiautomator2
appium driver doctor uiautomator2

appium driver doctor uiautomator2は前提条件の検証ツールです。必須の修正が0件と出れば、設定は済んでいます。オプション項目の指摘は出ても構いません。

前提として押さえる点は次のとおりです。

  • Appium 3はNode 20.19.0以上とnpm 10以上が必要です
  • UiAutomator2ドライバーの5.0.0以降はAppium 3専用です
  • ドライバー6.0.0以降のAndroidは、最小がAPI 26(Android 8.0)です
  • ANDROID_HOMEとJAVA_HOMEの環境変数を設定します。JavaはAndroid SDKがAPI 30未満なら8、API 30以上なら9以降が必要です
  • エミュレーターを使うなら、AVDとシステムイメージを用意します
  • iOSを対象にするXCUITestドライバーはmacOSが必要です

XCUITestはmacOSが要るため、AndroidとiOSのテストは別の作業として書かせます。この記事ではAndroidだけを扱います。

CLAUDE.mdに書いておく規約

Claudeに毎回同じ判断をさせたくない項目は、CLAUDE.mdに固定します。次は例として使える断片です。

## モバイルE2E(Appium)
- 対象はAndroid。automationNameは UiAutomator2、クライアントは WebdriverIO
- capabilitiesのAppium独自キーには必ず `appium:` 接頭辞を付ける
- 端末はudidで指定する。deviceNameは端末選択には使われない
- ロケータの優先順位: accessibilityId、id、-android uiautomator、xpath の順
- xpathを使うときはコメントで理由を書く
- テストコードを書いたら `node --check` で構文を確認する。セッションを張る実行は
  人が指示したときだけ行う
- 要素が見つからない失敗は、ページソースのXMLを読んでからロケータを直す

最後の2行が、前の節で分けた線引きに対応します。Claudeが端末を触る実行は人が指示したときだけに絞り、手元で完結する確認だけは常に走らせる、という分担です。

権限は.claude/settings.jsonで、読み取り系と確認系のコマンドだけを先に許可しておくと、確認のたびに承認を待たずに済みます。次は例です。

{
  "permissions": {
    "allow": [
      "Bash(appium driver doctor uiautomator2)",
      "Bash(adb devices -l)",
      "Bash(node --check:*)"
    ]
  }
}

セッションを張るテスト本体の実行(node tests/...)は、あえて許可に入れていません。端末の状態を変えるため、実行のたびに人が見える形にしておくほうが事故を防げます。ここは運用の好みで変えてかまいません。

セッション設定をClaudeに書かせる

セッション設定の書式は、Appium側のルールがはっきりしています。Claudeに渡す指示は、機種や用途の事実だけで済みます。

エミュレーター(AVD名Pixel_8_API_35)で、標準の設定アプリを起動するWebdriverIOのテストを書いて。udidはemulator-5554。capabilitiesはappium:optionsでまとめて。

ルールとして知っておく点は次のとおりです。

  • Appium独自のcapabilityにはappium:接頭辞が必要です。appium:optionsにまとめれば、中では接頭辞を外せます
  • platformNameは標準のcapabilityなので、接頭辞は付きません
  • appium:automationNameはuiautomator2と書き、大文字小文字は区別されません
  • appium:deviceNameは端末選択には使われません。実機はappium:udid、エミュレーターはappium:avdを使います
  • 並列実行では、appium:udidとappium:systemPortを端末ごとに変えます
  • セッション開始後はcapabilitiesを変えられません。途中で変えたい挙動はSettings APIの領分です

Claudeが書く形は、たとえば次のようになります(公式のクイックスタートに沿った形です)。

const {remote} = require('webdriverio');
 
const capabilities = {
  platformName: 'Android',
  'appium:options': {
    automationName: 'UiAutomator2',
    udid: 'emulator-5554',
    appPackage: 'com.android.settings',
    appActivity: '.Settings',
    printPageSourceOnFindFailure: true,
  },
};
 
const wdOpts = {
  hostname: process.env.APPIUM_HOST || 'localhost',
  port: parseInt(process.env.APPIUM_PORT, 10) || 4723,
  logLevel: 'info',
  capabilities,
};
 
async function runTest() {
  const driver = await remote(wdOpts);
  try {
    // 公式サンプルのまま xpath を使用(画面のテキストで探す例)
    const appsItem = await driver.$('//*[@text="Apps"]');
    await appsItem.waitForDisplayed({timeout: 10000});
    await appsItem.click();
  } finally {
    await driver.deleteSession();
  }
}
 
runTest().catch((e) => { console.error(e); process.exit(1); });

公式のサンプルとの違いは3つです。capabilitiesをappium:optionsにまとめたこと、printPageSourceOnFindFailureを有効にしたこと、waitForDisplayedで待機を入れたことです。

printPageSourceOnFindFailureは、要素の検索が失敗したときにXMLのページソースをサーバーのログへ出す設定で、既定はfalseです。次の節で述べるように、Claudeがロケータを直すときの材料になります。

テスト用のプロジェクトには、WebdriverIOを入れておきます。

npm init
npm i --save-dev webdriverio

実行はAppiumサーバーを別ターミナルで起動してから行います。

appium
node --check tests/settings-apps.js
node tests/settings-apps.js

サーバーを起動せずに実行すると、サーバーに接続できないというエラーになります。これはコードの誤りではありません。

ロケータの書き分けをClaudeに任せる

ロケータは、Claudeが最も推測で書きやすい部分です。画面の実物を見ずにボタンのテキストやIDを当てずっぽうで書くと、実行して初めて外れます。

UiAutomator2ドライバーのロケータ戦略は、公式の表で速度のランキングが付いています。

戦略対応するUiAutomatorの仕組み速度の目安
id対応するUiAutomatorの仕組みリソース名の完全一致速度の目安★5
accessibilityId対応するUiAutomatorの仕組みcontent descriptionの完全一致速度の目安★5
className対応するUiAutomatorの仕組みクラス名の完全一致速度の目安★5
-android uiautomator対応するUiAutomatorの仕組みUiSelector速度の目安★4
xpath対応するUiAutomatorの仕組みページソースと同じXMLツリー速度の目安★3

この順位をCLAUDE.mdの優先順位に反映したのが、前の節の規約です。accessibilityIdはReact NativeアプリではaccessibilityLabelの値に当たります。

Claudeには次の使い分けを指示すると、迷いが減ります。

  • アプリ側のコードに触れるなら、まずtestIDやcontentDescriptionを付けてもらい、accessibilityIdかidで取ります
  • Jetpack Composeのテストタグは、リソースIDとして露出させないとidで取れません。ドライバーのmapTestTagToResourceId設定(8.4.0以降)で、外から有効にできます
  • アプリのidにパッケージ名の接頭辞が付かない場合は、disableIdLocatorAutocompletionをtrueにします
  • スクロールして探す動作が要るときは-android uiautomatorのUiScrollableを使います
  • どうしても取れない要素に限ってxpathを使います

-android uiautomatorには注意が一つあります。GoogleがUiSelectorなどの機能をUiAutomatorから外す予定があり、そうなればこの戦略のロケータは動かなくなると、ドライバーの資料に警告があります。新規のテストではUiScrollable以外の用途に増やさないほうが後で楽です。

画面の実物をClaudeに見せる

ロケータの当たり外れを減らす最も確実な方法は、ページソースのXMLをClaudeに読ませることです。xpathは、ページソースAPIが返すものと同じXMLツリーを対象にします。つまりXMLに見えている属性が、そのままロケータの材料です。

手順は次のとおりです。

手順

ロケータをページソースから決める流れ

  1. 1

    エミュレーターで該当の画面を開く

    テストコードの途中までを実行して、対象画面で止めます。

  2. 2

    ページソースを保存する

    WebdriverIOではdriver.getPageSource()の戻り値をファイルに書き出します。

  3. 3

    Claudeに読ませてロケータを決めさせる

    resource-id、content-desc、textのうち、画面内で一意になる属性を選ばせます。

  4. 4

    再実行して当たるか確かめる

    外れたら、失敗時に出力されるXMLを渡して直させます。

ページソースに出てくる属性名は、resource-id、content-desc、text、classなどです。この中から、画面内で値が重複しないものを選ばせるのが基本です。

要素が見つからないときの切り分け

テストが落ちたら、Claudeには「ロケータを書き換える前に原因を分ける」ことを求めます。公式の資料も、要素が見つからない原因を段階的に挙げています。

切り分け

要素が見つからないときの切り分け

  1. QページソースのXMLに、その要素は出ていますか?
    答えが出ていない

    画面遷移の途中かもしれません。待機を入れて、数秒待ってから再検索します。

    答えがはい
  2. Qxpath以外のロケータを使っていますか?
    答えがxpathを使っている

    allowInvisibleElementsやignoreUnimportantViewsの設定を変えて試せます。XPath 2.0の解釈で問題が出る場合は、enforceXPath1をtrueにしてXPath 1.0へ戻せます。

    答えがはい

ロケータの綴りか、対象が存在しない

xpath以外のロケータは、検索をUiAutomatorにそのまま渡します。待機後も見つからないなら、綴りの誤りか、対象が画面に無いかのどちらかです。

ここで、Claudeが勝手にxpathへ逃げないようにするのが、CLAUDE.mdの「xpathを使うときは理由を書く」の役目です。

よくあるつまずき

実行して初めて分かる失敗を、症状から並べます。

  • 接頭辞の付け忘れ: Appium独自のcapabilityにappium:が無いと、サーバーが拒否します。appium:optionsにまとめれば起きません
  • Appium 2とUiAutomator2の組み合わせ: ドライバー5.0.0以降はAppium 3専用です。古いAppiumに新しいドライバーは入りません
  • --allow-insecureの書式: Appium 3ではスコープ接頭辞が必須です。たとえば--allow-insecure=uiautomator2:adb_shellと書きます
  • 並列実行のポート衝突: systemPortを端末ごとに変えないと、ポートが衝突します
  • テスト途中でサーバーが落ちる: テストコードがセッション削除を送ると、UiAutomator2のサーバーも終了します。以後のコマンドは接続エラーになります

この記事の線引きをまとめると

Claudeに書かせて手元で閉じられるのは、capabilitiesの書式、構文、appium:接頭辞、ドライバー要件の確認です。ロケータが画面に当たるか、機種ごとの挙動、待機の長さは、エミュレーターか実機で動かして初めて分かります。

そのため、段階1はClaudeに常時やらせ、段階2以降は人が実行を指示して、失敗時のページソースをClaudeに返す形に分けるのが運用しやすい形です。

テスト実行を完了条件にしてClaudeを止めない方法はagent hookで「テストが通るまで止めない」を検証させるに手順があります。ブラウザ側のE2Eを同じ発想で回す場合はPlaywrightのE2Eテストをhookで自動実行するが参考になります。自前の端末ではなくクラウドの実機で確認する選択肢はBrowserStack MCPでテストを計画・実行・デバッグするで扱っています。

この記事を共有:XはてブLinkedIn