[AWS] Device FarmをCLIで実行してみた

AWS CLIでDeviceFarmを利用する場合の実行例をまとめました。

他のサービスと比べて実例がまだ少なく、検索しても [CLIリファレンス](http://docs.aws.amazon.com/cli/latest/reference/devicefarm/index.html) ぐらいしか出てこなので、少し長くなりましたが実例をほぼそのまま載せています。

"arn"とリージョンの設定が少しややこしいので注意が必要です。実行結果はawsアカウントIDとSignatureのみ"999999999999"に変更していますが、それ以外は実際に実行した結果になっています。

# 1) プロジェクトの作成

create-projectコマンドで、プロジェクトを作成します。書式と実行結果は以下の通りです。

__``aws devicefarm create-project --region=<対象リージョン> --name <プロジェクト名>``__

```sh
$ aws devicefarm create-project --region=us-west-2 --name device-test-pj-va01
{
    "project": {
        "name": "device-test-pj-va01",
        "arn": "arn:aws:devicefarm:us-west-2:999999999999:project:abbdcdca-9675-4c74-b0ad-c952ca66f2c5",
        "created": 1498811354.048
    }
}
```

作成済みプロジェクトの確認はlist-projectsコマンドで行います。

__``aws devicefarm list-projects --region=<対象リージョン>``__

```sh
$ aws devicefarm list-projects --region=us-west-2
{
    "projects": [
        {
            "name": "device-test-pj-va01",
            "arn": "arn:aws:devicefarm:us-west-2:999999999999:project:abbdcdca-9675-4c74-b0ad-c952ca66f2c5",
            "created": 1498811354.048
        }
    ]
}
```

コンソールでも作成されたことが確認できます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/172826/7922c0fb-36a9-fceb-a821-489290b3dab9.png)

## 注意事項

- DeviceFarmのリージョンですが、今のところ全てus-west-2です。この例では毎回指定してありますが、config設定でデフォルトをus-west-2に設定してあれば指定は必要ありません。
- コンソールを見ていると、URL(parameter)の一部で別のリージョン(以下の例ではap-northeast-1)が入っているのものがあるのですが、CLIで指定しても動きません。エラーメッセージが分かりづらいですがリージョン指定の問題です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/172826/a5c84a76-43a2-d55f-aa8f-fd407dac4eee.png)

```sh
$ aws devicefarm list-projects --region ap-northeast-1
HTTPSConnectionPool(host='devicefarm.ap-northeast-1.amazonaws.com', port=443): Max retries exceeded with url: /
(Caused by NewConnectionError('<requests.packages.urllib3.connection.VerifiedHTTPSConnection object at 0x7f455b534c50>
: Failed to establish a new connection: [Errno -2] Name or service not known',))
```

# 2) DevicePool作成

DevicePoolとはテストを実行するデバイスの一覧です。まず、利用可能なデバイスを取得します。

__``aws devicefarm list-devices --region=<対象リージョン>``__

```sh
$ aws devicefarm list-devices --region=us-west-2 | jq -r '.devices[] |  "\(.name),\(.arn)"'
Samsung Galaxy Tab 3 10.1" (WiFi),arn:aws:devicefarm:us-west-2::device:268066CE839F45A68487E46A25EEF89E
Apple iPhone 5c,arn:aws:devicefarm:us-west-2::device:9EB7F8906B1849D89E261352C630DA03
Samsung Galaxy S6 Edge SM-G925F,arn:aws:devicefarm:us-west-2::device:4D3ADF7212964893BFD8C13E93E26BD5
Apple iPhone 6 Plus,arn:aws:devicefarm:us-west-2::device:CFD07092060E49B08C494177A90B66EC
Motorola DROID Ultra (Verizon),arn:aws:devicefarm:us-west-2::device:B6B524CF9BF84CA891FFEF1C88E9A279
Samsung Galaxy S4 Active (AT&T),arn:aws:devicefarm:us-west-2::device:577DC08D6B964346B86610CFF090CD59
Apple iPhone 7,arn:aws:devicefarm:us-west-2::device:FD9876C6541D4D6186F338207B4881B2
(・・・以下省略)
```

