AWS Device Farm で Appium を使って Androidネイティブアプリの自動テストを試してみた

2021.06.17

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

いわさです。

以下の記事で独自アプリで独自テストコードを実行する部分まで実施しています。
いよいよ今回は、AWS Device Farmで自動テストを行ってみたいと思います。

テストパッケージの作成

Device Farm で自動テストを実行する流れとしては、以下の流れとなります。

  1. APKをアップロード
  2. テストコードをアップロード
  3. 実行デバイスを選択

テストコードはアップロード時にはパッケージ化し、Zipアーカイブする必要があります。
以下のドキュメントに npm-bundle を使ってパッケージ化する方法が案内されていますのでご参照ください。

APKやテストコードのアップロードとテスト実施

マネジメントコンソールで Create a new run を選択します。

APKファイルをアップロードします。

前回の記事で触れた、パーミッションに管理者機能が含まれる場合はアップロード時のバリデーションで弾かれます。

APKがアップロード出来たら、次は Appium Node.js を選択し zipで用意したテストコードをアップロードします。

テスト設定ファイルのtestフェーズで対象のテストコードを実行しています。
ここは各自のテストコードファイル名に変更する必要があります。
私の場合だとindex.jsに変更しました。

適当な名前をつけて保存します。
このテスト構成ファイルは、Device Farm上に保存され、テストを再度行う際には使い回せるのでわかりやすい名前をつけておくと良さそうです。

次に、テスト対象のデバイスプールを選択します。
デバイスプールとはテスト対象のデバイス群のことです。

Top Devicesはデフォルトで用意されているデバイスプールです。

私はうっかり Top Devices を選んでしまい、毎回テストのたびに合計20分づつデバイス稼働が発生してしまいました。
テストやDeviceFarmの疎通を確認する段階であれば、1台のデバイスプールを使うほうが良いです。

以下がDevice Farmの料金になりますが、デバイス実行時間の合計で分単位で従量課金です。
複数デバイスを意図せず並列実行していると予想の数倍の金額にすぐ達するのでお気をつけください。
1デバイスあたり5分のテストを3台同時に実行すると15分の料金が発生します。

Specify device state ではセンサーなど実環境に依存するデータのシミュレーション値を設定出来ます。
権限の問題があるので、Android と iOSで設定可能項目が違いそうな気がします。

後日このあたりは色々試してようと思います。

最後に実行タイムアウト時間を設定して終了です。
タイムアウト時間は最小5分、最大150分です。

タイムアウト時間の考え方ですが、実行時間が最大タイムアウト時間を超えた場合、そのデバイスでのテスト実行は強制的に停止されます。
なので、テストに必要な時間以上を設定する必要があるのでご注意ください。

AWSのドキュメントでは 20分のテストであれば30分程度を設定するように推奨例の記載がありました。

これで設定は完了です。
テストが実行されます。

テストの実行時間はデバイス毎に異なっており、並列実行されます。

テスト結果の確認とトラブルシューティング

テストが完了しました、オールグリーンです。
テスト結果を確認してみます。

デフォルト設定ではデバイス毎にテスト開始から終了までの動画が取得されます。
動画を取得するかどうかはテスト時のオプションで指定が可能です。

動画を確認してみます。

なんとホーム画面から動いていません。
何かがおかしいです。

Test specification log を見てみましょう。
テスト実行ログが格納されているので、これを見てトラブルシュートします。

2021-06-17T11:26:34.073Z ERROR webdriver: Request failed with status 500 due to unknown error: An unknown server-side error occurred while processing the command. Original error: The application at '/Users/iwasa.takahito/Downloads/app-debug.apk' does not exist or is not accessible

どうやらテストコードのcapabilitiesで指定したAPKパスにアクセス出来ないと言われているようです。
ローカル実行時はADB経由でこのローカルファイルを自動でアップロードし自動テストが開始されていました。
今回はローカルPCに接続しているわけではないのでエラーが出てもおかしくないですね。
他にもCapabilitiesにはテスト実行環境に依存する情報が詰め込まれています。

どうしたら良いでしょうか。
結論からいうと、空のcapabiritiesをオプションに設定してやれば良いです。

const wdio = require("webdriverio");
const assert = require("assert");

// const opts = {
//   path: '/wd/hub',
//   port: 4723,
//   capabilities: {
//     platformName: "Android",
//     platformVersion: "11",
//     deviceName: "Android Emulator",
//     app: "/Users/iwasa.takahito/Downloads/app-debug.apk",
//     appPackage: "com.tak1wa.hellohello",
//     appActivity: ".MainActivity",
//     automationName: "UiAutomator2"
//   }
// };
const opts = {
  path: '/wd/hub',
  port: 4723,
  capabilities: {}
};

async function main () {
  const client = await wdio.remote(opts);
  const txt1 = await client.$(await client.findElement("id", "com.tak1wa.hellohello:id/txtParam1"));
  await txt1.setValue("2");

  const txt2 = await client.$(await client.findElement("id", "com.tak1wa.hellohello:id/txtParam2"));
  await txt2.setValue("3");

  const btn = await client.$(await client.findElement("id", "com.tak1wa.hellohello:id/btnInput"));
  await btn.click();

  const lbl = await client.$(await client.findElement("id", "com.tak1wa.hellohello:id/lblOutput"));
  const value = await lbl.getText();
  assert.strictEqual(value,"5");

  await client.deleteSession();
}

main();

このテストコードでパッケージを再作成&アップロードし、DeviceFarmでテスト実行しましょう。

今度は成功したようです、動画が取得出来ており、ログ上もエラーがありません。

なお、実行時のCPU、メモリ、スレッド数などのパフォーマンスデータが取得可能です。
これは地味にとても良い機能ですね。

まとめ

  • テストコードはパッケージ化しZipファイルでアップロードする
  • WebDriverIOに必要なCapabilitiesオプションは空で設定する
  • 並列実行してもデバイス毎の稼働時間の合計で料金が発生する
  • テスト実行結果として動画やパフォーマンスデータ、実行ログを取得することが出来る