MagicPod の公式ドキュメント「CircleCI との連携」では、CircleCI 上から MagicPod のクラウド一括実行を起動し、成功・失敗でジョブを止めるまでの手順が説明されています。
ただしCircleCIと連携する際に、各テストケースの結果を CircleCI 側に記録して、Tests タブや Test Insights で扱う方法までは紹介されていません。これはMagic Podで実行したテスト結果を、CircleCIが読み込める形(JUnix XML)に変換する必要があるためです。
本記事では、公式手順を完了した状態をベースに、結果の取得と JUnit XML 化するスクリプトを追加し、CircleCI の Test Insights で MagicPod の結果を扱えるようにする方法を紹介します。
この記事で実現すること
CircleCI のジョブ詳細にある Tests タブに MagicPod のケースごとの結果を表示できるようになります。これにより CircleCIダッシュボードやAPI / MCPからテストの成功率等が分析できます。2 つ目は、テスト結果(生 JSON と JUnit XML)をアーティファクトとして残すことです。
本記事は、MagicPod 公式の「CircleCI との連携」手順を完了していることを前提とします。あわせて、次の前提条件を確認してください。
- 契約プランで MagicPod のクラウド一括テスト実行が利用できること(利用できないプランでは実行時に
403 Forbiddenが返ります) -
MAGICPOD_API_TOKEN/MAGICPOD_ORGANIZATION/MAGICPOD_PROJECTが CircleCI 側で参照できること - API トークンは CircleCI プロジェクトの環境変数に Secret として登録し、リポジトリにコミットしないこと
検証は 2026 年 6 月時点の MagicPod 公式ドキュメント・API リファレンス、および magicpod-api-client のソースコードに基づいています。
なぜ変換が必要か
MagicPod が JUnit XML を直接出力できるかどうかは、実行形態によって異なります。
| 実行形態 | JUnit XML |
|---|---|
| MagicPod Desktop(ローカル PC) | 設定ファイルの xmlTestOutput: true で出力可能(主に Jenkins 連携用) |
| クラウド一括実行(CircleCI から起動) | JUnit XML 出力オプションはない(2026 年 6 月時点の公式ドキュメント・API リファレンスで確認) |
クラウド実行の結果は、MagicPod Web API の GET /{組織名}/{プロジェクト名}/batch-run/{batch_run_number}/ で JSON として取得できます。各テストケースの status(succeeded / failed / aborted / unresolved など)、実行時間(duration_seconds)、ケース名はこの JSON に含まれます。
したがって CircleCI へ取り込む流れは 4 ステップになります。
まず公式手順どおりテストを実行します。そして完了後に同じ CLI で結果 JSON を取得するステップを実行します。、その次に JUnit XML への変換を実施し、最後に store_test_results ステップで CircleCI に保存します。
Magic Podでのテスト実行等は公式の CLI を活かす形とし、テスト結果を利用することにフォーカスします。
全体像
CircleCI ジョブの構成は次のようになります。公式部分(実行)と本記事の追加部分(記録)が分離している点がポイントです。
[CircleCI ジョブ]
│
├─ checkout
├─ run_magicpod_test.sh(公式) … batch-run -S で実行・待機
│ └─ magicpod-api-client(Linux 用をダウンロード)
│
├─ 結果収集(本記事で追加)
│ ├─ get-batch-run -b <番号> → batch_run_result.json
│ └─ magicpod_to_junit.py → test-results/magicpod/results.xml
│
├─ store_test_results(path: test-results)
└─ store_artifacts(path: test-results) … 任意
JUnit XML の <testcase> には、パターン名(ブラウザ・端末などの実行設定名)を classname 属性として入れます。Test Insights 上で実行設定ごとに結果が分かれて見やすくなるためです。
Step 1: リポジトリに変換スクリプトを追加する
リポジトリ内に scripts/magicpod_to_junit.py を作成します。MagicPod の batch-run JSON を読み、JUnit XML に変換するスクリプトで、Python 3 標準ライブラリのみで動作します。
実行時間は JSON の duration_seconds をそのまま使います(タイムスタンプからの自前計算は不要です)。また、テストケース名に " などが含まれても XML が壊れないよう、属性値は quoteattr でエスケープします。
#!/usr/bin/env python3
"""MagicPod batch-run JSON を JUnit XML に変換する。"""
import json
import sys
from xml.sax.saxutils import escape, quoteattr
def main(in_path, out_path):
with open(in_path) as f:
data = json.load(f)
suites = []
for detail in data.get("test_cases", {}).get("details", []):
# pattern_name は null になり得るためフォールバックする
classname = (
detail.get("pattern_name")
or data.get("test_setting_name")
or "MagicPod"
)
cases, failures, errors = [], 0, 0
for r in detail.get("results", []):
name = r["test_case"]["name"]
url = r["test_case"].get("url", "")
# duration_seconds も null になり得るため 0.0 にフォールバック
t = r.get("duration_seconds") or 0.0
status = r["status"]
body = ""
if status == "failed":
failures += 1
body = (
f'<failure message="MagicPod test failed">'
f"{escape(url)}</failure>"
)
elif status == "aborted":
errors += 1
body = f'<error message="aborted">{escape(url)}</error>'
elif status == "unresolved":
# 自動修復(self-healing)発生。成功扱いにしつつ記録を残す例
body = (
f"<system-out>unresolved (self-healing): "
f"{escape(url)}</system-out>"
)
cases.append(
f" <testcase classname={quoteattr(classname)} "
f'name={quoteattr(name)} time="{t:.3f}">{body}</testcase>'
)
suites.append(
f"<testsuite name={quoteattr(classname)} tests=\"{len(cases)}\" "
f'failures="{failures}" errors="{errors}">\n'
+ "\n".join(cases)
+ "\n</testsuite>"
)
with open(out_path, "w") as f:
f.write(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<testsuites>\n"
+ "\n".join(suites)
+ "\n</testsuites>\n"
)
if __name__ == "__main__":
main(sys.argv[1], sys.argv[2])
このスクリプトは test_cases.details の各実行設定をたどり、その中の results を 1 件ずつ <testcase> に変換します。pattern_name と duration_seconds はいずれも値が入らない(null になる)ことがあるため、or でフォールバックしています。status が failed のときは <failure>、aborted のときは <error> を本文に入れ、本文には MagicPod のケース結果ページ URL(test_case.url)を記録して、失敗時にすぐ該当ケースへ飛べるようにしています。
unresolved をジョブ失敗に含めるかは運用ポリシー次第です。上記は JUnit 上は成功ケースとして記録し、本文に self-healing の発生を残す例にしています。終了コードとの整合については Step 2 で扱います。
Step 2: 公式スクリプトに「結果収集」を足す
公式の run_magicpod_test.sh の末尾に、結果取得と変換を追加します。ブラウザテストを想定した例です(モバイルの場合も batch-run 以降の流れは同じです)。
#!/bin/bash -e
# --- ここまで公式ドキュメントと同じ ---
OS=linux # CircleCI の Docker 実行環境(amd64)では linux。Arm リソースクラスでは linux_arm64
FILENAME=magicpod-api-client
curl -L "https://app.magicpod.com/api/v1.0/magicpod-clients/api/${OS}/latest/" \
-H "Authorization: Token ${MAGICPOD_API_TOKEN}" \
--output "${FILENAME}.zip"
unzip -q "${FILENAME}.zip"
export MAGICPOD_ORGANIZATION="<MagicPod組織名>"
export MAGICPOD_PROJECT="<MagicPodプロジェクト名>"
LOG_FILE="test-results/magicpod/magicpod-batch.log"
mkdir -p test-results/magicpod
# テスト実行(ログを残すため tee を使用)
set +e
./magicpod-api-client batch-run -S "<設定番号>" | tee "${LOG_FILE}"
CLI_EXIT=${PIPESTATUS[0]}
set -e
# --- ここから本記事の追加部分 ---
# batch_run_number をログから取得(例: "#50 wait until 1 tests...")
BATCH_RUN_NO=$(grep -oE '#[0-9]+ wait' "${LOG_FILE}" | head -1 | grep -oE '[0-9]+')
if [ -z "${BATCH_RUN_NO}" ]; then
# フォールバック。同一プロジェクトで並行実行があると別の run を拾う可能性がある点に注意
BATCH_RUN_NO=$(./magicpod-api-client latest-batch-run-no)
fi
echo "batch_run_number: ${BATCH_RUN_NO}"
./magicpod-api-client get-batch-run -b "${BATCH_RUN_NO}" \
> test-results/magicpod/batch_run_result.json
python3 scripts/magicpod_to_junit.py \
test-results/magicpod/batch_run_result.json \
test-results/magicpod/results.xml
# unresolved(終了コード 2)をジョブ成功として扱う場合は、ここで 2 を 0 に変換する
# if [ "${CLI_EXIT}" = "2" ]; then CLI_EXIT=0; fi
exit "${CLI_EXIT}"
このスクリプトでは、テストの成否判定は従来どおり batch-run の終了コードに従います。JUnit XML の生成はその後に行うため、テストが失敗していても XML は生成されます。batch_run_number は CLI の待機ログに出力される #<番号> wait until ... から取得し、取得できない場合は latest-batch-run-no にフォールバックします。
get-batch-run サブコマンドは、API レスポンス相当の JSON を標準出力に出力します(CLI 内部の構造体を経由するため、CLI が解釈しないフィールドは含まれません)。curl で直接 Web API を叩く方法でも構いません。
batch-run の終了コードは 0 が成功、1 が失敗、2 が unresolved(自動修復が発生)の 3 値です。Step 1 のスクリプトは unresolved を JUnit 上は成功として記録するため、終了コード 2 をそのままジョブ失敗として扱うと「ジョブは失敗、Tests タブは全パス」という不整合が起きます。unresolved を成功とみなすなら上記コメントのように終了コード 2 を 0 に変換し、失敗とみなすならスクリプト側で unresolved を <failure> として扱ってください。終了コードと JUnit の扱いは必ずセットで決めます。
Step 3: CircleCI 設定に store_test_results を追加する
公式ドキュメントのジョブ定義に、テスト結果とアーティファクトの保存を追加します。run ステップで run_magicpod_test.sh を実行した後に、結果を CircleCI に渡します。
magic-pod-e2e-test:
steps:
- checkout
# ... デプロイやビルドなど、既存の前処理 ...
- run:
name: MagicPod E2E テストと結果収集
command: bash run_magicpod_test.sh
- store_test_results:
path: test-results
- store_artifacts:
path: test-results
2 つのステップの役割は次のとおりです。store_test_results は JUnit XML を Tests タブと Test Insights に取り込みます。store_artifacts は生 JSON と XML をジョブ画面からダウンロード可能にします(任意)。
store_test_results と store_artifacts は、先行ステップが失敗していても実行されます(ジョブがキャンセルされた場合やランタイムのタイムアウトに達した場合を除く)。store_artifacts には暗黙的に when: always が付与されています。このため、直前の run が失敗しても、XML が生成されていれば Tests タブに表示されます。
Step 4: 環境変数を確認する
公式ドキュメントで設定済みの変数に加えて、次の 3 つを確認します。
| 変数名 | 用途 |
|---|---|
MAGICPOD_API_TOKEN |
API トークン(MagicPod の API トークン画面) |
MAGICPOD_ORGANIZATION |
組織名(URL の /組織名/プロジェクト名/) |
MAGICPOD_PROJECT |
プロジェクト名 |
run_magicpod_test.sh 内で export するか、CircleCI の Project Settings → Environment Variables に登録してください。MAGICPOD_API_TOKEN は Secret として登録します。
Step 5: パイプラインを実行して確認する
設定が完了したら、対象ブランチを push してジョブを実行し、結果を確認しましょう。
- 対象ブランチを push し、MagicPod E2E ジョブを実行する
- ジョブの完了後、ジョブ詳細の Tests タブを開く
- MagicPod のテストケース名が一覧表示されることを確認する
- 必要に応じて Artifacts から
batch_run_result.jsonとresults.xmlをダウンロードする
取り込み元となる MagicPod のクラウド一括実行(例: 一括実行 #50)です。1 ケースが成功し、ブラウザは Chrome 141・実行環境はクラウドであることが分かります。
ジョブが成功すると、Tests タブの Testing Overview に MagicPod の結果が取り込まれます。下の例では Total tests 1 / Passed 1 / Failed 0 となり、MagicPod 側の成功件数と一致しています。
Test Insights のデータは最大 24 時間遅れて反映されます(画面の「Data delayed up to 24 hours.」)。反映後は、実行が 1 回でも対象テストが一覧に表示され、フレーキーテスト・失敗傾向・最も遅いテストを確認できます。フレーキー検出や失敗傾向は直近 100 件のワークフロー実行を基に算出されるため、実行を重ねるほど分析の精度が上がります。
store_artifacts を有効にしている場合、Artifacts タブから生 JSON と JUnit XML をダウンロードできます。test-results/magicpod/ 配下に batch_run_result.json と results.xml が出力されます。
ダウンロードした results.xml は次のような内容です。<testcase> の classname には MagicPod のパターン名(この例では既定の「実行設定」)、name には MagicPod 上のケース名がそのまま入ります。
変換元の batch_run_result.json も確認できます。test_cases.details[].results[] の各ケースが <testcase> に対応し、pattern_name が classname、test_case.name が name、duration_seconds が time に対応します。
(任意)既存の batch-run だけを参照して試す
クラウド実行のプラン制限やデモ用途で、新規実行をせず過去の一括実行結果だけを取り込みたい場合は、環境変数 MAGICPOD_BATCH_RUN_NUMBER を使います。
# batch-run をスキップし、既存 run の JSON だけ取得する例
export MAGICPOD_BATCH_RUN_NUMBER="50"
./magicpod-api-client get-batch-run -b "${MAGICPOD_BATCH_RUN_NUMBER}" \
> test-results/magicpod/batch_run_result.json
python3 scripts/magicpod_to_junit.py \
test-results/magicpod/batch_run_result.json \
test-results/magicpod/results.xml
CircleCI ジョブでは、run ステップの environment に値を指定できます。MAGICPOD_BATCH_RUN_NUMBER に既存の一括実行番号を渡すと、その結果だけを Test Insights に取り込めます。
- run:
name: 既存 MagicPod 結果を Test Insights へ取り込み
command: bash run_magicpod_collect_results.sh
environment:
MAGICPOD_ORGANIZATION: "<組織名>"
MAGICPOD_PROJECT: "<プロジェクト名>"
MAGICPOD_BATCH_RUN_NUMBER: "50"
対応する MagicPod の結果ページ URL は、取得した batch_run_result.json の url フィールドにそのまま含まれています(形式: https://app.magicpod.com/<組織名>/<プロジェクト名>/batch-run/<番号>/)。
まとめ
MagicPod のクラウド一括実行には JUnit XML の出力オプションがありませんが、公式 CLI でテストを実行し、同じ CLI の get-batch-run で結果 JSON を取得して JUnit XML に変換することで、CircleCI の Test Insights に MagicPod の結果を載せられます。テストの起動は公式手順に任せ、本記事の手順は結果の記録専用として追加するだけで導入できます。
| 段階 | やること |
|---|---|
| 公式(済) |
run_magicpod_test.sh + batch-run -S でクラウド E2E を実行 |
| Step 1 |
magicpod_to_junit.py で JSON → JUnit XML |
| Step 2 |
get-batch-run で JSON 取得し、公式スクリプトの末尾に変換を追加 |
| Step 3 |
store_test_results / store_artifacts を CircleCI に追加 |
| 確認 | ジョブの Tests タブに MagicPod ケースが表示される |
今回はブラウザテストを例にしましたが、batch-run 以降の流れはモバイルテストでも同じです。Test Insights に結果が蓄積されれば、フレーキーテストの検出や失敗傾向の分析にもそのまま活用できます。