一覧の中から利用するデバイスを選択して、そのarnのリストを含むパラメタjsonを作成します。
ここは抽出、変換をがんばる必要があり、Xperia全てを対象とした場合の例は以下の通りです。

```sh
$ aws devicefarm list-devices --region=us-west-2 | jq -r '.devices[] |  "\(.name),\(.arn)"' | grep Xperia
Sony Xperia Z1 Compact,arn:aws:devicefarm:us-west-2::device:29B5FCDF48794C89A514939415931DAA
Sony Xperia Z3 Compact,arn:aws:devicefarm:us-west-2::device:133050132D324AA8B264180506232FC1
Sony Xperia Z2,arn:aws:devicefarm:us-west-2::device:30D43F76A0B14F0FA162ED6AC7D4B8AE
Sony Xperia Z5,arn:aws:devicefarm:us-west-2::device:72B1388DC6F941A293C00A8CBD557EF6
Sony Xperia Z3,arn:aws:devicefarm:us-west-2::device:2DA046320998446B84407B50BFBD7757
Sony Xperia Z4 Tablet,arn:aws:devicefarm:us-west-2::device:5EE66F887B4D497F939935C2390EF4D3
$ cat testDevices.json
{
    "projectArn": "arn:aws:devicefarm:us-west-2:999999999999:project:d4d3ace3-a2aa-4f5e-80b8-42bdd35e6aa3",
    "name": "my-device-pool1",
    "description": "my-device-pool1",
    "rules":[
        {
            "attribute": "ARN",
            "operator": "IN",
            "value": "[\"arn:aws:devicefarm:us-west-2::device:29B5FCDF48794C89A514939415931DAA\",\"arn:aws:devicefarm:us-west-2::device:133050132D324AA8B264180506232FC1\",\"arn:aws:devicefarm:us-west-2::device:30D43F76A0B14F0FA162ED6AC7D4B8AE\",\"arn:aws:devicefarm:us-west-2::device:72B1388DC6F941A293C00A8CBD557EF6\",\"arn:aws:devicefarm:us-west-2::device:2DA046320998446B84407B50BFBD7757\",\"arn:aws:devicefarm:us-west-2::device:5EE66F887B4D497F939935C2390EF4D3\"]"
     }
    ]
}
```

ここで、"projectArn"は 1) で作成したプロジェクトのarn、"value"は対象のデバイスのarnをコンマ区切りで指定したものです。

jsonが作成できたら、それを引数としてDevicePoolを作成します。

__``aws devicefarm create-device-pool --region=<対象リージョン> --cli-input-json file://<先ほど作成したファイル>``__

```sh
$ aws devicefarm create-device-pool --region=us-west-2 --cli-input-json file://testDevices.json
{
    "devicePool": {
        "rules": [
            {
                "operator": "IN",
                "attribute": "ARN",
                "value": "[\"arn:aws:devicefarm:us-west-2::device:29B5FCDF48794C89A514939415931DAA\",\"arn:aws:devicefarm:us-west-2::device:133050132D324AA8B264180506232FC1\",\"arn:aws:devicefarm:us-west-2::device:30D43F76A0B14F0FA162ED6AC7D4B8AE\",\"arn:aws:devicefarm:us-west-2::device:72B1388DC6F941A293C00A8CBD557EF6\",\"arn:aws:devicefarm:us-west-2::device:2DA046320998446B84407B50BFBD7757\",\"arn:aws:devicefarm:us-west-2::device:5EE66F887B4D497F939935C2390EF4D3\"]"
            }
        ],
        "type": "PRIVATE",
        "name": "my-device-pool1",
        "arn": "arn:aws:devicefarm:us-west-2:999999999999:devicepool:abbdcdca-9675-4c74-b0ad-c952ca66f2c5/08a8c626-1caa-40e7-887e-7ebb43660a9b",
        "description": "my-device-pool1"
    }
}
```

