TestKase
TestKase
|Docs
AutomateMobileXCUITest

Getting started

Build your app and UI test Runner for a real device, get your access key, and run your first XCUITest build on a real iPhone with four curl calls.

Four calls: upload the app, upload the test suite, start a build, read the result. This page does each one with curl, so you can paste it into any CI step.

1. Build the app and the test Runner

From your Xcode project, build for testing on a real device (not the simulator):

xcodebuild build-for-testing \
  -scheme Acme \
  -destination 'generic/platform=iOS' \
  -derivedDataPath build

Use your own scheme name in place of Acme. Xcode writes everything to build/Build/Products/Debug-iphoneos/. Two things in there matter:

In Debug-iphoneos/What it is
Acme.appThe app — what your tests exercise
AcmeUITests-Runner.appThe test suite — the Runner app Xcode builds for your UI test target, with your tests inside it

Package them like this:

cd build/Build/Products/Debug-iphoneos

# the app, as an .ipa
mkdir -p Payload && cp -R Acme.app Payload/ && zip -qr Acme.ipa Payload

# the test Runner, as a .zip
zip -qr AcmeUITests.zip AcmeUITests-Runner.app

Zipping the -Runner.app in Finder (Compress) works too; the extra __MACOSX files Finder adds are ignored.

Signing

Sign both with Development or Ad Hoc signing, from any team — the signing Xcode uses for a normal device build is fine. An App Store build will not work.

Your app keeps its bundle id, so XCUIApplication() and XCUIApplication(bundleIdentifier: "com.acme.app") find it as they do locally. Features that need special entitlements — push notifications, App Groups, iCloud, universal links — aren't available in a run. Leave tests for those features out with skipTesting and run them on your own devices.

2. Get your access key

Open API Keys in your dashboard and copy your key. It starts with xyz_.

That key is the only thing that authenticates you. Send it either way:

  • HTTP Basic — the key is the password: curl -u "x:$TESTKASE_ACCESS_KEY" …. The x is a placeholder username we do not check.
  • Bearer — Authorization: Bearer $TESTKASE_ACCESS_KEY.

It is the same key your Appium and Selenium tests use. Keep it in your CI's secret store; the examples below read it from TESTKASE_ACCESS_KEY.

A missing, wrong or expired key always answers 401 with "That access token isn't valid."

3. Upload the app

curl -u "x:$TESTKASE_ACCESS_KEY" \
  -F "[email protected]" \
  https://hub.testkase.com/xcuitest/v1/app
{
  "app_url": "farm://app-3f9c01a2b4c5d6e7",
  "app_name": "Acme",
  "bundle_id": "com.acme.app",
  "expires_at": "2027-01-08T09:12:44.000Z"
}

Keep app_url. bundle_id is read from the app's own Info.plist.

The upload must finish within five minutes, and each file can be at most 500 MB, so a very large app can hit one or the other — see Uploading the app and suite.

4. Upload the test suite

curl -u "x:$TESTKASE_ACCESS_KEY" \
  -F "[email protected]" \
  https://hub.testkase.com/xcuitest/v1/test-suite
{
  "test_suite_url": "farm://suite-81ad02c4e9f1b367",
  "test_target": "AcmeUITests",
  "runner_bundle_id": "com.acme.AcmeUITests.xctrunner",
  "target_bundle_id": null,
  "expires_at": "2027-01-08T09:13:02.000Z"
}

Keep test_suite_url. test_target is the name of your UI test target — the first part of every onlyTesting entry.

5. Start a build

Name the devices as "<model>-<iOS version>", for example "iPhone 15-26" (models and iOS versions vary — copy them from the capability generator). Leave the version off, as in "iPhone 15", to take any iOS version. The capability generator lists the iPhones, iPads and iOS versions we have right now.

curl -u "x:$TESTKASE_ACCESS_KEY" \
  -H "Content-Type: application/json" \
  https://hub.testkase.com/xcuitest/v1/build \
  -d '{
    "app": "farm://app-3f9c01a2b4c5d6e7",
    "testSuite": "farm://suite-81ad02c4e9f1b367",
    "devices": ["iPhone 15-26"],
    "buildTag": "nightly-412"
  }'
{ "build_id": "5b1e7c2a-9d4f-4e1b-8a63-2f0c7d9e4b18", "message": "Success" }

Everything is checked before any device is taken: a wrong URL, a filter not written as Target, Target/Class or Target/Class/method, or a device we don't have is a 400 straight away, and nothing is billed. A well-formed filter that matches no test is accepted; that device then ends error with "No tests matched the build's filters." Every field is on Starting a build.

6. Read the result

curl -u "x:$TESTKASE_ACCESS_KEY" \
  https://hub.testkase.com/xcuitest/v1/builds/5b1e7c2a-9d4f-4e1b-8a63-2f0c7d9e4b18
{
  "build_id": "5b1e7c2a-9d4f-4e1b-8a63-2f0c7d9e4b18",
  "status": "running",
  "project": null,
  "buildTag": "nightly-412",
  "devices": [
    { "device": "iPhone 15-26", "sessionId": "0747163a-5e2d-4c8b-9f10-6a3b2c1d0e9f", "status": "running",
      "tests": { "total": 3, "passed": 2, "failed": 0, "skipped": 1 } }
  ]
}

Poll it until status is no longer queued or running, then gate your CI on it: passed is green, anything else is not. What each status means is on Results.

A simple poll loop for a CI step:

BUILD_ID=5b1e7c2a-9d4f-4e1b-8a63-2f0c7d9e4b18
while true; do
  STATUS=$(curl -s -u "x:$TESTKASE_ACCESS_KEY" \
    "https://hub.testkase.com/xcuitest/v1/builds/$BUILD_ID" | jq -r .status)
  echo "build $BUILD_ID: $STATUS"
  case "$STATUS" in queued|running) sleep 30 ;; *) break ;; esac
done
[ "$STATUS" = "passed" ]

7. See it in the dashboard

Open Automate → Mobile Apps → Runs. Your build is listed under its buildTag (or its project if you sent no tag, or "XCUITest build" if you sent neither). Open a device's session for the per-test video, the logs and the All Commands tab.

The status response does not include a dashboard link, so a recognisable buildTag — a CI run number or commit SHA — is the easiest way to find it.

Next