※ 結論だけ知りたい方は、4.結論 をご覧ください 🙏

1.前提とこの記事で解決したい課題

1-1.前提

最近は MagicPod という E2E テスト自動化ツールを触ることが多い。

MagicPod では、複数のテストシナリオを実行(MagicPod ではこれを テスト一括実行/一括テスト実行 と称している。)することができる。また、テスト対象のアプリケーションのアーカイブ(App ファイル)には、MagicPod 上でユニークな連番である、app_file_number を割り当てられ、テスト一括実行 の際にはこの app_file_number を指定することで、任意の App ファイルを指定することもできる。

app_file_number のいちばんカンタンな取得方法は、App ファイルを MagicPod 側にアップロードする際のレスポンスから取得することである。

$ app_file_number=$(./magicpod-api-client upload-app -a <path to app/ipa/apk>)
$ ./magicpod-api-client batch-run \
-t "${token}" -o "${organization}" -p "${project}" -S "${test_settings_number}" \
-s "{\"app_file_number\":\"${app_file_number}\"}"

参考: https://github.com/Magic-Pod/magicpod-api-client#upload-app-run-batch-test-for-the-app-wait-until-the-batch-run-is-finished-and-delete-the-app-if-the-test-passed

1-2.この記事で解決したい課題

しかし、App ファイルを MagicPod 上にアップロードするジョブとテスト一括実行のジョブが独立しているとしたらどうだろうか。App ファイルのアップロードのレスポンスから取得した app_file_number をテスト一括実行の引数として渡すことができない。そのため、指定したい App ファイルの app_file_number の取得方法を新たに用意しなければならない。

あるいは、App ファイルに test など任意の文字列が含まれさえしなければ、最新のもの指定したい場合もあるだろう。この場合はどうやって指定すればよいだろうか。

この記事では、そのような課題に対する解決策のひとつを書き留めたい。

2.環境情報

3.MagicPod のテスト一括実行をおこなう際、任意の App ファイルの app_file_number を指定する

3-1.MagicPod のテスト一括実行をおこなう際、直近にアップロードした App ファイルを指定する

MagicPod Web API を眺めると、/v1.0</{organization_name}/{project_name}/list-files API で、以下のように書かれていることに目がいく。

Get list of app files (.app, .ipa, .apk or .aab) in MagicPod cloud. If no app files uploaded, app_files parameter in response will have an empty array.

さっそく、app_files 配列の中身がどういう構造となっているのか? /v1.0</{organization_name}/{project_name}/list-files API を叩いてみよう。すると、以下のようなレスポンスが返ってくる。

