iOSのパイプラインでは、コンパイルがすべて成功していても、シミュレータへのインストール時に初めてパッケージ構造の異常が判明したり、起動直後にAppが終了したりすることがあります。最初から完全なUIテストを実行すると、フィードバックが遅くなるだけでなく、基礎的な障害がタイムアウトとして扱われてしまいます。より確実なのは、クラウドMac上に2〜3分で完了するインストール・起動スモークゲートを追加することです。使用するランタイムを固定し、生成直後の.appをインストールして、対象プロセスが稼働していることを確認したうえで、起動ログを保存します。
ゲートで確認すべき項目
このゲートでは業務機能を検証せず、次の4つの二択の問いだけに答えます。シミュレータを正常に起動できるか、成果物をインストールできるか、指定したBundle IDを起動できるか、観察期間中にプロセスが稼働し続けるか、という点です。いずれか1つでも失敗した場合は、後続のUIテスト用リソースを消費すべきではありません。
| チェック項目 | 成功条件 | 失敗時に保存する情報 |
|---|---|---|
| 起動 | bootstatusが正常終了する |
デバイスUDID、ランタイムバージョン |
| インストール | simctl installが0を返す |
Appのパス、コマンド出力 |
| App起動 | simctl launchがPIDを返す |
Bundle ID、終了コード |
| 稼働確認 | 観察期間後もプロセスを照会できる | システムログ、クラッシュ記録 |
スモークゲートの価値は、より広い範囲をカバーすることではなく、「成果物が存在する」と「成果物が実行できる」を2つの明確な結論に分けられる点にあります。
ジョブにはシミュレータのUDIDを明示的に渡し、bootedに依存しないようにします。共有クラウドMacには、別のジョブが起動したデバイスが残っている可能性があります。曖昧な対象指定を使うと、ログやインストール結果が混在します。NowMiniノードで選択できる具体的な構成は、コンソールで確認してください。パイプライン自体が依存するのは、インストール済みのXcodeと対応するシミュレータランタイムだけです。
ビルド成果物と対象デバイスを先に固定する
まずビルド出力をジョブディレクトリに集約し、DerivedDataからパスを推測しないようにします。Scheme、構成、Bundle ID、UDIDはすべて環境変数で渡し、ジョブの冒頭で検証します。
set -euo pipefail
: "${SIM_UDID:?SIM_UDID is required}"
: "${APP_BUNDLE_ID:?APP_BUNDLE_ID is required}"
WORK_DIR="${RUNNER_TEMP:-$PWD/.smoke}"
APP_PATH="$WORK_DIR/build/Sample.app"
LOG_DIR="$WORK_DIR/logs"
rm -rf "$WORK_DIR"
mkdir -p "$LOG_DIR"
xcodebuild \
-workspace Sample.xcworkspace \
-scheme Sample \
-configuration Debug \
-sdk iphonesimulator \
-destination "platform=iOS Simulator,id=$SIM_UDID" \
CONFIGURATION_BUILD_DIR="$WORK_DIR/build" \
build
findで最初の.appを取得してはいけません。プロジェクトによっては、ホストApp、テストホスト、拡張機能が同時に生成されるうえ、並び順も安定しません。製品名が分かっている場合はパスを直接組み立て、インストール前にInfo.plistを検証します。
test -d "$APP_PATH"
BUILT_ID=$(/usr/libexec/PlistBuddy -c \
"Print :CFBundleIdentifier" "$APP_PATH/Info.plist")
test "$BUILT_ID" = "$APP_BUNDLE_ID"
この手順により、誤ったSchemeの指定、ビルド構成の置換ミス、インストールコマンドがテストホストを参照しているといった問題を早期に検出できます。
起動、インストール、プロセス検証を行う
デバイスの起動では、bootstatus -bを使ってシステムが実際に利用可能になるまで待機します。bootだけを実行してすぐにインストールすると、負荷が高い状況ではサービスの準備が整っておらず、断続的に失敗することがあります。
xcrun simctl boot "$SIM_UDID" 2>/dev/null || true
xcrun simctl bootstatus "$SIM_UDID" -b
xcrun simctl uninstall "$SIM_UDID" "$APP_BUNDLE_ID" \
2>/dev/null || true
xcrun simctl install "$SIM_UDID" "$APP_PATH"
LAUNCH_OUTPUT=$(xcrun simctl launch \
"$SIM_UDID" "$APP_BUNDLE_ID" \
-SmokeTestMode YES)
printf '%s
' "$LAUNCH_OUTPUT"
PID=$(printf '%s
' "$LAUNCH_OUTPUT" | awk -F': ' 'NF == 2 {print $2}')
test -n "$PID"
起動引数を使えば、Appの初回ガイドをスキップしたり、アニメーションを無効化したり、ローカルフィクスチャへ切り替えたりできます。ただし、これらはアプリケーションコード側で明示的に実装する必要があります。任意の引数が自動的に有効になると想定してはいけません。Appを起動したら短い観察期間を設け、その後シミュレータ内部からプロセスを照会します。launchの戻り値だけを確認すると、起動から1秒以内に終了するケースを見逃します。
sleep 8
xcrun simctl spawn "$SIM_UDID" launchctl print \
"gui/$(id -u)/$APP_BUNDLE_ID" >/dev/null
Appによっては、プロセスラベルとBundle IDが完全には一致しません。その場合は既知のプロセス名を照会するか、Appの起動時にテスト専用の準備完了マーカーを書き込む方式に変更します。待機時間を固定で延ばすだけで、不安定な状態を覆い隠してはいけません。
失敗時に判読可能な証拠を保存する
後処理は重要ですが、その前に証拠を保存する必要があります。最小限の証拠には、ビルド出力、デバイス情報、インストールおよび起動時の出力、対象プロセス周辺の統合ログを含めます。ログの対象期間は現在のジョブを中心に設定し、ホスト全体の過去ログを出力しないようにします。
xcrun simctl list devices -j > "$LOG_DIR/devices.json"
xcrun simctl spawn "$SIM_UDID" log show \
--style compact \
--last 2m \
--predicate "process == 'Sample'" \
> "$LOG_DIR/sample-launch.log" 2>&1 || true
よくある誤りは、標準エラーだけをアーカイブすることです。起動直後に終了する場合、実際の原因はシミュレータのシステムログに記録されていることがあります。たとえば、動的ライブラリの読み込み失敗、リソースパスの誤り、未処理の例外などです。ログにトークン、リクエストヘッダー、ユーザーデータが含まれる場合は、アーカイブ前にマスキングし、パイプライン成果物へのアクセス範囲も制限してください。
環境障害とアプリケーション障害を区別する
bootstatusの失敗は、通常デバイス環境の問題に分類されます。installが失敗した場合は、パッケージ構造、ランタイム互換性、ディスク容量を優先的に確認します。launchには成功したもののプロセスが消えた場合は、まずアプリケーションログを確認します。障害を分類してから、再試行するかどうかを判断してください。アプリケーションが同じ形で連続して失敗する場合、自動的に何度も再実行すべきではありません。実際のフィードバックが遅れるだけです。
状態をクリーンアップしてパイプラインへ組み込む
ジョブの成否にかかわらず、Appを終了し、今回使用したシミュレータをシャットダウンします。shellのtrapを使えば、確実に後処理を実行できます。
cleanup() {
xcrun simctl terminate "$SIM_UDID" "$APP_BUNDLE_ID" \
2>/dev/null || true
xcrun simctl uninstall "$SIM_UDID" "$APP_BUNDLE_ID" \
2>/dev/null || true
xcrun simctl shutdown "$SIM_UDID" \
2>/dev/null || true
}
trap cleanup EXIT
ジョブごとに完全に同一の初期状態が必要な場合は、シャットダウン後にsimctl eraseを実行できます。ただし、処理時間が増え、事前に用意したデータも削除されます。より一般的な折衷案は、Appをアンインストールし、ジョブディレクトリを削除したうえで、パイプラインごとに独立したデバイスを割り当てる方法です。権限の状態が関係する場合は、対象サービスを明示的にリセットします。
最後に、このゲートをコンパイル後、単体テストまたはUIテストの前に配置します。スクリプトは明確な非ゼロ終了コードを返し、ログのアーカイブ手順は常に実行されるように設定してください。これにより、インストール失敗がテストのタイムアウトに見せかけられることも、起動直後の終了によって実行環境一式が無駄になることもありません。失敗するたびに十分な証拠が残るため、開発者は同じランタイムと同じ成果物を使って再現できます。
よくある質問
xcodebuildの成功だけでは不十分なのはなぜですか?
ビルド成功は成果物の生成を示すだけで、インストールや起動は保証しません。Bundle構造、最小OSバージョン、実行時リソースの問題は後の段階で発生します。
スモーク検査の失敗時に何を保存すべきですか?
xcodebuildの出力、シミュレータのUDIDとランタイム、Appのパス、simctl installとlaunchの終了コード、失敗前後の対象プロセスログを保存します。
独自の物理ノードで次のタスクを実行
M4、16GB RAM、256GB SSDを搭載したクラウドMacを、シンガポール、東京、ソウル、香港のノードからタスク期間に合わせて選択できます。