4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Jenkins で Airetest を Device Farm でE2Eテスト実行するCIを作った

Last updated at Posted at 2023-12-20

本記事は、ソフトウェアテストの小ネタ Advent Calendar 2023 20日目の記事です。

概要

AWS Device FarmAppiumをPytestでE2Eテストするにあたって基本的な情報をまとめています。

導入

まずは、一般的な使い方やハマりやすいポイントを解説します。

Console

・プロジェクトを用意後に新しく実行を設定する際に新たにアプリファイルをアップロードかすでにアップロード済みのアプリを指定

スクリーンショット 2022-09-06 10.13.39.png

・テストの種類を指定するセレクトから Appium Python を選んで公式にある通り作成した test_bundle.zip をアップロードかすでにアップロード済みのZipファイルを指定

スクリーンショット 2022-09-06 10.14.11.png

・TestSpecの設定はConfigure画面上で表示されているテキストを編集しても適応されないためEditボタンからEdit your YAML上で編集してSave as Newボタンで保存してから適応

スクリーンショット 2022-09-06 10.15.31.png スクリーンショット 2022-09-06 10.15.58.png スクリーンショット 2022-09-06 10.16.06.png

・テスト対象のデバイスの指定はデフォルトでも問題ないがOSのバージョンを別々に実行したいテストや特定のデバイスを限定して実行したいテストがある場合は前もってdevice poolを作成して実行する

スクリーンショット 2022-09-06 10.16.46.png スクリーンショット 2022-09-06 18.59.54.png

・デフォルトのTestSpecに設定されている$DEVICEFARM_LOG_DIRにテストスクリプトで生成したログやスクショのファイルを保存しておくと実行履歴からテストのFilesタブにあるCustomer Artifactsのリンクからファイルをダウンロードすることができる

スクリーンショット 2022-09-06 19.17.29.png

requirements.txt

公式で

WorkSpace で次のコマンドを実行して、requirements.txt ファイルを生成します。

 $ pip freeze > requirements.txt

と解説があるものの補足がないため初見ではわかりにくい。
すでにローカルでAppiumをPytestで実装済みであれば、このコマンドでインストール済みのpip群を出力できるもののDevice Farmがサポートする環境に適していないpipやバージョンが多く、例えば以下のようなエラーが発生する。

  • ERROR: Could not find a version that satisfies the requirement numpy==1.22.3
  • ERROR: Failed building wheel for PyNaCl
  • CMake Error: CMake was unable to find a build program corresponding to "Ninja". CMAKE_MAKE_PROGRAM is not set. You probably need to select a different build tool.

そのため必要最低限のpip群だけ記述したrequirements.txtを出力するため、PytyonのDockerコンテナのイメージを作ってAppium-Python-Clientをインストールした余計なものが入っていない環境下でpip freeze > requirements.txtを実行してDevice Farmにアップロードしたところ、Device Farm側で用意されるiOS向けの環境では問題なかったもののAndroid向けの環境では上記のCMake Errorが起きて、このエラーの解消はどうしても対応できませんでした。

結論としては、pip freeze > requirements.txtはやらずにPytestのコード上でimportしているライブラリのみ指定すればよく、あとはよしなにDevice Farm側で必要なpip群を自動でインストールしてくれる仕組みになっている。
そのため、Appiumのみであれば

requirements.txt
Appium-Python-Client==2.6.0
pytest==7.1.2

を記載(バージョンはよしなに調整)したrequirements.txtをアップロードすれば、問題なくPytestでAppiumのテストがiOS/Androidの両方で問題なく正常動作しました。

test_bundle.zipの作り方

応用

ここからは、Device Farmを活用したトピックを解説します。

Airtest

ネイティブアプリだけでなくUnityやCocos2dxなどの3DCG APIを用いて作られたアプリにもE2EテストができるAirtestをDevice FarmのAppiumサポートを通して実行することもできました。

Android

参考にした上記のアカツキさんの記事のまま対応してみたところ、TestSpecの$adb_path=$(which adb)