作成済みDevicePoolの確認にはlist-device-poolsコマンドを使用します。引数のarnは、プロジェクトarnです。

__``aws devicefarm list-device-pools --arn <プロジェクトarn> --region=<対象リージョン>``__

```sh
$ aws devicefarm list-device-pools --arn arn:aws:devicefarm:us-west-2:999999999999:project:abbdcdca-9675-4c74-b0ad-c952ca66f2c5 --region=us-west-2
{
    "devicePools": [
        {
            "rules": [
                {
                    "operator": "IN",
                    "attribute": "ARN",
                    "value": "[\"arn:aws:devicefarm:us-west-2::device:29B5FCDF48794C89A514939415931DAA\",\"arn:aws:devicefarm:us-west-2::device:133050132D324AA8B264180506232FC1\",\"arn:aws:devicefarm:us-west-2::device:30D43F76A0B14F0FA162ED6AC7D4B8AE\",\"arn:aws:devicefarm:us-west-2::device:72B1388DC6F941A293C00A8CBD557EF6\",\"arn:aws:devicefarm:us-west-2::device:2DA046320998446B84407B50BFBD7757\",\"arn:aws:devicefarm:us-west-2::device:5EE66F887B4D497F939935C2390EF4D3\"]"
                }
            ],
            "type": "PRIVATE",
            "name": "my-device-pool1",
            "arn": "arn:aws:devicefarm:us-west-2:999999999999:devicepool:abbdcdca-9675-4c74-b0ad-c952ca66f2c5/08a8c626-1caa-40e7-887e-7ebb43660a9b",
            "description": "my-device-pool1"
        }
    ]
}
```

コンソールでも作成されたことが確認できます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/172826/d833f33d-1d9f-b058-2a94-bca7f5e57241.png)

# 3) ファイルのアップロード

テストで使用するファイルをアップロードします。

アップロード対象のファイルは、Androidアプリ(apk)、iOSアプリ(ipa)、Appiumなどのテストコード(zip)ですが、手順は全て同じです。以下はAndroidアプリの実行例です。

## 3-1) アップロードURLの取得

まず、create-uploadコマンドを実行してアップロードURLを取得します。

__``aws devicefarm create-upload --region=<対象リージョン> --project-arn <プロジェクトarn> -name <アップロードするファイル名> --type <アップロードするファイルの種類>``__

```sh
$ aws devicefarm create-upload --region=us-west-2 --project-arn arn:aws:devicefarm:us-west-2:999999999999:project:abbdcdca-9675-4c74-b0ad-c952ca66f2c5 --name app-debug_010-QA.apk --type ANDROID_APP
{
    "upload": {
        "status": "INITIALIZED",
        "name": "app-debug_010-QA.apk",
        "created": 1498814391.555,
        "url": "https://prod-us-west-2-uploads.s3-us-west-2.amazonaws.com/arn%3Aaws%3Adevicefarm%3Aus-west-2%3A056530783435%3Aproject%3Aabbdcdca-9675-4c74-b0ad-c952ca66f2c5/uploads/arn%3Aaws%3Adevicefarm%3Aus-west-2%3A056530783435%3Aupload%3Aabbdcdca-9675-4c74-b0ad-c952ca66f2c5/c6837154-8222-428e-a691-d0673fac7d93/app-debug_010-QA.apk?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20170630T091951Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86400&X-Amz-Credential=AKIAI3XXXXXX7TQL2OZA%2F20170630%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Signature=9999999999999999999999999999999999999999999999999999999999999999",
        "type": "ANDROID_APP",
        "arn": "arn:aws:devicefarm:us-west-2:999999999999:upload:abbdcdca-9675-4c74-b0ad-c952ca66f2c5/c6837154-8222-428e-a691-d0673fac7d93"
    }
}
```

