TestKase
TestKase
|Docs
Test ManagementAutomationTest Frameworks

XCUITest

Set up XCUITest for iOS UI testing and integrate test results with TestKase.

Want to run your XCUITest suite on our real iPhones and iPads instead? Upload your app and your UI test Runner and we run them on real devices. See Automate → XCUITest on real devices.

Overview

XCUITest is Apple's native UI testing framework for iOS, macOS, tvOS, and watchOS applications. Tests are written in Swift or Objective-C and run within Xcode, interacting with the app through accessibility elements. XCUITest is tightly integrated with the Xcode toolchain and provides reliable, first-party UI automation.

To integrate XCUITest results with TestKase, first convert the .xcresult bundle to JUnit XML format, then report with --format junit.

Prerequisites

  • macOS (required for Xcode)
  • Xcode 15+ with command-line tools installed
  • An iOS Simulator or physical device enrolled in your development team
  • A result converter: xcresultparser (macOS 12+)

Installation

XCUITest is built into Xcode — no additional framework installation is needed. To add a UI Testing target to your project:

  1. In Xcode, go to File > New > Target
  2. Select UI Testing Bundle
  3. Name it (e.g., MyAppUITests) and click Finish

xcodebuild writes an .xcresult bundle, which is a binary format the reporter cannot read. Install xcresultparser to convert it to JUnit XML:

brew install xcresultparser

xcresultparser is in Homebrew core and requires macOS 12 or newer. Alternatively, use the xcresulttool bundled with Xcode to extract raw JSON and convert it with your own script.

Project Setup

UI Test Target Structure

After creating the UI Testing target, Xcode generates the following structure:

MyApp/
  MyApp/                    # Main app source
  MyAppTests/               # Unit tests
  MyAppUITests/             # UI tests (XCUITest)
    MyAppUITests.swift
    MyAppUITestsLaunchTests.swift

Scheme Configuration

Ensure your scheme includes the UI test target:

  1. Click the scheme selector in the Xcode toolbar
  2. Select Edit Scheme
  3. Under the Test action, verify that MyAppUITests is listed and enabled

If you are running tests from the command line, you can list available schemes with xcodebuild -list -project MyApp.xcodeproj or xcodebuild -list -workspace MyApp.xcworkspace.

Writing Tests

Create a test class in the UI test target:

// MyAppUITests/LoginUITests.swift
import XCTest

final class LoginUITests: XCTestCase {

    let app = XCUIApplication()

    override func setUpWithError() throws {
        continueAfterFailure = false
        app.launch()
    }

    func test_48271_ValidLogin() throws {
        let emailField = app.textFields["emailInput"]
        emailField.tap()
        emailField.typeText("[email protected]")

        let passwordField = app.secureTextFields["passwordInput"]
        passwordField.tap()
        passwordField.typeText("password123")

        app.buttons["loginButton"].tap()

        let dashboard = app.staticTexts["dashboardTitle"]
        XCTAssertTrue(dashboard.waitForExistence(timeout: 5))
    }

    func test_48272_InvalidPassword() throws {
        let emailField = app.textFields["emailInput"]
        emailField.tap()
        emailField.typeText("[email protected]")

        let passwordField = app.secureTextFields["passwordInput"]
        passwordField.tap()
        passwordField.typeText("wrong")

        app.buttons["loginButton"].tap()

        let errorMessage = app.staticTexts["errorMessage"]
        XCTAssertTrue(errorMessage.waitForExistence(timeout: 5))
        XCTAssertEqual(errorMessage.label, "Invalid credentials")
    }

    func test_48273_ForgotPasswordNavigation() throws {
        app.buttons["forgotPasswordLink"].tap()

        let resetTitle = app.staticTexts["resetPasswordTitle"]
        XCTAssertTrue(resetTitle.waitForExistence(timeout: 5))
    }
}

Link 5-digit Automation IDs from TestKase to each test. Since Swift method names cannot contain brackets, embed the ID in the method name using underscores — as the sample above does — and configure the reporter with --automation-id-format "_(\\d{5})_". The default [XXXXX] bracket pattern will never match an XCUITest run, so this flag is required on every command.

For the tests above, generate the IDs in TestKase and link them:

  • 48271 → linked to the "valid login" test case in TestKase
  • 48272 → linked to the "invalid password" test case in TestKase
  • 48273 → linked to the "forgot password" test case in TestKase

Generate Automation IDs in TestKase first. For XCUITest where method names cannot contain brackets, use the --automation-id-format CLI flag to customize the extraction regex.

Running Tests

Run UI tests from the command line using xcodebuild:

xcodebuild test \
  -scheme MyApp \
  -destination 'platform=iOS Simulator,name=iPhone 15' \
  -resultBundlePath test-results/results.xcresult

