AWS SDKを使いDeviceFarmでのテストをスケジューリングする

概要

AWS SDKを使用してDeviceFarmへAPKとテストコードをアップロードし、Calabashによるテストを実行してみたのでそのメモ。

APIドキュメントと使用するSDK

ドキュメントは以下を参照した。

また、SDKにはAWS SDK for Ruby - Version 2を使用する。

AWS SDK Clientの作成

clientを作成します。IAMでAWSDeviceFarmFullAccessのポリシーをアタッチしたユーザを用意し、そのAccessKeyIdとSecretAccessKeyを使用する。また、regionにはus-west-2を指定する。

require 'aws-sdk'

devicefarm = Aws::DeviceFarm::Client.new(
    region: 'us-west-2',
    credentials: Aws::Credentials.new("YOUR_ACCESS_KEY_ID", "YOUR_SECRET_ACCESS_KEY"),
)

DeviceFarmのプロジェクト情報を取得

まず試しにDeviceFarmで作成したプロジェクトの情報を取得してみる。プロジェクトを作成していない場合DeviceFarmのコンソールから「Create a new project」をクリックしプロジェクトを作成しておく。

プロジェクト情報は以下のコードで取得できる。

devicefarm.get_project({
    arn: "プロジェクトのARN"
})

ここで、プロジェクトのARNは以下のようになる。

arn:aws:devicefarm:us-west-2:<ポリシーをアタッチしたユーザーID>:project:<DeviceFarmのプロジェクトID>

DeviceFarmのプロジェクトIDは、DeviceFarmでプロジェクトを開いた際のURLの以下の部分を使用する。

https://us-west-2.console.aws.amazon.com/devicefarm/home?region=us-west-2#/projects/この部分/runs

DevicePoolの取得

デバイスプールについて

テストを実行するデバイスの種類は、デバイスプールと呼ばれる単位で管理されている。デバイスプールをAWS SDKから作成することは出来るが、コンソールから作成した方がわかりやすい。

コンソールのプロジェクトページに表示される「Create a new run」をクリックすると新しいRun(テスト対象APKやデバイスの種類、テストケースを紐付けたもの)を作成することができるので、一度作成しRunを実行しておく。AWSのブログ記事が実行まで詳細に書いてあるので参考にした。

AWS SDKでのDevicePoolの取得

以下のコードでDevicePoolを取得でき、レスポンスにはDevicePoolのARNなどの情報が含まれる。今回はレスポンスに含まれる最初のDevicePoolを使用する。ドキュメントはここ

resp = devicefarm.list_device_pools({
    arn: "プロジェクトのARN"
})

# DevicePoolのARN(後ほど使用)
device_pool_arn = resp.device_pools[0].arn

テスト対象APKのアップロード

APKのアップロードは2段階の手順を踏む。

  • devicefarm.create_upload()でのinitialize
  • Pre-Signed URLを使用したAPKアップロード

initialize

以下のコードで初期化し、Pre-Signed URLを取得する。また、アップロードするオブジェクトのARNを取得しておく(実行をスケジューリングする際に使用)。ドキュメントはここ

resp = devicefarm.create_upload({
    project_arn: "プロジェクトのARN",
    name: "app-debug.apk",
    type: "ANDROID_APP",
    content_type: "application/octet-stream"
})

# Pre-Signed URLの取得
pre_signed_url = resp.upload.url

# 実行をスケジューリングする際に使用
apk_arn = resp.upload.arn

create_uploadのレスポンスのシンタックスは以下のようになっている。ドキュメントはここ

{
    "Upload": {
        "Arn": "string",
        "ContentType": "string",
        "Created": number,
        "Message": "string",
        "Metadata": "string",
        "Name": "string",
        "Status": "string",
        "Type": "string",
        "Url": "string"
    }
}

statusは、FAILEDINITIALIZEDPROCESSINGSUCCEEDEDの4種類があり、create_uploadを実行した直後は、INITIALIZEDになり、この状態ではDeviceFarmから利用できない。Pre-Signed URLを利用してAPKをアップロードすることでPROCESSINGとなり、その後SUCCEEDEDへ変化する。ここでPROCESSINGの際に不具合が起きるとFAILEDになる。

upload

ここを参考に、以下のようなコードでAPKをアップロードできる。

url = URI.parse(pre_signed_url)
apk = File.open("YOUR_APK_PATH", "rb").read
Net::HTTP.start(url.host) do |http|
    http.send_request("PUT", url.request_uri, apk, {"content-type" => "application/octet-stream"})
end

テストパッケージのアップロード

今回テストにはCalabashを使用するので、Calabashのfeaturesをzipに圧縮してアップロードする。アップロードの手順はAPKのアップロードと同じ。create_uploadのtypeにCALABASH_TEST_PACKAGEを指定する。

resp = devicefarm.create_upload({
    project_arn: "プロジェクトのARN",
    name: "features.zip",
    type: "CALABASH_TEST_PACKAGE",
    content_type: "application/octet-stream"
})

# Pre-Signed URLの取得
pre_signed_url = resp.upload.url

# 実行をスケジューリングする際に使用
calabash_package_arn = resp.upload.arn

url = URI.parse(pre_signed_url)
features = File.open("YOUR_FEATURES_PATH", "rb").read
Net::HTTP.start(url.host) do |http|
    http.send_request("PUT", url.request_uri, features, {"content-type" => "application/octet-stream"})
end

テストの実行

実行のスケジューリング

実行のスケジュールは以下のように行う。ドキュメントはここ

app_arnには、APKをアップロードした際に取得したARNを、device_pool_arnはDevicePoolの取得で取得したARNを、test_package_arnにはCalabashのfeatures.zipをアップロードした際に取得したARNを使用する。

devicefarm.schedule_run({
    project_arn: "プロジェクトのARN",
    app_arn: apk_arn,
    device_pool_arn: device_pool_arn,
    test: {
        type: 'CALABASH',
       test_package_arn: calabash_package_arn
    }
})

APK、Test packageのアップロード完了を待つ

APK、Test packageをアップロードした直後では、それぞれのstatusがSUCCEEDEDになっているとは限らない。このため、定期的にstatusがSUCCEEDEDかどうか確認する手順が必要になる。

get_uploadを使用すると、uploadしたオブジェクトの状態を取得できるので、それを1秒ごとに取得しstatusを確認。そして、APK、Test package両方のstatusがSUCCEEDEDならschedule_runを実行する。

10.times do
    resp_apk = devicefarm.get_upload({
        arn: apk_arn
    })

    resp_test_package = devicefarm.get_upload({
        arn: calabash_package_arn
    })

    if resp_apk.upload.status == "SUCCEEDED" &&
        resp_test_package.upload.status == "SUCCEEDED"

        devicefarm.schedule_run({
            project_arn: "プロジェクトのARN",
            app_arn: apk_arn,
            device_pool_arn: device_pool_arn,
            test: {
                type: 'CALABASH',
                test_package_arn: calabash_package_arn
            }
        })

        exit
    end

    sleep(1)
end

これでスケジューリングされ、DeviceFarm上でCalabashのテストが実行される。

TODO

CircleCIと連携させGitHubへのPushをトリガーにしてDeviceFarmでのテストを自動実行する。

参考