Lab 10.2 — Xcode Cloud Workflow
Goal: stand up two Xcode Cloud workflows — one for PR tests, one for TestFlight from main — entirely without touching CI YAML.
Time: 30–60 minutes.
Prereqs: Paid Apple Developer subscription, a GitHub/GitLab/Bitbucket repo with an iOS project, Xcode 16+.
Setup
- Open an existing iOS project (or create
XCloudLabfrom a SwiftUI App template). - Push the project to a GitHub repository.
- In App Store Connect, create the app record matching the bundle ID.
- In Xcode:
View → Navigators → Reports(⌘9). Click the Cloud tab.
Build
Workflow 1 — PR Tests
- In the Cloud tab, click Get Started (or Create Workflow).
- Connect to App Store Connect (sign in if prompted).
- Connect source control:
- Choose GitHub, authenticate, install Xcode Cloud’s GitHub App scoped to this repo only.
- Apple proposes a default workflow. Edit it:
- Name:
PR Tests - Description: “Run unit + UI tests on every pull request to main”
- Repository: your repo
- Branch: leave blank
- Start Conditions: remove the auto-added “Branch Changes” → add Pull Request Changes → Source branches: any; Target branch:
main - Environment:
- macOS Version: latest (or pin “macOS 15”)
- Xcode Version: pin a specific version like “Xcode 16.0”
- Clean Build: off (faster incremental)
- Actions:
- Remove “Build” if present
- Add Test
- Destination: iOS Simulator → iPhone 16 Pro (Latest OS)
- Scheme: your app’s main scheme
- Test Plan: your default test plan
- Post-Actions: leave empty for now
- Name:
- Save. Trigger a test run by opening a PR or pushing a commit on a branch and opening a PR.
Workflow 2 — Main CI → TestFlight
- In the Cloud tab, + → Create Workflow.
- Configure:
- Name:
Main CI - Start Conditions: Branch Changes →
mainonly - Environment: same Xcode version as Workflow 1 (consistency matters)
- Actions:
- Test (same simulator destination)
- Archive: iOS, Deployment Preparation set to “TestFlight (Internal Testing Only)”
- Post-Actions:
- TestFlight Internal Testing: select your internal group(s)
- Optionally: Notify → Slack webhook (URL in Environment → Secrets)
- Name:
- Save.
Add a custom script
In your repo, create ci_scripts/ci_post_clone.sh:
#!/bin/sh
set -e
echo "📦 Xcode Cloud post-clone"
echo "Workflow: $CI_WORKFLOW"
echo "Build: $CI_BUILD_NUMBER"
echo "Branch: $CI_BRANCH"
echo "Commit: $CI_COMMIT"
# Install SwiftLint via brew (Xcode Cloud has brew pre-installed)
which swiftlint || brew install swiftlint
Make it executable, commit, push:
chmod +x ci_scripts/ci_post_clone.sh
git add ci_scripts/ci_post_clone.sh
git commit -m "ci: add post-clone script"
git push
This script runs on every workflow before xcodebuild. Confirm by viewing the build log under the Cloud tab.
Add a secret + use it in ci_post_xcodebuild.sh
- App Store Connect → Xcode Cloud → your workflow (
Main CI) → Environment → Secrets → AddSLACK_WEBHOOK = https://hooks.slack.com/services/... - Create
ci_scripts/ci_post_xcodebuild.sh:
#!/bin/sh
set -e
if [ "$CI_WORKFLOW" = "Main CI" ] && [ "$CI_XCODEBUILD_EXIT_CODE" = "0" ]; then
curl -X POST -H 'Content-Type: application/json' \
--data "{\"text\":\"✅ Main CI build $CI_BUILD_NUMBER passed — TestFlight upload in progress\"}" \
"$SLACK_WEBHOOK"
fi
chmod +x ci_scripts/ci_post_xcodebuild.sh
git add ci_scripts/ci_post_xcodebuild.sh
git commit -m "ci: add slack post-build notification"
git push
Verify
- Open a PR →
PR Testsworkflow starts in Xcode Cloud → green check appears as a GitHub status check. - Merge to
main→Main CIruns → archive uploaded to TestFlight → Slack message arrives.
Stretch
- Workflow 3 — Release Candidate: Trigger on tag
v*.*.*. Action: Archive. Post-action: TestFlight External “Beta Wide”. - Conditional skip: add
if [ "$CI_PULL_REQUEST_TARGET_BRANCH" != "main" ]; then exit 0; fiin aci_pre_xcodebuild.shto no-op for non-main PRs. - Parallel test plan: split unit and UI tests into two test plans; add two Test actions running in parallel.
- Cost watch: in App Store Connect → Xcode Cloud → Usage, observe minutes consumed; tune workflows to stay under 25h/mo.
- Migrate one job from GitHub Actions: pick the noisiest macOS job in your current GitHub Actions setup and replicate it in Xcode Cloud. Compare wall clock + $.
Notes
- Xcode Cloud workflows live in App Store Connect, not in your repo. They’re not PR-reviewable as files. If you want diff-able config, you’d need to script the App Store Connect API to write workflow definitions — only worth it at scale.
CI_*env vars are auto-populated; full list: Apple Xcode Cloud Environment Variables.- Xcode Cloud’s free tier (25 compute hours/mo) is per Apple Developer organization, not per app. A four-app team easily blows through it.
- If a workflow seems stuck “Queueing”, check Apple’s Xcode Cloud system status — Apple occasionally throttles.