Skip to content

Project Setup

This page covers everything needed to integrate the AutoMobile XCTestRunner into an existing iOS Xcode project: adding the dependency, configuring the test target, installing prerequisites, and running your first test locally.

Dependency

The XCTestRunner is a Swift Package Manager library. It can be consumed as a local path dependency (for reproducibility before a GitHub release is available) or as a remote dependency once published.

Local path dependency (current approach)

The recommended approach is to commit the XCTestRunner source alongside your project so CI can resolve it without network access — analogous to how the Android JUnitRunner ships committed JARs in libs/maven/.

Step 1 — Copy the package source into your repo:

# From your project root
mkdir -p libs/spm/XCTestRunner

# Copy from a local auto-mobile checkout
cp -r ~/path/to/auto-mobile/ios/XCTestRunner/Sources \
      libs/spm/XCTestRunner/Sources

Step 2 — Create the package manifest:

// libs/spm/XCTestRunner/Package.swift
// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "XCTestRunner",
    platforms: [
        .iOS(.v15),
        .macOS(.v13),
    ],
    products: [
        .library(name: "XCTestRunner", targets: ["XCTestRunner"]),
    ],
    targets: [
        .target(name: "XCTestRunner", path: "Sources/XCTestRunner"),
    ]
)

Step 3 — Commit both the manifest and the source files. CI runners resolve them from disk with no network dependency.

Remote dependency (once published)

When the package is published to GitHub, switch to a version-pinned remote reference:

# ios/YourApp/project.yml
packages:
  XCTestRunner:
    url: https://github.com/kaeawc/auto-mobile
    from: "0.0.14"
.package(url: "https://github.com/kaeawc/auto-mobile", from: "0.0.14")

Using a local build from source

If you are developing the XCTestRunner itself and need to test against your local changes, point the package at your checked-out source tree:

# ios/YourApp/project.yml
packages:
  XCTestRunner:
    path: /path/to/auto-mobile/ios/XCTestRunner   # absolute path during development

Absolute paths are not portable

Absolute path references only work on your machine. Use a repo-relative path (e.g. ../../libs/spm/XCTestRunner) for anything committed to source control so all team members and CI runners resolve the package correctly.

Test target setup

XcodeGen configuration

AutoMobile tests live in a dedicated test target separate from your unit tests. This keeps the targets independent — unit tests can run without a simulator or daemon, while AutoMobile tests require both.

# ios/YourApp/project.yml
packages:
  # … existing packages …
  XCTestRunner:
    path: ../../libs/spm/XCTestRunner   # or remote URL once published