After the test run completes, convert the .xcresult bundle to JUnit XML:

xcresultparser -o junit test-results/results.xcresult > test-results/junit.xml

For workspaces (.xcworkspace), use the -workspace flag instead of the default project: xcodebuild test -workspace MyApp.xcworkspace -scheme MyApp ...

TestKase Integration

After converting the test results to JUnit XML, report results to TestKase:

npx @testkase/reporter report \
  --token $TESTKASE_PAT \
  --project-id PRJ-1 \
  --org-id 1173 \
  --cycle-id TCYCLE-5 \
  --format junit \
  --automation-id-format "_(\\d{5})_" \
  --results-file test-results/junit.xml

--cycle-id is optional. If not provided, results are reported to TCYCLE-1 — the master test cycle for the project.

Automation ID Mapping

The reporter extracts Automation IDs based on the configured pattern. For XCUITest, embed the 5-digit ID in the method name and use a custom --automation-id-format:

ApproachTest MethodCLI FlagExtracted ID
Underscore patternfunc test_48271_ValidLogin()--automation-id-format "_(\\d{5})_"48271

Generate the 5-digit ID in TestKase first, then embed it in your test method name.

Complete Example

1. Test File

// MyAppUITests/LoginUITests.swift
import XCTest

final class LoginUITests: XCTestCase {

    let app = XCUIApplication()

    override func setUpWithError() throws {
        continueAfterFailure = false
        app.launch()
    }

    func test_48271_ValidLogin() throws {
        app.textFields["emailInput"].tap()
        app.textFields["emailInput"].typeText("[email protected]")

        app.secureTextFields["passwordInput"].tap()
        app.secureTextFields["passwordInput"].typeText("password123")

        app.buttons["loginButton"].tap()

        XCTAssertTrue(app.staticTexts["dashboardTitle"].waitForExistence(timeout: 5))
    }

    func test_48272_InvalidPassword() throws {
        app.textFields["emailInput"].tap()
        app.textFields["emailInput"].typeText("[email protected]")

        app.secureTextFields["passwordInput"].tap()
        app.secureTextFields["passwordInput"].typeText("wrong")

        app.buttons["loginButton"].tap()

        let errorMessage = app.staticTexts["errorMessage"]
        XCTAssertTrue(errorMessage.waitForExistence(timeout: 5))
        XCTAssertEqual(errorMessage.label, "Invalid credentials")
    }
}

2. Run Tests and Convert Results

# Run UI tests and generate xcresult bundle
xcodebuild test \
  -scheme MyApp \
  -destination 'platform=iOS Simulator,name=iPhone 15' \
  -resultBundlePath test-results/results.xcresult

# Convert xcresult to JUnit XML
xcresultparser -o junit test-results/results.xcresult > test-results/junit.xml

3. Report Results to TestKase

npx @testkase/reporter report \
  --token $TESTKASE_PAT \
  --project-id PRJ-1 \
  --org-id 1173 \
  --cycle-id TCYCLE-5 \
  --format junit \
  --automation-id-format "_(\\d{5})_" \
  --results-file test-results/junit.xml

Troubleshooting

xcresult conversion fails

The .xcresult format changes between Xcode releases, so keep the converter current:

brew upgrade xcresultparser

If you are converting by hand instead, note that Xcode 16 changed xcresulttool's API — the older get --format json call now requires --legacy:

# Xcode 15
xcrun xcresulttool get --format json --path test-results/results.xcresult

# Xcode 16+
xcrun xcresulttool get --legacy --format json --path test-results/results.xcresult

No Automation IDs extracted

The converted XML carries the Swift method name (e.g. test_48271_ValidLogin), which never matches the reporter's default [XXXXX] bracket pattern. Pass --automation-id-format "_(\\d{5})_" on every report command, and confirm with --dry-run --verbose.

Simulator not booted

If xcodebuild fails because the simulator is not available, boot it manually before running tests:

xcrun simctl boot "iPhone 15"

List all available simulators and their states:

xcrun simctl list devices available

If the simulator you need is not installed, create one:

xcrun simctl create "iPhone 15" "com.apple.CoreSimulator.SimDeviceType.iPhone-15" "com.apple.CoreSimulator.SimRuntime.iOS-17-2"

Test target not found

If xcodebuild reports that no test target is found, verify that your scheme includes the UI test target:

  1. Open the scheme editor: Product > Scheme > Edit Scheme
  2. Select the Test action in the left sidebar
  3. Click + and add MyAppUITests if it is not listed

From the command line, verify the scheme configuration:

xcodebuild -list -project MyApp.xcodeproj

Ensure the scheme name you pass to -scheme exactly matches one of the listed schemes.