[DeviceFarm] $adb_path=$(which adb)
/tmp/scratchKdJ6cj.scratch/shell-script-ZUpYYv/shell_script.sh: 125: /tmp/scratchKdJ6cj.scratch/shell-script-ZUpYYv/shell_script.sh: =/opt/dev/android-sdk-linux/platform-tools/adb: not found

とエラーになってしまったため、確実に取得できるよう以下のように調整することで回避できました。

testspec_for_android.yml
      - ls -l ./lib/python3.7/site-packages/airtest/core/android/static/
      - rm ./lib/python3.7/site-packages/airtest/core/android/static/adb/linux/adb
      - adb_path=`which adb`
      - echo $adb_path  # 確認できるよう念のため出力しておく
      - cp $adb_path ./lib/python3.7/site-packages/airtest/core/android/static/adb/linux/adb
      - sed -i -e s#^\$ADB#$adb_path.orig#g ./lib/python3.7/site-packages/airtest/core/android/static/adb/linux/adb

iOS

Airtestの公式には詳細がないため、iOSサポートの実装が似ているAltUnityTesterの公式ドキュメント

で解説されている内容がAirtestでもおそらく同様の問題で対応できない模様。

We encountered some problems forwarding the port on iOS devices. This why we only talk about running tests on Android devices. We will update this page and the sample project once we have a solution for iOS.

[翻訳: DeepL]
iOSデバイスでポートの転送に問題が発生しました。このため、Androidデバイスでのテスト実行についてのみ説明しています。iOS用のソリューションができ次第、このページとサンプルプロジェクトを更新します。

そのサンプルプロジェクト

を確認するとローカルで実装する際に参考になるサンプルは豊富で

のプロジェクトでDevice Farmのサンプルとしては不足していてこの内容では実装できなかった。

ただし

などのサンプルでシェルスクリプトでの実行方法や

iproxy 13000 13000 $DEVICE_UDID &

iproxyをデーモン起動できることなどが知れたので参考になる情報が多くありました。

AWS CLI: $ aws devicefarm

こちらの記事で、device farmのコマンドについてわかりやすくまとめられていてとても参考になった。
$ aws devicefarm get-runでコンソール上から操作して実行したテストのリストを取得して実行時間をざっと算出したが、なぜかAn error occurred (ArgumentException) when calling the GetRun operation: Invalid arn ...になってしまってできなかったため残念・・・

テストの実行

schedule-runでテストを実行することができる。
すでにアップロードしているファイルを指定するケースの場合、以下のように各arnを取得してJsonを準備して--cli-input-jsonオプションにセットする。

# すでにアップロードしているアプリファイルのarnを取得する
% aws devicefarm list-uploads --arn arn:aws:devicefarm:us-west-2:$(ACCOUNT_ID):project:$(PROJECT_ID) --region=us-west-2 --type=IOS_APP
{
    "uploads": [
        {
            "arn": "arn:aws:devicefarm:us-west-2:$(ACCOUNT_ID):upload:$(PROJECT_ID)/$(APP_UPLOAD_ID)",
            "name": "PageBasedSample.ipa",
            "created": "2022-10-04T17:23:33.510000+09:00",
            "type": "IOS_APP",
            "status": "SUCCEEDED",
            "url": "https://prod-us-west-2-uploads.s3-us-west-2.amazonaws.com/arn%3Aaws%3Adevicefarm%3Aus-west-2%3A$(ACCOUNT_ID)%3Aproject%3A$(PROJECT_ID)/uploads/arn%3Aaws%3Adevicefarm%3Aus-west-2%3A$(ACCOUNT_ID)%3Aupload%3A$(PROJECT_ID)/$(APP_UPLOAD_ID)/PageBasedSample.ipa?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20221011T043929Z&X-Amz-SignedHeaders=content-type%3Bhost&X-Amz-Expires=86400&X-Amz-Credential=AKIAUJHLTYS5AWNTRO6L%2F20221011%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Signature=$(SIGNATURE_ID)",
            "metadata": "{\"activity_name\":\"\",\"minimum_arm\":6,\"error_type\":null,\"package_name\":\"net.gremito.app.ios.PageBasedSample\",\"sdk_version\":1210,\"files\":{},\"warning_type\":null,\"supported_os\":\"12.1\",\"executable\":\"PageBasedSample\",\"platform\":[\"iPhoneOS\"],\"form_factor\":[1,2]}",
            "contentType": "application/octet-stream",
            "category": "PRIVATE"
        }
    ]
}