$ curl -sS -X GET https://app.magicpod.com/api/v1.0/${ORGANIZATION}/${PROJECT}/list-files/ \
> -H "Authorization: Token ${TOKEN}" | \
> jq -r
{
  "app_files": [
    {
      "app_file_name": "<xxx1>",
      "app_file_number": 333(数字は書き換えている。以降では書き換えている旨は記載しない。),
      "app_file_created": <0000(アップロードされた日時のタイムスタンプと推測)>
    },
    {
      "app_file_name": "<xxx2>",
      "app_file_number": 332,
      "app_file_created": <0000(アップロードされた日時のタイムスタンプと推測)>

上記のレスポンスの app_files 配列内の、app_file_numberapp_file_name の並びから、最新の App ファイルは最後尾にあると考えてよさそうだ。実際、0 番目(先頭)に位置する app_file_number は、テストケースを実行する際「最後にアップロードしたファイル」を選択した場合に使われる App ファイルの隣に記載された数字と一致していた。

このことから、直近にアップロードされた App ファイルを指定するには、app_files 配列の 0 番目(先頭)に位置する app_file_numberを指定すればよいだろう。

app_files 配列の 0 番目(先頭)に位置する app_file_numberを指定する方法はいくつかあると思うが、jq ではこのように書くことができる。

$ curl -sS -X GET https://app.magicpod.com/api/v1.0/${ORGANIZATION}/${PROJECT}/list-files/ \
> -H "Authorization: Token ${TOKEN}" | jq -r '.app_files[0].app_file_number'
333

ちなみに、直近にアップロードされた App ファイルを指定するだけでよければ、わざわざ上記の方法ではなくても、app_file_number を渡さず CrossBatchRun API を POST するだけでもできる。

CrossBatchRun API と似たようなものとして、BatchRun API があるのだが、こちらは非推奨とのこと。

参考: /org/proj/batch-run の API を画面上で非推奨にする · Issue #588 · Magic-Pod/japanese-issue-and-doc

ところで、数ある App ファイルの指定方法のうち、最初に、app_files 配列の 0 番目(先頭)に位置する app_file_numberを指定する方法をとりあげた理由は、以下の 2 点である。

  • やりかたがカンタンそうだから。
  • またやりかたがカンタンそうであるがために、いちはやく任意の App ファイルを指定するために使う /v1.0</{organization_name}/{project_name}/list-files API を叩いたときのレスポンスがどんなものか?おさえておくことができるだろうと考えたため。

3-2.MagicPod のテスト一括実行をおこなう際、任意の文字列を含む App ファイルを指定する

※ 例として、something という任意の文字列を含む app_file_name のうち、最後にアップロードされた app_file_name と同じ配列の app_file_number を指定する。

jq を使うと以下の 2 つの書き方ができる。

両者の類似点と相違点

  • 類似点は、どちらも検索結果に応じて boolean 値を返却すること。
  • 相違点は、検索方法にある。前者は文字列があるかないかの単純な比較であり、後者は正規表現も使えること。

具体的にはこのように書く。

select 関数と contains 関数を使った場合

$ FILTER=something
$ curl -sS -X GET https://app.magicpod.com/api/v1.0/${ORGANIZATION}/${PROJECT}/list-files/
>  -H "Authorization: Token ${TOKEN}" | \
> jq -r '.app_files[] | select(contains({app_file_name: "'${FILTER}'"}))'
333
332

select 関数と test 関数を使った場合

$ curl -sS -X GET https://app.magicpod.com/api/v1.0/${ORGANIZATION}/${PROJECT}/list-files/ \
> -H "Authorization: Token ${TOKEN}" | \
> jq -r '.app_files[] | select(.app_file_name | test(".*'${FILTER}'.*")) | .app_file_number'
333
332

あとは、app_file_number 郡を配列として受け取って、0 番目を指定すれば ok

$ curl -sS -X GET https://app.magicpod.com/api/v1.0/${ORGANIZATION}/${PROJECT}/list-files/ \
> -H "Authorization: Token ${TOKEN}" | \
> jq -r '[.app_files[] | select(.app_file_name | test(".*'${FILTER}'.*")) | .app_file_number][0]'
333

参考: https://stedolan.github.io/jq/manual/#test(val),test(regex;flags)

test 関数に渡す変数は正規表現も受け付ける! すごい!!

$ cat dummy.json
{
  "app_files": [
  {
    "app_file_name": "xxx-app-147",
    "app_file_number": 330,
    "app_file_created": 1234567891
  }
  {
    "app_file_name": "xxx-app-139",
    "app_file_number": 329,
    "app_file_created": 1234567891
  }
  {
    "app_file_name": "dummy-xxx-app-139",
    "app_file_number": 329,
    "app_file_created": 1234567891
  }
}
$ FILTER=^xxx-app-1[3-4]9
$ cat dummy.json | \
> jq -r '.app_files[] | select(.app_file_name | test("'${FILTER}'"))'
{
  "app_file_name": "xxx-app-139",
  "app_file_number": 329,
  "app_file_created": 1234567891
}
$ FILTER=^xxx-app-13[79]
$ cat dummy.json | \
> jq -r '.app_files[] | select(.app_file_name | test("'${FILTER}'"))'
{
  "app_file_name": "xxx-app-139",
  "app_file_number": 329,
  "app_file_created": 1234567891
}

3-3.MagicPod のテスト一括実行をおこなう際、任意の文字列を含まない App ファイルを指定する

今度は逆に任意の文字列を含まない App ファイルを指定してみよう。

※ 例として、foo という任意の文字列を含まない app_file_name のうち、最後にアップロードされた app_file_name と同じ配列の app_file_number を指定する。

書き方としては、3-2.MagicPod のテスト一括実行をおこなう際、任意の文字列を含む App ファイルを指定する で紹介した方法に、not で boolean 値を反転させるだけである。

select 関数と contains 関数を使った場合

$ IGNORED=foo
$ curl -sS -X GET https://app.magicpod.com/api/v1.0/${ORGANIZATION}/${PROJECT}/list-files/ \
> -H "Authorization: Token ${TOKEN}" | \
> jq -r '[.app_files[] | select(contains({app_file_name: "${IGNORED}"})|not) | .app_file_number][0]'
99

select 関数と test 関数を使った場合

$ IGNORED=foo
$ curl -sS -X GET https://app.magicpod.com/api/v1.0/${ORGANIZATION}/${PROJECT}/list-files/ \
> -H "Authorization: Token ${TOKEN}" | \
> jq -r '[.app_files[] | select(.app_file_name | test("'${IGNORED}'")|not) | .app_file_number][0]'
99

not を使う場合も test 関数に渡す値は正規表現も受け付けることがわかった!

$ IGNORED=^xxx-app-13[79]
$ cat dummy.json | \
> jq -r '.app_files[] | select(.app_file_name | test("'${IGNORED}'")|not)'
{
  "app_file_name": "xxx-app-147",
  "app_file_number": 330,
  "app_file_created": 1234567891
}
{
  "app_file_name": "dummy-xxx-app-139",
  "app_file_number": 329,
  "app_file_created": 1234567891
}

4.結論

任意の App ファイルの指定方法を、IGNORED という変数を含まず、かつ FILTER という変数を含む app_file_nameapp_files の配列から 0 番目の app_file_number を指定することとした場合、以下のように jq の test 関数を使って書くのがよいだろう。

contains 関数ではなく test 関数を推す理由は、上述したとおり正規表現が使えるためである。

$ IGNORED=.*foo.*
$ FILTER=^xxx-app-13[0-9]
$ curl -sS -X GET https://app.magicpod.com/api/v1.0/${ORGANIZATION}/${PROJECT}/list-files/ \
> -H "Authorization: Token ${TOKEN}" | \
> jq -r '[.app_files[] | select(.app_file_name | test("'${IGNORED}'")|not) | select(.app_file_name | test("'${FILTER}'"))][0]'

5.小ネタ

5-1.指定する配列のインデックスの変数化

これまではレスポンスを jq で該当の配列のインデックを 0 番目などとベタで指定していたが、実はこれも変数として扱うことができる。

以下のように --argjson オプションを渡すだけだ。

$ number=0
$ cat dummy.json | \
> jq -r '[.app_files[] | select(.app_file_name | test("'${FILTER}'")) | select(.app_file_name | test("'${IGNORED}'")|not)]' | jq -r --argjson index ${number} '.[$index]'
{
  "app_file_name": "xxx-app-147",
  "app_file_number": 330,
  "app_file_created": 1234567891
}

app_file_number だけ取得したい場合、このように書ける。

$ number=0
$ cat dummy.json | \
> jq -r '[.app_files[] | select(.app_file_name | test("'${IGNORED}'")|not)]' | jq -r --argjson index ${number} '.[$index].app_file_number'
330

参考: json - How I pass argument as index to jq? - Stack Overflow

5-2.テスト一括実行時に変数を渡す

この記事のテーマとずれるが、使いがちなので紹介したい。

テスト一括実行時に変数を渡すには、shared_variables を使う。shared_variables には、key value secret の 3 つのパラメータがある。secret とは、ログで value をマスク化するかしないか boolean 値で指定するパラメータである。

一例として、Magic-Pod/magicpod-api-client を使った場合、このように書く。

$ YOUR_VARIABLE=hoge
$ ./magicpod-api-client batch-run \
-t "${TOKEN}" -o "${ORGANIZATION}" -p "${PROJECT}" \
> -S "${TEST_SETTINGS_NUMBER}" \
-s "{\"app_file_number\":\"${APP_FILE_NUMBER}\", \"shared_variables\": [{\"key\":\"YOUR_VARIABLE\",\"value\":\"${YOUR_VARIABLE}\",\"secret\":false}]}"

MagicPod のテストケース側で渡した変数の値を参照するには、参照したいテストケースのコマンドで ${YOUR_VARIABLE} と書けばよい。

参考: 変数の活用 – MagicPod ヘルプセンター

6.参考