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

  1. Open an existing iOS project (or create XCloudLab from a SwiftUI App template).
  2. Push the project to a GitHub repository.
  3. In App Store Connect, create the app record matching the bundle ID.
  4. In Xcode: View → Navigators → Reports (⌘9). Click the Cloud tab.

Build

Workflow 1 — PR Tests

  1. In the Cloud tab, click Get Started (or Create Workflow).
  2. Connect to App Store Connect (sign in if prompted).
  3. Connect source control:
    • Choose GitHub, authenticate, install Xcode Cloud’s GitHub App scoped to this repo only.
  4. 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
  5. 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

  1. In the Cloud tab, + → Create Workflow.
  2. Configure:
    • Name: Main CI
    • Start Conditions: Branch Changes → main only
    • 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)
  3. 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

  1. App Store Connect → Xcode Cloud → your workflow (Main CI) → Environment → Secrets → Add SLACK_WEBHOOK = https://hooks.slack.com/services/...
  2. 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 Tests workflow starts in Xcode Cloud → green check appears as a GitHub status check.
  • Merge to mainMain CI runs → archive uploaded to TestFlight → Slack message arrives.

Stretch

  1. Workflow 3 — Release Candidate: Trigger on tag v*.*.*. Action: Archive. Post-action: TestFlight External “Beta Wide”.
  2. Conditional skip: add if [ "$CI_PULL_REQUEST_TARGET_BRANCH" != "main" ]; then exit 0; fi in a ci_pre_xcodebuild.sh to no-op for non-main PRs.
  3. Parallel test plan: split unit and UI tests into two test plans; add two Test actions running in parallel.
  4. Cost watch: in App Store Connect → Xcode Cloud → Usage, observe minutes consumed; tune workflows to stay under 25h/mo.
  5. 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.

Next: Lab 10.3 — Full Release Pipeline