# すでにアップロードしている`test_bundle.zip`のarnを取得する
% aws devicefarm list-uploads --arn arn:aws:devicefarm:us-west-2:$(ACCOUNT_ID):project:$(PROJECT_ID) --region=us-west-2 --type=APPIUM_PYTHON_TEST_PACKAGE
{
    "uploads": [
        {
            "arn": "arn:aws:devicefarm:us-west-2:$(ACCOUNT_ID):upload:$(PROJECT_ID)/$(TEST_PACKAGE_UPLOAD_ID)",
            "name": "test_bundle.zip",
            "created": "2022-10-04T17:23:41.907000+09:00",
            "type": "APPIUM_PYTHON_TEST_PACKAGE",
            "status": "SUCCEEDED",
            "url": "https://prod-us-west-2-uploads.s3-us-west-2.amazonaws.com/arn%3Aaws%3Adevicefarm%3Aus-west-2%3A$(ACCOUNT_ID)%3Aproject%3A$(PROJECT_ID)/uploads/arn%3Aaws%3Adevicefarm%3Aus-west-2%3A$(ACCOUNT_ID)%3Aupload%3A$(PROJECT_ID)/$(TEST_PACKAGE_UPLOAD_ID)/test_bundle.zip?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20221011T043947Z&X-Amz-SignedHeaders=content-type%3Bhost&X-Amz-Expires=86400&X-Amz-Credential=AKIAUJHLTYS5AWNTRO6L%2F20221011%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Signature=$(SIGNATURE_ID)",
            "metadata": "{\"valid\":true}",
            "contentType": "application/octet-stream",
            "category": "PRIVATE"
        }
    ]
}

# すでにアップロードしているTestSpecのarnを取得する。
% aws devicefarm list-uploads --arn arn:aws:devicefarm:us-west-2:$(ACCOUNT_ID):project:$(PROJECT_ID) --region=us-west-2 --type=APPIUM_PYTHON_TEST_SPEC  
{
    "uploads": [
        ...
        {
            "arn": "arn:aws:devicefarm:us-west-2::upload:$(TEST_SPEC_UPLOAD_ID)",
            "name": "TestSpec v7.0 for iOS Appium Python (sets Python version 3 as the default)",
            "created": "2021-10-26T00:37:02.386000+09:00",
            "type": "APPIUM_PYTHON_TEST_SPEC",
            "status": "SUCCEEDED",
            "url": "https://prod-us-west-2-uploads-testspec.s3-us-west-2.amazonaws.com/public-yaml-files/appium_119_python_3_ios.yml?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20221011T044001Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86400&X-Amz-Credential=AKIAUJHLTYS5AWNTRO6L%2F20221011%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Signature=$(SIGNATURE_ID)",
            "category": "CURATED"
        },
        ...
    ]
}

# すでに作成しているDevice Poolのarnを取得する
% aws devicefarm list-device-pools --arn arn:aws:devicefarm:us-west-2:$(ACCOUNT_ID):project:$(PROJECT_ID) --region=us-west-2
{
    "devicePools": [
        ...
        {
            "arn": "arn:aws:devicefarm:us-west-2:$(ACCOUNT_ID):devicepool:$(PROJECT_ID)/$(DEVICEPOOL_ID)",
            "name": "iphones",
            "type": "PRIVATE",
            "rules": [
                {
                    "attribute": "ARN",
                    "operator": "IN",
                    "value": "[\"arn:aws:devicefarm:us-west-2::device:$(DEVICE_ID)\"]"
                }
            ]
        }
    ]
}