これでファイルがアップロードされたわけではなく、レスポンスの"url"としてアップロード用のURLが取得されただけです。この後実際にアップロードする必要があります。

## 3-2) ファイルのアップロード

curl等でファイルアップロードします。urlに、create-uploadコマンドで返されたS3のURL(長いです)を指定します。

```sh
$ curl -T app-debug_010-QA.apk "https://prod-us-west-2-uploads.s3-us-west-2.amazonaws.com/arn%3Aaws%3Adevicefarm%3Aus-west-2%3A056530783435%3Aproject%3Aabbdcdca-9675-4c74-b0ad-c952ca66f2c5/uploads/arn%3Aaws%3Adevicefarm%3Aus-west-2%3A056530783435%3Aupload%3Aabbdcdca-9675-4c74-b0ad-c952ca66f2c5/c6837154-8222-428e-a691-d0673fac7d93/app-debug_010-QA.apk?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20170630T091951Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86400&X-Amz-Credential=AKIAI3XXXXXX7TQL2OZA%2F20170630%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Signature=9999999999999999999999999999999999999999999999999999999999999999"
```

アップロードが完了すると、コンソールでもファイルが確認できます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/172826/a376abbf-e2c8-178a-e64f-103f70ab51ef.png)

### 注意事項

- 今回の例では、URLをダブルコーテーションで囲まないとエラーになりました。
- curlの他のオプションで実行した場合 "SignatureDoesNotMatch" や、"MissingContentLength/You must provide the Content-Length HTTP header."が出る場合があります ( 参考：https://forums.aws.amazon.com/thread.jspa?threadID=231815 ) 

```sh
curl "https://prod-us-west-2-uploads.s3-us-west-2.amazonaws.com/arn%3Aaws%3Adevicefarm%3Aus-west-2%3A056530783435%3Aproject%3Aabbdcdca-9675-4c74-b0ad-c952ca66f2c5/uploads/arn%3Aaws%3Adevicefarm%3Aus-west-2%3A056530783435%3Aupload%3Aabbdcdca-9675-4c74-b0ad-c952ca66f2c5/c6837154-8222-428e-a691-d0673fac7d93/app-debug_010-QA.apk?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20170630T091951Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86400&X-Amz-Credential=AKIAI3XXXXXX7TQL2OZA%2F20170630%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Signature=9999999999999999999999999999999999999999999999999999999999999999" -X PUT --upload-file /home/azureuser/app-debug_010-QA.apk -H "Content-Type:application/octet-stream;Content-Length:1332683"
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message><AWSAccessKeyId>AAAAAAAAAAAAAAAAAAAA</AWSAccessKeyId><StringToSign>PUT
```

- アップロードされたファイルは、現在DeviceFarmの仕様で、アップロード後30日で自動削除されます。期限を延長する方法は無いので、同じファイルで継続してテストしたい場合でも、30日ごとに再アップロードが必要です。

# 4) テスト実行

プロジェクト、DevicePool、ファイルの準備ができたらschedule-runコマンドでテストを実行します。まず、これまでの結果(から取得されるプロジェクト、DevicePool、アップロードのarn)を集めてパラメタjsonを作成します。

```sh
$ cat execTest.json
{
    "projectArn": "arn:aws:devicefarm:us-west-2:999999999999:project:d4d3ace3-a2aa-4f5e-80b8-42bdd35e6aa3",
    "appArn": "arn:aws:devicefarm:us-west-2:999999999999:upload:d4d3ace3-a2aa-4f5e-80b8-42bdd35e6aa3/78a23ee1-98d0-459a-8391-b3262c15254b",
    "devicePoolArn": "arn:aws:devicefarm:us-west-2:999999999999:devicepool:d4d3ace3-a2aa-4f5e-80b8-42bdd35e6aa3/28c5918c-c0e1-4a70-85a7-b67cf7ba4058",
    "name": "nakatsu-device-test-001",
    "test": {
        "type": "BUILTIN_FUZZ"
    },
    "configuration": {
        "locale": "ja_JP"
    }
}
```

schedule-runコマンドに --generate-cli-skeletonオプションを付けて実行すると、全てのオプションを含んだjsonスケルトンが自動生成されます。これ以外のオプションを指定する場合は、スケルトンとCLIリファレンスを参考に追加して下さい。

```sh
$ aws devicefarm schedule-run --region=us-west-2 --generate-cli-skeleton
```

jsonの準備ができたら実行します。

__``aws devicefarm schedule-run --region=<対象リージョン> --cli-input-json file://<先ほど作成したファイル>``__

```sh
$ aws devicefarm schedule-run --region=us-west-2 --cli-input-json file://execTest.json
{
    "run": {
        "status": "SCHEDULING",
        "name": "nakatsu-device-test-001",
        "created": 1498815355.117,
        "totalJobs": 6,
        "completedJobs": 0,
        "platform": "ANDROID_APP",
        "result": "PENDING",
        "billingMethod": "METERED",
        "type": "BUILTIN_FUZZ",
        "arn": "arn:aws:devicefarm:us-west-2:999999999999:run:abbdcdca-9675-4c74-b0ad-c952ca66f2c5/3a4176dc-d110-41f4-9ac7-c6b162f144fe",
        "counters": {
            "skipped": 0,
            "warned": 0,
            "failed": 0,
            "stopped": 0,
            "passed": 0,
            "errored": 0,
            "total": 0
        }
    }
}
```

進捗はget-runコマンドのレスポンス"status"で確認します。get-runコマンドの結果がCOMPLETEDになれば完了です。

__``aws devicefarm get-run --region=<対象リージョン> --arn <schedule-runコマンドから返されたarn>``__

```sh
$ aws devicefarm get-run --region=us-west-2 --arn arn:aws:devicefarm:us-west-2:999999999999:run:abbdcdca-9675-4c74-b0ad-c952ca66f2c5/3a4176dc-d110-41f4-9ac7-c6b162f144fe | jq -r '.run .status'
RUNNING
$ aws devicefarm get-run --region=us-west-2 --arn arn:aws:devicefarm:us-west-2:999999999999:run:abbdcdca-9675-4c74-b0ad-c952ca66f2c5/3a4176dc-d110-41f4-9ac7-c6b162f144fe | jq -r '.run .status'
COMPLETED
```

コンソールでも結果が確認できます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/172826/5242deac-90ca-9c46-df3d-78b85e8bb331.png)