targets:
  YourApp:
    type: application
    # … existing app target …

  YourAppTests:
    type: bundle.unit-test
    platform: iOS
    sources:
      - path: Tests
        excludes:
          - AutoMobile/**       # keep AutoMobile files out of the unit test bundle
    dependencies:
      - target: YourApp
      # … existing test dependencies …

  YourAppAutoMobileTests:
    type: bundle.unit-test
    platform: iOS
    sources:
      - path: Tests/AutoMobile  # Swift files compiled; YAML files bundled as resources
    dependencies:
      - target: YourApp
      - package: XCTestRunner
        product: XCTestRunner
    settings:
      base:
        PRODUCT_BUNDLE_IDENTIFIER: com.example.ios.YourAppAutoMobileTests

schemes:
  YourApp:
    build:
      targets:
        YourApp: all
        YourAppTests:
          - test
        YourAppAutoMobileTests:
          - test
    test:
      config: Debug
      parallelizeBuild: true
      enableCodeCoverage: false
      targets:
        - name: YourAppTests
        - name: YourAppAutoMobileTests

Excluding AutoMobile files from unit tests

The excludes: [AutoMobile/**] entry in YourAppTests prevents the AutoMobile test Swift files from being compiled into the unit test bundle, where XCTestRunner is not linked. Without this exclusion the build fails with “no such module ‘XCTestRunner’”.

Regenerate the Xcode project

cd ios/YourApp
xcodegen generate --spec project.yml

Test plan resources

YAML plan files placed under Tests/AutoMobile/ are automatically picked up by XcodeGen as Copy Bundle Resources (.yaml is not a compiled source extension). They are bundled flat into YourAppAutoMobileTests.xctest.

Tests/
└── AutoMobile/
    ├── AppLaunchAutoMobileTests.swift
    ├── AppLifecycleAutoMobileTests.swift
    └── test-plans/
        ├── launch-app.yaml
        └── app-background-foreground.yaml

The AutoMobilePlanExecutor resolves the planPath property first from the test bundle resources, falling back to a filesystem path relative to the current working directory if the bundle lookup fails. For a path like "test-plans/launch-app.yaml" the executor looks for launch-app.yaml in the bundle’s resource directory. Unique plan file names across the target are enough for reliable resolution.

Prerequisites

Xcode and Command Line Tools

XCTestRunner requires Xcode 15.0+ on macOS 13.0+.

xcode-select --install   # installs Command Line Tools if missing
xcodebuild -version      # verify Xcode version

iOS Simulator

Boot a simulator before running tests. The scripts/ios/boot-simulator.sh helper script in the reference repo finds or creates an appropriate simulator and boots it:

bash scripts/ios/boot-simulator.sh

Or manually:

xcrun simctl boot "iPhone 16"
open -a Simulator

Verify the simulator is booted:

xcrun simctl list devices booted

AutoMobile daemon

The XCTestRunner communicates with a locally running AutoMobile daemon over a Unix domain socket at /tmp/auto-mobile-daemon-<uid>.sock. DaemonManager.ensureDaemonRunning() (called from setUpAutoMobile in each test class) attempts to start the daemon automatically. For predictable local development, start it yourself:

npm install -g @kaeawc/auto-mobile --ignore-scripts
auto-mobile --daemon start &

Or using bun:

bun install -g @kaeawc/auto-mobile
auto-mobile --daemon start &

Wait for the socket before proceeding:

until [ -S "/tmp/auto-mobile-daemon-$(id -u).sock" ]; do sleep 1; done
echo "Daemon ready"

CtrlProxy iOS

The daemon needs the AutoMobile CtrlProxy app running inside the simulator for view hierarchy access during observe and interaction steps. The daemon installs it automatically on first use.

Pre-install it before running tests using the CLI observe command to avoid installation delays mid-test:

auto-mobile --cli observe --platform ios

This installs CtrlProxy into the booted simulator and confirms the daemon can reach it.

Running tests locally

Step 1 — Build for testing

bash scripts/ios/xcode-build-for-testing.sh

Or directly with xcodebuild:

xcodebuild build-for-testing \
  -scheme YourApp \
  -destination 'platform=iOS Simulator,name=iPhone 16' \
  -derivedDataPath build/DerivedData \
  CODE_SIGN_IDENTITY="" \
  CODE_SIGNING_REQUIRED=NO \
  -skipMacroValidation

The build produces a .xctestrun file in build/DerivedData/Build/Products/ that captures both YourAppTests and YourAppAutoMobileTests.

Step 2 — Start the daemon

auto-mobile --daemon start &
until [ -S "/tmp/auto-mobile-daemon-$(id -u).sock" ]; do sleep 1; done

Step 3 — Pre-install CtrlProxy

auto-mobile --cli observe --platform ios

Step 4 — Run AutoMobile tests

bash scripts/ios/xcode-automobile-tests.sh

Or directly:

xctestrun_file=$(find build/DerivedData/Build/Products -name "*.xctestrun" | head -1)
booted_udid=$(xcrun simctl list devices booted -j \
  | python3 -c "import sys,json; d=json.load(sys.stdin)['devices']; \
    print(next(v[0]['udid'] for v in d.values() if v), end='')" 2>/dev/null)

xcodebuild test-without-building \
  -xctestrun "$xctestrun_file" \
  -destination "platform=iOS Simulator,id=$booted_udid" \
  -only-testing:YourAppAutoMobileTests \
  -enableCodeCoverage NO \
  -skipMacroValidation

Running unit tests separately

The standard test script skips the AutoMobile bundle so unit tests never require the daemon:

xcodebuild test-without-building \
  -xctestrun "$xctestrun_file" \
  -destination "platform=iOS Simulator,id=$booted_udid" \
  -skip-testing:YourAppAutoMobileTests \
  -enableCodeCoverage NO \
  -parallel-testing-enabled YES \
  -skipMacroValidation

Reading test results

Test results are written as an .xcresult bundle:

build/automobile-tests.xcresult   # AutoMobile tests
build/test.xcresult               # unit tests

Inspect with the xcresulttool:

# Pass/fail summary
xcrun xcresulttool get test-results summary --path build/automobile-tests.xcresult

# All tests with status
xcrun xcresulttool get test-results tests --path build/automobile-tests.xcresult

# Extract failing test identifiers
xcrun xcresulttool get test-results tests \
  --path build/automobile-tests.xcresult --format json \
  | jq '[.. | objects | select(.testStatus? == "Failure") | .nodeIdentifier]'

Troubleshooting

Problem Likely cause Fix
no such module 'XCTestRunner' YourAppTests compiles AutoMobile files without the package linked Add excludes: [AutoMobile/**] to YourAppTests sources in project.yml; regenerate
AutoMobile daemon is not running and could not be started auto-mobile not on PATH or daemon failed to start Install @kaeawc/auto-mobile globally; check PATH includes ~/.bun/bin or /usr/local/bin
Plan not found at path: test-plans/launch-app.yaml YAML file not bundled Verify the YAML is under Tests/AutoMobile/ and appears in Build Phases → Copy Bundle Resources
No booted iPhone simulator found No simulator running Run bash scripts/ios/boot-simulator.sh or xcrun simctl boot "iPhone 16"
Missing AutoMobile test plan path. planPath returns empty string Override var planPath: String in your test class
Could not resolve package 'XCTestRunner' Local path wrong or source files missing Verify libs/spm/XCTestRunner/ exists and Package.swift references the correct path

See also