# `schedule-run`にセットするJsonを作成する
% cat <<EOF > ~/execTest.json
> {
    "projectArn": "arn:aws:devicefarm:us-west-2:$(ACCOUNT_ID):project:$(PROJECT_ID)",
    "appArn": "arn:aws:devicefarm:us-west-2:$(ACCOUNT_ID):upload:$(PROJECT_ID)/$(APP_UPLOAD_ID)",
    "devicePoolArn": "arn:aws:devicefarm:us-west-2:$(ACCOUNT_ID):devicepool:$(PROJECT_ID)/$(DEVICE_POOL_ID)",
    "name": "202210111605",
    "test": {
        "type": "APPIUM_PYTHON",
        "testPackageArn": "arn:aws:devicefarm:us-west-2:$(ACCOUNT_ID):upload:$(PROJECT_ID)/$(TEST_PACKAGE_UPLOAD_ID)",
        "testSpecArn": "arn:aws:devicefarm:us-west-2::upload:$(TEST_SPEC_UPLOAD_ID)"
    },
    "configuration": {
        "locale": "ja_JP"
    }
}
EOF

# テストを実行する
% aws devicefarm schedule-run --region=us-west-2 --cli-input-json file:///Users/gremito/execTest.json 
{
    "run": {
        "arn": "arn:aws:devicefarm:us-west-2:$(ACCOUNT_ID):run:$(PROJECT_ID)/$(RUN_ID)",
        "name": "202210111605",
        "type": "APPIUM_PYTHON",
        "platform": "IOS_APP",
        "created": "2022-10-11T16:06:44.681000+09:00",
        "status": "SCHEDULING",
        "result": "PENDING",
        "started": "2022-10-11T16:06:44.681000+09:00",
        "counters": {
            "total": 0,
            "passed": 0,
            "failed": 0,
            "warned": 0,
            "errored": 0,
            "stopped": 0,
            "skipped": 0
        },
        "totalJobs": 1,
        "completedJobs": 0,
        "billingMethod": "METERED",
        "appUpload": "arn:aws:devicefarm:us-west-2:$(ACCOUNT_ID):upload:$(PROJECT_ID)/$(APP_UPLOAD_ID)",
        "jobTimeoutMinutes": 150,
        "devicePoolArn": "arn:aws:devicefarm:us-west-2:$(ACCOUNT_ID):devicepool:$(PROJECT_ID)/$(DEVICEPOOL_ID)",
        "locale": "ja_JP",
        "radios": {
            "wifi": true,
            "bluetooth": false,
            "nfc": true,
            "gps": true
        },
        "testSpecArn": "arn:aws:devicefarm:us-west-2:$(ACCOUNT_ID):upload:$(PROJECT_ID)/$(TEST_SPEC_UPLOAD_ID)"
    }
}

テスト実行結果の取得

get-runは単一のテスト実行結果の詳細を取得したいときのAPIで、実行結果の一覧を取得するAPIはlist-runsを指定することで取得できた。

