Llim.run

Run XCTest suites

lim xcode test builds your scheme's test targets on a remote Xcode sandbox, runs them on an attached cloud iOS simulator (unit and UI targets alike), and streams one line per test case as it finishes. It exits non-zero when any test fails, so it drops straight into CI.

Everything about syncing from Build with Xcode applies here too: XcodeGen projects, ignore and include patterns, additional files, and the sync cache.

Run the suite

From the project directory, run the tests of the default scheme, or name the scheme explicitly:

lim xcode test .
lim xcode test ./MyProject --scheme MyApp

The command reuses your last simulator-backed Xcode target, or creates and attaches the sandbox and simulator it needs, so repeat runs skip provisioning and start with warm build state. The scheme must have a test action configured; shared schemes exported by Xcode have one whenever the project has test targets.

--xcode-version 27 builds the test targets with another installed Xcode (27 selects that major's newest GA release, 27.1 selects that exact version). The simulator keeps the fleet's default runtime, so the run warns that runtime-dependent failures are possible and then proceeds. See Xcode and tool versions.

The output streams the build log first, then one line per case and a closing summary:

  PASS LimFixtureTests.CartModelTests/testDiscountAppliesOverThreshold (12ms)
  FAIL LimFixtureTests.LegacyPricingTests/testLegacyRounding (31ms)
       XCTAssertEqual failed: ("$4.50") is not equal to ("$5.00")
  5 passed, 1 failed

Swift test classes are reported module-qualified (Module.Class), Objective-C classes bare.

Select tests

Pass xcodebuild's identifier format Target[/Class[/method]]. Repeat a flag for several entries; --only-testing and --skip-testing are mutually exclusive:

# Only one test method
lim xcode test . --only-testing MyAppTests/LoginTests/testValidLogin

# Everything except the UI target
lim xcode test . --skip-testing MyAppUITests

A bare target name selects or skips that whole target. Targets named by no --only-testing entry are skipped entirely. An entry naming a target the build did not produce fails the run instead of silently running everything.

Launch settings and attachments

Limrun preserves the launch arguments and environment recorded in the .xctestrun manifest, and keeps test-host and UI app settings separate: CommandLineArguments and EnvironmentVariables configure the test runner, while UITargetAppCommandLineArguments and UITargetAppEnvironmentVariables configure the application under UI test. Arguments containing spaces stay single arguments.

Every enabled test plan configuration runs, including configurations that repeat the same test targets. XCTest activities are enabled, so tests can use XCTContext.runActivity and add XCTAttachment objects. The streamed results contain case outcomes and summaries; they do not include attachment downloads or an .xcresult bundle.

Get JSON results

--json streams the raw per-case events as NDJSON and ends with an exit record, for piping into your own tooling:

lim xcode test . --json > results.ndjson

Each line is one event:

{"type":"case","testClass":"MyAppTests.LoginTests","method":"testValidLogin","passed":true,"durationMs":12}
{"type":"case","testClass":"MyAppTests.LoginTests","method":"testExpiredToken","passed":false,"durationMs":31,"failureMessage":"XCTAssertEqual failed: ..."}
{"type":"summary","passed":1,"failed":1,"planFinished":true}
{"exitCode":1}

A missing summary event means the run died partway. planFinished: false means the suite started but did not complete its plan.

Build without running

--build-only compiles the test targets on a plain sandbox without acquiring a simulator. The products stay on the sandbox for a later run:

lim xcode test . --build-only

Run tests from the TypeScript SDK

The TypeScript SDK runs tests through xcodebuild with action: 'build-for-testing'. Unlike the CLI, it does not create a simulator for you: the tests run only when a simulator is attached to the Xcode sandbox, and without one the call just compiles them. Attach one first, as in Install on a simulator. Per-case events stream through onXctestEvent, and the accumulated cases and summary land on the result:

await xcode.attachNewSimulator(); // or xcode.attachSimulator(iosInstance)

const result = await xcode.xcodebuild(
  { scheme: 'MyApp', sdk: 'iphonesimulator', action: 'build-for-testing' },
  { onXctestEvent: (event) => console.log(event) },
);

console.log(result.xctest?.summary); // { type: 'summary', passed, failed, planFinished }
console.log(result.exitCode);        // non-zero when tests fail

onlyTesting and skipTesting go on the same settings object as scheme. runTests: false compiles the test targets without running them, the SDK equivalent of --build-only. xcode is the Xcode sandbox client from Build with Xcode.

Run in CI

GitHub Actions recipes shows how to set up the rest of a Linux CI job: install the CLI and pass LIM_API_KEY from a secret.

Next steps

hammer

Build with Xcode

Sync rules, schemes, and build flags shared with lim xcode test.

flask-conical

Maestro

Run YAML UI flows against the same cloud simulators.

git-pull-request

PR previews

Pair the test run with a preview link on every pull request.