---
title: "XCUITest Tutorial: Write and Run iOS UI Tests"
description: "Add a UI test target, write your first XCUITest in Swift, run it with xcodebuild and on GitHub Actions, and fix the common failures."
canonical: "https://momentic.ai/blog/xcuitest-tutorial"
last-updated: "2026-10-31T02:14:40Z"
---

# XCUITest Tutorial: Write and Run iOS UI Tests

URL: https://momentic.ai/blog/xcuitest-tutorial

[blog](/blog) [/ resources](/blog/category/resources)  / xcuitest-tutorial

Resources

Set up a UI test target in Xcode, write your first XCUITest in Swift, run it from xcodebuild and GitHub Actions, and fix the failures you will hit first.

Wei-Wei Wu

CEO, Momentic

## What XCUITest is and how it relates to XCTest

XCUITest lets you check an iOS app by launching it and interacting with its interface. The UI test APIs ship in Apple’s [XCUIAutomation framework](https://developer.apple.com/documentation/xcuiautomation) and run inside [XCTest](https://developer.apple.com/documentation/xctest), which supplies test cases and assertions. XCUIAutomation adds the APIs for launching the app, finding UI elements and performing actions such as taps and text entry.

An XCUITest test lives in a UI test target and runs outside the app it tests. Rather than call a view controller method directly, the test uses `XCUIApplication` to launch the app and queries its accessibility interface to find controls. That makes UI tests useful for checking what a user can actually do, while unit tests remain better suited to checking a small piece of code in isolation.

## Add a UI test target in Xcode

Xcode needs a UI testing bundle to run tests that launch your app and interact with its interface. In your app project, choose **File > New > Target**, select **UI Testing Bundle** under iOS, and click **Next**. Give the target a name, choose Swift, and set **Target to be Tested** to your app before clicking **Finish**.

Check that your app’s scheme includes the new target. Choose **Product > Scheme > Edit Scheme**, open **Test**, and confirm that the UI testing bundle appears in the test list and is enabled. Xcode creates a starter test file in the new target. You can replace its example test when you write your first UI test.

## Write your first UI test in Swift

A UI test can check whether tapping a button produces the screen a user expects. Give the button and its destination stable accessibility identifiers in your app code. For example, a SwiftUI view with a `showWelcome` state property could use these identifiers.

`Button("Continue") { showWelcome = true }  .accessibilityIdentifier("continueButton")  if showWelcome {  Text("Welcome")  .accessibilityIdentifier("welcomeTitle") }`

In the UI test target, `XCUIApplication` represents the app under test. Call `launch()` before querying its elements. The test below finds the button by identifier, taps it, and checks the resulting text.

`import XCTest  final class OnboardingUITests: XCTestCase {  @MainActor  func testContinueShowsWelcome() {  let app = XCUIApplication()  app.launch()   let continueButton = app.buttons["continueButton"]  XCTAssertTrue(continueButton.waitForExistence(timeout: 5))  continueButton.tap()   let welcomeTitle = app.staticTexts["welcomeTitle"]  XCTAssertTrue(welcomeTitle.waitForExistence(timeout: 5))  XCTAssertEqual(welcomeTitle.label, "Welcome")  } }`

[`waitForExistence(timeout:)`](https://developer.apple.com/documentation/xcuiautomation/xcuielement/waitforexistence(timeout:)) gives each element time to appear before the test interacts with or inspects it. `XCTAssertTrue` fails if the element never appears, while `XCTAssertEqual` checks that the destination displays the expected label.

## Run UI tests from the command line with xcodebuild

Run the UI test target through your app’s Xcode scheme so the tests use the same build configuration you selected in Xcode. Replace `<App>` with your scheme name and `<device>` with an installed simulator name.

`xcodebuild test \  -scheme "<App>" \  -destination 'platform=iOS Simulator,name=<device>'`

If you do not know which simulators the scheme can use, list its available destinations first.

`xcodebuild -showdestinations -scheme "<App>"`

During development, `-only-testing` runs a single test method instead of the full suite. Use the UI test target, test class, and method names from your project.

`xcodebuild test \  -scheme "<App>" \  -destination 'platform=iOS Simulator,name=<device>' \  -only-testing:MyAppUITests/OnboardingUITests/testContinueShowsWelcome`

## Run XCUITest in CI on GitHub Actions

A CI run needs an installed iOS simulator and a result bundle you can inspect when a test fails. This GitHub Actions job selects Xcode, boots an iPhone 16 simulator, and runs the UI test scheme. Replace `MyApp` with your project and scheme names.

`name: iOS UI tests  on:  pull_request:  jobs:  ui-tests:  runs-on: macos-15  steps:  - uses: actions/checkout@v4   - uses: maxim-lobanov/setup-xcode@v1  with:  xcode-version: latest-stable   - name: Boot simulator  run: |  xcrun simctl boot "iPhone 16"  xcrun simctl bootstatus "iPhone 16" -b   - name: Run UI tests  run: |  xcodebuild test \  -project MyApp.xcodeproj \  -scheme MyApp \  -destination 'platform=iOS Simulator,name=iPhone 16' \  -resultBundlePath TestResults.xcresult   - name: Upload test results  if: always()  uses: actions/upload-artifact@v4  with:  name: ui-test-results  path: TestResults.xcresult  if-no-files-found: ignore`

The selected Xcode installation must include the named simulator. If the boot step cannot find it, run `xcrun simctl list devices available` on the runner and use an installed device name in both steps. The result bundle preserves test failures for inspection after the job ends.

## Common XCUITest failures and fixes

XCUITest failures usually come from a mismatch between the test’s assumptions and the app or simulator state. Check the failure point before changing an assertion.

**Timing.** If an element appears after a network response or animation, an immediate lookup can fail even when the screen works. Wait for the specific element instead of adding a fixed sleep. Apple documents [`waitForExistence(timeout:)`](https://developer.apple.com/documentation/xcuiautomation/xcuielement/waitforexistence(timeout:)) for this purpose.

`let results = app.staticTexts["searchResults"] XCTAssertTrue(results.waitForExistence(timeout: 10))`

**Simulator state.** A test that passes alone but fails after another test may depend on stored login data, permissions, or an unfinished screen. Reset relevant app state before each test. When you need a clean device, shut down and erase a dedicated test simulator, then rerun the suite in the same order that failed.

**System alerts.** iOS permission dialogs appear outside your app’s view hierarchy. Register an [interruption monitor](https://developer.apple.com/documentation/xctest/xctestcase/adduiinterruptionmonitor(withdescription:handler:)) before the action that triggers the alert, then interact with the app so XCUITest invokes the monitor. Match the button your test expects, since alert text can vary by permission and language.

`addUIInterruptionMonitor(withDescription: "Notifications permission") { alert in  let allow = alert.buttons["Allow"]  guard allow.exists else { return false }  allow.tap()  return true }  app.buttons["Enable notifications"].tap() app.tap()`

**Locator drift.** A test can fail after a UI change because its label-based query no longer matches. Give important controls stable accessibility identifiers, and update the test when the control’s behavior changes. If a selector still resolves but targets the wrong element, inspect the accessibility hierarchy before changing the assertion.

## Where XCUITest stops

[XCUITest](https://developer.apple.com/documentation/xcuiautomation) works well when you need to verify an iOS app through its native interface. You still maintain the test code as the UI changes. Accessibility identifiers keep queries independent of visible labels, but a removed identifier or changed navigation path can still break a test.

XCUITest also keeps your suite within Apple’s testing environment. The same Swift UI tests will not run against an Android app or a web app, so you need separate coverage for those surfaces.

If you are evaluating another approach, read [how Momentic handles iOS testing](https://momentic.ai/ios-testing) and its [guide to XCUITest alternatives](https://momentic.ai/blog/xcuitest-alternatives). Check supported app types, execution environments, and CI requirements before moving tests out of an existing suite.

## Frequently asked questions

Is XCUITest free?    Yes. XCUITest ships with Xcode, which you can download for free. You need a Mac to develop and run the tests.    Can XCUITest run on real devices?    Yes. You can run XCUITest UI tests on a connected iPhone or iPad as well as an iOS Simulator. For device runs, configure code signing for the app and UI test target, then select the device as the test destination.    XCUITest vs Appium?    XCUITest is Apple's framework for testing iOS apps. Appium provides a cross-platform test interface and uses its XCUITest driver to automate iOS apps. Choose XCUITest when you want to write an iOS-specific suite in Xcode. Appium can make more sense when you need one automation interface for iOS and Android, though each platform still needs its own setup and selectors.    Can I write XCUITest tests in Objective-C?    Yes. XCTest supports Objective-C test classes. Import XCTest, subclass XCTestCase, and use the XCUITest APIs through Objective-C syntax. The Swift examples in this guide need translation rather than a direct copy into an Objective-C file.

Still have additional questions?

## Keep reading.

[Resources   What replaces Appium for Android end-to-end tests? Lower-maintenance options compared     Espresso, UI Automator, Maestro, Detox and Momentic compared for Android end-to-end tests: maintenance cost, CI fit and limits.     Wei-Wei Wu     10 min read](/blog/appium-alternatives-android-e2e)[Resources   How to run iOS and Android UI tests in CI: GitHub Actions, GitLab CI, Bitrise and CircleCI     Working CI config for iOS simulator and Android emulator UI tests in GitHub Actions, GitLab CI, Bitrise and CircleCI, plus how to skip macOS runners.     Wei-Wei Wu     10 min read](/blog/run-ios-android-ui-tests-in-ci)[Resources   Best Android Testing Tools: 11 Tools Compared     Espresso, Appium, Maestro, Detox, Momentic and six more Android testing tools, compared by testing layer, cross-platform support, selector maintenance and pricing model.     Wei-Wei Wu     5 min read](/blog/best-android-testing-tools)

## Close the feedback loop.

Point Momentic at your app. Free to start, no credit card.

[Try for free](https://app.momentic.ai/signup) [Contact sales](/sales)
