WebdriverIO + Appium + AWS Device FarmでCordova製iOSアプリをテストする(後編)
こんにちは、開発部の中川@Nkznです。
前回はWebdriverIO + AppiumでCordova製iOSアプリをテストする手法について解説しました。これをAWS Device Farmで動かす場合のコツを見つけたので、今回は後編として、こちらを解説します。
AWS Device Farm
AWS Device Farm(以下、Device Farm)はAmazonが管理しているAndroid/iOSデバイスを、リモートで借りて、アプリのテストに利用できるサービスです。
iOSデバイスで自動テストを実行する場合は、次のテストフレームワークがサポートされています。
- Appium Java TestNG
- Appium Java JUnit
- Appium Node.js
- Appium Python
- Appium Ruby
- Calabash
- UI Automation
- XCTest for iOS と AWS Device Farm の使用
- XCTest UI
Node.jsでAppiumを動かすオプションがあるので、これを使えば前回のノウハウがそのまま使えそうですね。
Appiumサーバーの扱いでハマった
まずは公式ドキュメント↓のとおりに進めてみました。
https://docs.aws.amazon.com/ja_jp/devicefarm/latest/developerguide/test-types-ios-appium-node.htmldocs.aws.amazon.com
Device Farmではipaファイルとテストコードのファイル(npm-bundle
コマンドでまとめたtgzから作ったzip)を別々にアップロードする必要があるため、CordovaプロジェクトとWebdriverIOプロジェクトを別々に分けました。
wdio.conf.js
は特に変更なしで、テスト仕様のyamlに npm test
だけを追加しました。
すると、次のようなエラーが起きました。
[DeviceFarm] echo "Start Appium Node test" Start Appium Node test [DeviceFarm] npm test > helloworld@1.0.0 test /private/tmp/scratchBFwg0o.scratch/test-packageRV57J1/node_modules/helloworld > wdio wdio.conf.js Execution of 1 spec files started at 2019-11-12T06:08:30.524Z 2019-11-12T06:08:36.017Z ERROR @wdio/appium-service: Appium exited before timeout (exit code: 2) [35m[HTTP][39m Could not start REST http interface listener. The requested port may already be in use. Please make sure there is no other instance of this server running already.
@wdio/appium-service
がAppiumの起動に失敗しているようです。
Appiumサーバーの適切な起動方法はDevice Farmのほうが詳しい
Device Farmのテスト仕様のyamlをよく見ると、Device Farm側が用意した処理の中で、Appiumコマンドを準備して、Appiumサーバーを立ち上げるところまでが済んでいます。
phases: install: commands: - export APPIUM_VERSION=1.9.1 - avm $APPIUM_VERSION - ln -s /usr/local/avm/versions/$APPIUM_VERSION/node_modules/.bin/appium /usr/local/avm/versions/$APPIUM_VERSION/node_modules/appium/bin/appium.js # ... pre_test: commands: # We recommend starting appium server process in the background using the command below. # Appium server log will go to $DEVICEFARM_LOG_DIR directory. # The environment variables below will be auto-populated during run time. - echo "Start appium server" # The default WDA used is at DEVICEFARM_WDA_DERIVED_DATA_PATH_V1 (Supports versions iOS 12 and below), it is using commit f865d3. See (https://github.com/appium/appium-xcuitest-driver/tree/f865d32e78a5a8a15469bee30ed2f985d378575d) # If you need an older WDA version or need support for node modules, use the WDA at DEVICEFARM_WDA_DERIVED_DATA_PATH_V0. (This version does not suport iOS 12) - >- appium --log-timestamp --device-name $DEVICEFARM_DEVICE_NAME --platform-name $DEVICEFARM_DEVICE_PLATFORM_NAME --app $DEVICEFARM_APP_PATH --udid $DEVICEFARM_DEVICE_UDID --automation-name XCUITest --default-capabilities "{\"usePrebuiltWDA\": true, \"derivedDataPath\":\"$DEVICEFARM_WDA_DERIVED_DATA_PATH_V1\"}" >> $DEVICEFARM_LOG_DIR/appiumlog.txt 2>&1 & # ...
これが先に起動していたため、 @wdio/appium-service
が後から起動してもポートを取れなかったという流れだったようです。
手元と同じ感覚ではダメだった
本来やりたかったのは、手元で実行したときと同じ、次の図のような流れでした。
しかし、どうやら @wdio/appium-service
にAppiumサーバーのハンドリングを任せるのは、Device Farm環境では上手い方法ではないようです。おそらく$DEVICEFARM_******
な環境変数をwdio.conf.jsの中で呼び出す方法もありますが、デフォルトのテスト仕様yamlを大幅に書き換えることになりそうなので、避けたいところです。
また、よく考えてみると、Device Farm環境ではテストコードを実行するNode.jsと、テスト対象のデバイスが、同じホストにあるのかどうかすら、筆者は知らないことに気がつきました。ローカル環境と同じ感覚で進めても上手く行くはずがなかったのです。
Device Farmは、Device Farmに合った形で前処理・後処理を行いながら、適切なパラメータを設定してAppiumサーバーを起動してくれています。これはそのまま活かしたほうがよさそうだと判断しました。
デバイスの扱いをDevice Farmに任せる
Device FarmのAppium Node.js環境に、デフォルトで用意されているテスト仕様yamlを活かすとして、WebdriverIOの設定はどうすればよいのでしょうか。
試行錯誤の末、次のような順番で動かせばよさそうなことがわかりました。
Device Farm側の動きとしては、ほとんどデフォルトのままなので、テスト仕様yamlで追加したのは npm test
だけです。
# 略 # The test phase includes commands that run your test suite execution. test: commands: - echo "Navigate to test source code" - cd $DEVICEFARM_TEST_PACKAGE_PATH/node_modules/* - echo "Start Appium Node test" - npm test # <= 自分で追加したのはここだけ # 略
$ npm test
で実行されるのは $ wdio wdio.conf.js
なので、テストの実施はWebdriverIOに任せる形になります。
Device Farmに合わせてWebdriverIOを設定する
さて、Device Farmの環境に合わせてwdio.conf.jsを編集していくわけですが、キモとして押さえておきたいのは、次の3点です。
- 使用するデバイスはDevice Farm側で決まる
- Device Farm標準の方法で立ち上がったAppiumサーバーは
http://0.0.0.0:4723
に配置される - WebdriverIOはAppiumのことをほとんど知らないまま
http://0.0.0.0:4723
にWebDriver仕様のサーバーがあると信じてテストを実行する
これを踏まえて、前回作成したwdio.conf.jsを編集すると、次のような形になります。
exports.config = { // 略 capabilities: [{ platformName: 'iOS', bundleId: "com.example.appid", autoWebview: true, // $()が繋ぎにいくコンテキストをWebView優先にする // 使用するデバイスの指定はDevice Farm側の設定に任せる }], // services: ['appium'], // Appiumの制御はDevice Farmの標準に任せる port: 4723, // Appiumサーバーのデフォルトポート baseUrl: 'http://0.0.0.0', // Device FarmのAppiumサーバーは0.0.0.0:4723に配置される maxInstances: 1, // 同時に起動できるAppiumサーバーはひとつだけ // 略 }
デバイスの指定がなくなったため、Desired Capabilitiesがだいぶスッキリしましたね。
services
の項目をなくしたので、 @wdio/appium-service
は使われなくなりましたが、 baseUrl
と port
が設定されていれば、そこにWebDriver APIがあると信じてリクエストしてくれるようです。
これで、前述の「Appium + WebdriverIOをAWS Device Farm環境で実行する」のシーケンス図の順で処理が実行されます。
余談:Appiumのバージョンをv1.15.1にしたら失敗した
本記事の執筆時点で、Appiumの最新バージョンはv1.15.1です。一方、Device Farmでデフォルトで使用されているAppiumのバージョンはv1.9.1です。
テスト仕様yamlの中では、次の場所のバージョン表記を変えることで、Appiumのバージョンを指定できます。
phases: install: commands: - export APPIUM_VERSION=1.9.1 # <= ここを変えるとAppiumのバージョンを変えられる - avm $APPIUM_VERSION - ln -s /usr/local/avm/versions/$APPIUM_VERSION/node_modules/.bin/appium /usr/local/avm/versions/$APPIUM_VERSION/node_modules/appium/bin/appium.js
ローカル環境ではv1.15.1で作業していたので、Device Farmでもv1.15.1を使うことにしました。
すると、次のようなエラーが発生しました。
どうやらデバイスが見つからなかったようです。Appiumのバージョンを戻すと元通りに動いたので、ひとまずv1.9.1で運用することにしました。
AppiumのバージョンとXcodeのバージョン(と使用可能な最新のiOSバージョン)に依存があるみたいなので、Device Farmで用意できているXcodeやiOSデバイスのバージョンが、そこまで新しくはないということかなと想像しています。
まとめ
前後編に分けて、Cordova製のiOSアプリにUIの自動テストを導入する場合のツール選択について解説しました。
WebdriverIO + Appiumの記事や、Appium + Device Farmの記事はあったのですが、すべて組み合わせた場合の記事が見つからずにだいぶ苦しみました。何とかやり方が見つかってよかったです。