% aws devicefarm list-runs --arn arn:aws:devicefarm:us-west-2:$(ACCOUNT_ID):project:$(PROJECT_ID) --region=us-west-2    
{
    "runs": [
        {
            "arn": "arn:aws:devicefarm:us-west-2:$(ACCOUNT_ID):run:$(PROJECT_ID)/$(RUN_ID)",
            "name": "202210111605",
            "type": "APPIUM_PYTHON",
            "platform": "IOS_APP",
            "created": "2022-10-11T16:06:44.681000+09:00",
            "status": "COMPLETED",
            "result": "FAILED",
            "started": "2022-10-11T16:06:44.681000+09:00",
            "stopped": "2022-10-11T16:20:14.277000+09:00",
            "counters": {
                "total": 3,
                "passed": 2,
                "failed": 1,
                "warned": 0,
                "errored": 0,
                "stopped": 0,
                "skipped": 0
            },
            "totalJobs": 1,
            "completedJobs": 1,
            "billingMethod": "METERED",
            "deviceMinutes": {
                "total": 11.22,
                "metered": 4.49,
                "unmetered": 0.0
            },
            "appUpload": "arn:aws:devicefarm:us-west-2:$(ACCOUNT_ID):upload:$(PROJECT_ID)/$(APP_UPLOAD_ID)",
            "jobTimeoutMinutes": 150,
            "devicePoolArn": "arn:aws:devicefarm:us-west-2:$(ACCOUNT_ID):devicepool:$(PROJECT_ID)/$(DEVICEPOOL_ID)",
            "locale": "ja_JP",
            "radios": {
                "wifi": true,
                "bluetooth": false,
                "nfc": true,
                "gps": true
            },
            "testSpecArn": "arn:aws:devicefarm:us-west-2:$(ACCOUNT_ID):upload:$(PROJECT_ID)/$(TEST_SPEC_UPLOAD_ID)"
        },
        {
        ...

Jnekins CI

上記の記事などを参考に次の設定でJenkinsからDvice Farmと連携してCI対応できる。

  1. Device Farmのアクセスを許可するIAM(Identity and Access Management)を準備
  2. JnekinsでDevice Farmのプラグインをインストール
  3. 設定画面に入るとDevice Farmの設定フォームが追加されているため、ダウンロードしたuser_credentials.csvからAKID(Access key ID)とSKID(Secret access key ID)を取得してその設定フォームに記入する
  4. ジョブの設定から作成済みのDevice Farmプロジェクトを選択できるようになる。
スクリーンショット 2022-10-02 12.31.56.png スクリーンショット 2022-10-02 13.01.13.png

注意1: セキュリティ問題

やJnekins上で警告が出ているとおり、現在2022/10/01時点でDevice FarmのJnekinsプラグインがunresolved security vulnerability affectingとなっているため、使用する際には十分検討すること。

対策としては、Jenkinsを起動しているマシンにアクセス制限をかけたセキュアなネットワーク環境にする。
または、一旦プラグインの使用を止めてAWS CLIをJnekinsマシンにインストールして、ジョブのビルド設定でシェルの実行からaws devicefarmを使って連携することでセキュアなCI対応ができる。

注意2: Locale設定問題

Device FarmのコンソールからだとDevice Locale(端末の国籍設定)を変更できるもののJenkinsプラグインの設定ではLocaleの変更が用意されていない。

上記のIssueがあったため試しに手元にMavenビルドできる環境を準備してhardcodeされてる箇所を変更して、make clean compileしてできたプラグインをJenkinsに入れて使ってみても変更されなかった。

そのためデフォルトのen_US以外を設定してテストを実行したい場合は、プラグインの使用をやめてシェルスクリプトで実行に変更して、上記の$ aws devicefarm schedule-runを用いることで実現できる。
が、アプリファイル・test_bundle.zip・TestSpecをアップロードするcreate-uploadの実行設定とそのレスポンスに入っているarnを以下のように取得して、schedule-runで指定するJsonに設定するshell操作が必要になるため少し手間がかかる。

% aws devicefarm create-upload \
    --region=us-west-2 \
    --project-arn arn:aws:devicefarm:us-west-2:$(ACCOUNT_ID):project:$(PROJECT_ID) \
    --name build/PageBasedSample.ipa \
    --type IOS_APP \
    | jq '.upload.arn'
"arn:aws:devicefarm:us-west-2:$(ACCOUNT_ID):upload:$(PROJECT_ID)/$(IOS_APP_UPLOAD_ID)"

料金

請求管理コンソール - us-east-1.console.aws.amazon.com.png

7, 8月で多く使用したことで途中から1000分無料枠を使い切って従量課金対象に切り替わったのでざっくり計算してみた。今回の他に前にもDevice Farmを別件で少し使ったことがあるため誤差はその分なのか。

スクリーンショット 2022-09-13 14.50.10.png スクリーンショット 2022-09-13 14.50.22.png

Airtestの対応でテストが正常に終わらず実行前に設定したタイムアウトも機能せず実行されたままの状態になるケースがあり、その場合無駄に使用時間が増えて料金も増えてしまうことがあった。
また、device poolで設定したデバイス数 × 1テスト実行の使用時間が計算されるため、調査や初回の導入対応の際は1,2つのデバイス数を設定とよいだろう。

スクリーンショット 2022-09-22 12.49.38.png スクリーンショット 2022-09-22 12.51.18.png

従量課金に切り替わった次の月でメンテナンスのため少し使用してみたところ上記のような料金結果になっていた。
使用時間が約5分近いものの実際には約3分使用した分の料金が計算されており、おそらく実行したテストの時間分ではなく、そのテスト内でデバイスを使用した時間を裏でカウントされたものが約3分だったのではないかと考えられる。

 

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?