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:
- In Xcode, go to File > New > Target
- Select UI Testing Bundle
- 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 xcresultparserxcresultparser 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.swiftScheme Configuration
Ensure your scheme includes the UI test target:
- Click the scheme selector in the Xcode toolbar
- Select Edit Scheme
- Under the Test action, verify that
MyAppUITestsis 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 TestKase48272→ linked to the "invalid password" test case in TestKase48273→ 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.xcresultAfter the test run completes, convert the .xcresult bundle to JUnit XML:
xcresultparser -o junit test-results/results.xcresult > test-results/junit.xmlFor 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:
| Approach | Test Method | CLI Flag | Extracted ID |
|---|---|---|---|
| Underscore pattern | func 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.xml3. 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.xmlTroubleshooting
xcresult conversion fails
The .xcresult format changes between Xcode releases, so keep the converter current:
brew upgrade xcresultparserIf 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.xcresultNo 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 availableIf 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:
- Open the scheme editor: Product > Scheme > Edit Scheme
- Select the Test action in the left sidebar
- Click + and add
MyAppUITestsif it is not listed
From the command line, verify the scheme configuration:
xcodebuild -list -project MyApp.xcodeprojEnsure the scheme name you pass to -scheme exactly matches one of the listed schemes.
