ガイド

専用MacでXCUITestによるiOS UIテストを自動化する

XCUITestを自動化するには、ユーザーがログインしているMacで、シミュレータのdestinationと-resultBundlePathを指定してxcodebuild testを実行します。専用のシミュレータはxcrun simctlで作成します。-parallel-testing-enabled YESを付けるとテストがシミュレータのクローンに分散され、-retry-tests-on-failureを付けると不安定なテストが再実行されます。ここにあるコマンドはすべて、Xcode 26.6とiOS 26.5のシミュレータを入れたMacで実行しました。

最初に必要なもの

  • Xcodeと、iOSのシミュレータランタイムが1つ以上入ったMac。
  • プロジェクト内のUIテストのターゲットと、それをテストする共有スキーム。
  • ログイン済みのデスクトップセッション。UIテストはシミュレータのアプリを操作するので、テストを実行するものはそのセッション内にある必要があります。つまりCIエージェントは、LaunchDaemonではなくLaunchAgentとして起動します。Buildkiteのplistのテンプレートにも同じことが書かれています。GUIモードならXcodeのUIテストができますが、ログインが必要です。

1. Xcodeとランタイムを確認する

xcodebuild -version
sudo xcodebuild -runFirstLaunch
xcrun simctl list runtimes
xcrun simctl list devicetypes | grep iPhone

-runFirstLaunchはパッケージをインストールし、ライセンスに同意します。Xcodeをインストールまたはアップグレードするたびに実行してください。

2. CI専用のシミュレータを作成する

専用のデバイスを使えば、CIと人が手で開いたものを分けられます。bootstatus -bはシミュレータを起動し、準備ができるまで待ちます。

UDID=$(xcrun simctl create "CI iPhone 17" "iPhone 17" com.apple.CoreSimulator.SimRuntime.iOS-26-5)
xcrun simctl bootstatus "$UDID" -b
echo "$UDID"

destinationにはUDIDを使います:-destination "platform=iOS Simulator,id=$UDID"。name=iPhone 17,OS=26.5のように名前でも指定できますが、2つのデバイスが同じ名前を持つことがあります。

3. result bundle付きでUIテストを実行する

rm -rf build/TestResults.xcresult
xcodebuild test \
  -project MyApp.xcodeproj \
  -scheme MyApp \
  -destination "platform=iOS Simulator,id=$UDID" \
  -derivedDataPath build/DerivedData \
  -resultBundlePath build/TestResults.xcresult

result bundleには、すべてのテスト結果、ログ、スクリーンショット、失敗が入っています。コマンドラインで概要を読むには次のようにします。

xcrun xcresulttool get test-results summary --path build/TestResults.xcresult

デバイスごとの成功、失敗、スキップの件数がJSONで出力されます。bundleはditto -c -k --keepParentでzipにし、CIの実行に成果物として添付してください。

4. 一度ビルドして、何度もテストする

ビルドとテストの実行を分けます。そうすれば、再コンパイルせずに一部だけを再実行できます。

xcodebuild build-for-testing -project MyApp.xcodeproj -scheme MyApp \
  -destination "platform=iOS Simulator,id=$UDID" -derivedDataPath build/DerivedData

xcodebuild test-without-building -project MyApp.xcodeproj -scheme MyApp \
  -destination "platform=iOS Simulator,id=$UDID" -derivedDataPath build/DerivedData \
  -only-testing:MyAppUITests/LoginTests/testSignIn

-only-testingには、Target、Target/Class、Target/Class/methodを指定できます。-skip-testingは同じ書き方で逆の動作をします。

5. テストを並列で実行する

xcodebuild test -project MyApp.xcodeproj -scheme MyApp \
  -destination "platform=iOS Simulator,id=$UDID" \
  -parallel-testing-enabled YES \
  -parallel-testing-worker-count 2 \
  -resultBundlePath build/TestResults.xcresult

Xcodeはシミュレータをクローンし、テストクラスをクローンに分散します。当社のログでは、iPhone 17のClone 1でテストが動いていました。-parallel-testing-enabledはスキームの設定を上書きします。16 GBのMacでは、ワーカー2つから始め、増やす前に計測してください。クローンはそれぞれ、メモリ上の完全なシミュレータです。複数のデバイスタイプで同時にテストするには、destinationを複数並べ、-maximum-concurrent-test-simulator-destinationsを設定します。

6. 不安定なテストをリトライする

xcodebuild test -project MyApp.xcodeproj -scheme MyApp \
  -destination "platform=iOS Simulator,id=$UDID" \
  -retry-tests-on-failure \
  -test-iterations 3 \
  -resultBundlePath build/TestResults.xcresult

