EarlGrey CI Integration: Running iOS UI Tests in GitHub Actions and Xcode Cloud

EarlGrey CI Integration: Running iOS UI Tests in GitHub Actions and Xcode Cloud

EarlGrey tests run with xcodebuild test like any XCTest suite. The challenge is iOS simulator availability, macOS runner requirements, and collecting test artifacts from CI.

Prerequisites

EarlGrey requires:

  • macOS runner (not Linux — iOS simulators only run on macOS)
  • Xcode installed
  • An iOS simulator available

GitHub Actions

Basic Setup

# .github/workflows/ui-tests.yml
name: EarlGrey UI Tests

on:
  push:
    branches: [main, develop]
  pull_request:

jobs:
  ui-tests:
    runs-on: macos-14  # Use macos-14 for Xcode 15.x

    steps:
      - uses: actions/checkout@v4

      - name: Select Xcode version
        run: sudo xcode-select -switch /Applications/Xcode_15.2.app

      - name: Show available simulators
        run: xcrun simctl list devices available

      - name: Run EarlGrey tests
        run: |
          xcodebuild test \
            -workspace MyApp.xcworkspace \
            -scheme MyAppUITests \
            -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.2' \
            -resultBundlePath TestResults.xcresult \
            | xcpretty --report junit --output test-results.xml

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: |
            TestResults.xcresult
            test-results.xml

Available macOS Runners and Xcode Versions

Runner Xcode versions
macos-14 Xcode 15.x
macos-13 Xcode 14.x, 15.x
macos-12 Xcode 13.x, 14.x

Check the current GitHub-hosted runner docs for current versions.

Caching Dependencies

For CocoaPods:

- name: Cache CocoaPods
  uses: actions/cache@v4
  with:
    path: Pods
    key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
    restore-keys: |
      ${{ runner.os }}-pods-

- name: Install CocoaPods
  run: pod install

For Swift Package Manager, Xcode caches packages automatically. No additional caching step needed.

Parallel Testing

EarlGrey tests run in a single simulator by default. To parallelize across multiple simulators:

- name: Run tests in parallel
  run: |
    xcodebuild test \
      -workspace MyApp.xcworkspace \
      -scheme MyAppUITests \
      -destination 'platform=iOS Simulator,name=iPhone 15' \
      -parallel-testing-enabled YES \
      -maximum-parallel-testing-workers 3 \
      -resultBundlePath TestResults.xcresult

EarlGrey tests are safe to parallelize — each simulator instance is independent.

xcpretty for Readable Output

Raw xcodebuild output is verbose. xcpretty formats it:

- name: Install xcpretty
  run: gem install xcpretty

- name: Run tests
  run: |
    set -o pipefail
    xcodebuild test \
      -workspace MyApp.xcworkspace \
      -scheme MyAppUITests \
      -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.2' \
      | xcpretty --report junit --output test-results.xml

The set -o pipefail ensures the step fails if xcodebuild fails, even when piped through xcpretty.

Xcode Cloud

Xcode Cloud runs natively on Apple infrastructure and has first-class support for iOS simulators.

ci_scripts Setup

Create ci_post_clone.sh in your repo:

#!/bin/sh
# ci_post_clone.sh — runs after Xcode Cloud clones your repo

set -e

# Install CocoaPods if needed
if [ -f "Podfile" ]; then
    gem install cocoapods
    pod install
fi

xcodebuild in Xcode Cloud

Xcode Cloud configures the build environment automatically. In the workflow editor:

  1. Add a Test action
  2. Select your UI test scheme
  3. Choose simulator destination
  4. Enable parallel testing if needed

EarlGrey's in-process architecture works transparently with Xcode Cloud's test infrastructure.

Result Bundles and Artifacts

.xcresult bundles contain screenshots, test logs, and coverage. Parse them:

# List test results from bundle
xcrun xcresulttool get --format json --path TestResults.xcresult

# Export screenshots
xcrun xcresulttool export \
  --type directory \
  --path TestResults.xcresult \
  --output-path exported-results

For human-readable HTML reports, use xchtmlreport:

gem install xchtmlreport
xchtmlreport -r TestResults.xcresult

Collecting Screenshots on Failure

EarlGrey saves screenshots on test failure automatically. The screenshots end up in the .xcresult bundle. Extract them:

- name: Extract failure screenshots
  if: failure()
  run: |
    xcrun xcresulttool export \
      --type directory \
      --path TestResults.xcresult \
      --output-path failure-screenshots

- name: Upload failure screenshots
  if: failure()
  uses: actions/upload-artifact@v4
  with:
    name: failure-screenshots
    path: failure-screenshots/

Common CI Failures

"Unable to find a destination matching the provided destination specifier"

The specified simulator doesn't exist on the runner. List available simulators:

xcrun simctl list devices available | grep iPhone

Use a simulator that's actually on the runner, or use name=Any iOS Simulator Device to let Xcode choose.

"Test session exited with unhandled exception"

EarlGrey's EarlGreyApp framework isn't linked to the app target. Verify the build phase includes it for the app target, not just the test target.

Tests pass locally but fail in CI (timeout)

CI machines are slower. Increase the EarlGrey timeout:

// In your test setUp
GREYConfiguration.shared.setValue(60.0,
    forConfigKey: kGREYConfigKeyInteractionTimeoutDuration)

Simulator not booting in time

Add a boot wait:

# Boot simulator explicitly before running tests
xcrun simctl boot "iPhone 15"
xcrun simctl bootstatus "iPhone 15" -b

Test Scheme Configuration

For CI, configure your test scheme to:

  1. Run tests in random order — catches test ordering dependencies
  2. Gather coverage — useful for tracking test quality
  3. Enable address sanitizer — catches memory issues in CI before production

In Xcode scheme editor: Product → Scheme → Edit Scheme → Test → Options.

For command line:

xcodebuild test \
  -workspace MyApp.xcworkspace \
  -scheme MyAppUITests \
  -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.2' \
  -enableCodeCoverage YES \
  -testOrder random \
  -resultBundlePath TestResults.xcresult

Read more

Start now free