# 5) 結果の確認

最終結果はget-runコマンドのレスポンス"result"で確認します。

__``aws devicefarm get-run --region=<対象リージョン> --arn <schedule-runコマンドから返されたarn>``__

```sh
$ aws devicefarm get-run --region=us-west-2 --arn arn:aws:devicefarm:us-west-2:999999999999:run:abbdcdca-9675-4c74-b0ad-c952ca66f2c5/3a4176dc-d110-41f4-9ac7-c6b162f144fe
{
    "run": {
        "status": "COMPLETED",
        "name": "nakatsu-device-test-001",
        "created": 1498815355.117,
        "totalJobs": 6,
        "completedJobs": 6,
        "deviceMinutes": {
            "unmetered": 0.0,
            "total": 22.61,
            "metered": 14.65
        },
        "platform": "ANDROID_APP",
        "result": "PASSED",
        "billingMethod": "METERED",
        "type": "BUILTIN_FUZZ",
        "arn": "arn:aws:devicefarm:us-west-2:999999999999:run:abbdcdca-9675-4c74-b0ad-c952ca66f2c5/3a4176dc-d110-41f4-9ac7-c6b162f144fe",
        "counters": {
            "skipped": 0,
            "warned": 0,
            "failed": 0,
            "stopped": 0,
            "passed": 18,
            "errored": 0,
            "total": 18
        }
    }
}
```

結果ファイルの取得には、list-artifactsコマンドを使用します。