xcodebuild -helpによると、失敗したテストは反復回数まで再実行されます。-test-iterationsを付けない場合、上限は3回です。一度失敗してから成功するテストで確認しました。実行はTEST SUCCEEDEDで終わりました。概要には、2つのテストに対して3回のテスト実行と報告されました。

つまりリトライすると不安定なテストは成功扱いになり、不安定さが隠れます。毎回result bundleを読み、どのテストにリトライが必要だったかを記録してください。-test-repetition-relaunch-enabled YESを付けると、試行ごとに新しいプロセスで再実行します。不安定なテストを探すには-run-tests-until-failureを使います。これは-retry-tests-on-failureとは併用できません。

7. 止まったテストに制限をかける

-test-timeouts-enabled YES \
-default-test-execution-time-allowance 120 \
-maximum-test-execution-time-allowance 300

これらのフラグをテストのコマンドに追加します。要素をいつまでも待ち続けるUIテストは、許容時間を過ぎると失敗します。CIのタイムアウトまでマシンを占有することはありません。

8. 実行の合間にシミュレータをきれいにする

xcrun simctl shutdown "$UDID"
xcrun simctl erase "$UDID"
xcrun simctl delete unavailable

eraseはシミュレータのコンテンツと設定をリセットします。delete unavailableは、現在のXcodeがサポートしなくなったデバイスを削除します。Xcodeをアップグレードするたびに実行してください。

再起動しても動き続ける仕組み

作成したシミュレータは再起動後も残ります。戻ってくる必要があるのは、テストを実行するCIエージェントです。LaunchAgentとして動かし、自動ログインをオンにし、各ジョブの最初にbootstatus -bでCI用のシミュレータを起動します。このコマンドは、すでに起動しているデバイスに対しても安全です。

実際に遭遇したエラーと対処法

  • Unable to find a device matching the provided destination specifier。その名前かOSが存在しません。xcrun simctl list devicesとランタイムの一覧を確認してください。
  • xcodebuild: error: Existing file at -resultBundlePath。毎回の実行の前に、古いbundleを削除してください。
  • Unable to erase contents and settings in current state: Booted。eraseの前にシミュレータをシャットダウンしてください。

専用Macが役立つ理由

UIテストは、最初のタップまでに多くの時間を使います。ビルド、シミュレータの起動、アプリのインストールを待つからです。電源が入ったままのマシンなら、DerivedDataと起動済みのシミュレータを準備しておけます。当社のベンチマークでは、Xcode 26.6でWikipediaのiOSアプリをビルドしました。小さな変更後のジョブは、ウォームなM6で27秒でした。新規のGitHubホスト型macos-26 runnerでは269秒でした。当社のM6はCPUが12コアなので、並列のクローンが2つあってもビルドの余裕が残ります。

ホスト型CIで足りる場合

1日に数件のプルリクエストで小さなスイートを実行するだけなら、分単位課金が合っています。GitHubのmacOSの単価$0.062/分(2026年9月に確認)なら、月額$139のMacは月2,242分ほどで元が取れます。それより少なければ、ホスト型のままにしてください。多数の実機のiPhoneでテストする必要があるなら、デバイスクラウドが必要です。Mac miniで動くのはシミュレータです。並列のスイートに16 GBを超えるメモリが必要でしょうか。当社のM5 Proのティアは48 GBまたは64 GBですが、予約注文制なので、約1週間の待ち時間を見込んでください。

パイプライン全体のどこにUIテストが入るかは、iOS CI/CDパイプラインのガイドで説明しています。ご自身の数字は計算ツールで試せます。

よくある質問

xcodebuildに、失敗したテストをリトライするフラグはありますか?

+

はい。-retry-tests-on-failureは、失敗したテストを-test-iterationsの回数まで、指定がなければ3回まで再実行します。-run-tests-until-failureとは併用できません。

コマンドラインからXCUITestのテストを並列で実行するには?

+

-parallel-testing-enabled YESを付け、必要なら-parallel-testing-worker-countも付けます。Xcodeはシミュレータをクローンし、テストクラスをクローンに分散します。

XCUITestはヘッドレスのMacで動きますか?

+

ユーザーがログインしているセッションが必要です。UIテストはシミュレータのアプリを操作するからです。CIエージェントをLaunchAgentとして動かし、自動ログインをオンにしてください。

Xcodeを開かずにxcodebuildのテスト結果を読むには?

+

-resultBundlePathを指定し、そのbundleに対してxcrun xcresulttool get test-results summary --pathを実行します。件数がJSONで出力されます。

関連ガイド