TestKase Docs
AutomationTest Frameworks

XCUITest

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

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: xcresult-to-junit or the swift-junit package

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

Install a converter tool to transform .xcresult bundles into JUnit XML:

brew install nicklama/tap/xcresult-to-junit

Alternatively, you can use the xcresulttool built into Xcode to extract test data and convert it with a custom 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 testValidLogin() throws {
        let emailField = app.textFields["emailInput"]
        emailField.tap()
        emailField.typeText("user@example.com")

        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 testInvalidPassword() throws {
        let emailField = app.textFields["emailInput"]
        emailField.tap()
        emailField.typeText("user@example.com")

        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 testForgotPasswordNavigation() 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 (e.g., func test_48271_ValidLogin()) and configure the reporter with --automation-id-format "_(\\d{5})_".

For the tests above, generate 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:

xcresult-to-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 \
  --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 testValidLogin() throws {
        app.textFields["emailInput"].tap()
        app.textFields["emailInput"].typeText("user@example.com")

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

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

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

    func testInvalidPassword() throws {
        app.textFields["emailInput"].tap()
        app.textFields["emailInput"].typeText("user@example.com")

        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
xcresult-to-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 \
  --results-file test-results/junit.xml

Troubleshooting

xcresult conversion fails

Ensure the xcresult-to-junit tool version is compatible with your Xcode version. The .xcresult format can change between Xcode releases. Update the converter:

brew upgrade nicklama/tap/xcresult-to-junit

If the tool does not support your Xcode version yet, you can use xcresulttool (bundled with Xcode) to extract raw JSON and convert it manually:

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

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.