__``aws devicefarm list-artifacts --region=<対象リージョン> --arn <schedule-runコマンドから返されたarn> --type <取得するファイルの種類>``__

```sh
$ aws devicefarm list-artifacts --region=us-west-2 --arn arn:aws:devicefarm:us-west-2:999999999999:run:d4d3ace3-a2aa-4f5e-80b8-42bdd35e6aa3/5d4507b3-9d2d-4dfe-9861-5b03bb36d55e --type SCREENSHOT
{
    "artifacts": [
        {
            "url": "https://prod-us-west-2-results.s3-us-west-2.amazonaws.com/999999999999/abbdcdca-9675-4c74-b0ad-c952ca66f2c5/3a4176dc-d110-41f4-9ac7-c6b162f144fe/00000/00001/00000/83e5554a-a6b0-4c07-8156-570278246abf.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20170630T095043Z&X-Amz-SignedHeaders=host&X-Amz-Expires=259200&X-Amz-Credential=AKIAI3XXXXXX7TQL2OZA%2F20170630%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Signature=9999999999999999999999999999999999999999999999999999999999999999",
            "extension": "png",
            "type": "SCREENSHOT",
            "name": "background_screenshot_0",
            "arn": "arn:aws:devicefarm:us-west-2:999999999999:artifact:abbdcdca-9675-4c74-b0ad-c952ca66f2c5/3a4176dc-d110-41f4-9ac7-c6b162f144fe/00000/00001/00000/00000"
        },
        {
            "url": "https://prod-us-west-2-results.s3-us-west-2.amazonaws.com/999999999999/abbdcdca-9675-4c74-b0ad-c952ca66f2c5/3a4176dc-d110-41f4-9ac7-c6b162f144fe/00000/00001/00000/72f21e0b-6fb9-4ac4-ab69-62e6e8365621.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20170630T095043Z&X-Amz-SignedHeaders=host&X-Amz-Expires=259200&X-Amz-Credential=AKIAI3XXXXXX7TQL2OZA%2F20170630%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Signature=9999999999999999999999999999999999999999999999999999999999999999",
            "extension": "png",
            "type": "SCREENSHOT",
            "name": "background_screenshot_1",
            "arn": "arn:aws:devicefarm:us-west-2:999999999999:artifact:abbdcdca-9675-4c74-b0ad-c952ca66f2c5/3a4176dc-d110-41f4-9ac7-c6b162f144fe/00000/00001/00000/00001"
        },
        ※以下省略※
    ]
}
```

--typeオプションに"FILE"または"LOG"を指定すると、実行ログやVideoファイルも取得することができます。また、デバイスやテストが多くなった場合は、list-jobsやlist-runsコマンドで一部（特定デバイスだけの結果など）を取得することもできます。


実際のファイルは、アップロードと同様に"url"から取得できます。以下はcurlの例です。

```sh
$ curl -o test.png "https://prod-us-west-2-results.s3-us-west-2.amazonaws.com/999999999999/d4d3ace3-a2aa-4f5e-80b8-42bdd35e6aa3/5d4507b3-9d2d-4dfe-9861-5b03bb36d55e/00001/00001/00000/67c67b08-65b4-43ce-a6d4-e315e3ad8337.png?AWSAccessKeyId=AKIAI3XXXXXX7TQL2OZA&Expires=1484294431&Signature=9999999999999999999999999999999999999999999999999999999999999999"
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/172826/4d859d7a-1bad-4277-1331-74844a477739.png)

以上で、プロジェクトの作成から実行結果まで一通り確認できました。

# おわりに

この実行結果は、昨年実際のプロジェクトでDevice Farmを使う機会があり、その実行結果をまとめたものです。その時にはAzure側にも [Xamarin Test Cloud](https://developer.xamarin.com/testcloud/) があることを知らず、ここだけawsを使っていました！

というわけで次回は Xamarin Test Cloud についても調べてみたいと思います。