XCUITest test utilities provide the reusable infrastructure needed to keep iOS UI automation consistent, readable, and maintainable as a test suite grows. Instead of repeating waits, screenshots, scrolling, keyboard handling, application setup, element checks, and common gestures across dozens of tests, SDETs can centralize these operations into focused Swift utilities.
What are XCUITest Test Utilities?
XCUITest test utilities are reusable Swift helpers that encapsulate common automation operations used across multiple XCUITest cases, such as synchronization, screenshots, application lifecycle management, scrolling, keyboard control, element validation, and reusable gestures.
The goal is simple:
Repeated Automation Logic
↓
Reusable Utility
↓
Consistent Test Behavior
Apple’s XCUIAutomation framework provides APIs for controlling the application UI, querying elements, performing gestures, and inspecting UI state. XCUIElement also provides state checks and waiting capabilities such as exists, isHittable, and waitForExistence(timeout:). (Apple Developer)
Key Points
- Centralize repeated automation logic.
- Keep utilities focused on one responsibility.
- Avoid duplicating synchronization code.
- Reuse screenshot and diagnostic helpers.
- Create application lifecycle helpers.
- Build safe scrolling utilities.
- Encapsulate keyboard handling.
- Create reusable element validation methods.
- Prefer accessibility identifiers.
- Keep Page Objects focused on screen behavior.
- Keep test utilities independent from business scenarios.
- Avoid creating one giant utility class.
- Make helpers deterministic and configurable.
- Use utilities to reduce maintenance, not hide test intent.
Why Reusable Utilities Matter in XCUITest
A small test suite may contain code like:
app.buttons["login"].waitForExistence(
timeout: 10
)
app.swipeUp()
XCTAssertTrue(
app.staticTexts["Welcome"].exists
)
As the suite grows, the same operations appear everywhere.
You might eventually have:
50 tests
20 repeated waits
30 scrolling implementations
40 screenshot blocks
25 keyboard-handling blocks
The problem is not only duplicated code.
It is inconsistent behavior.
One test may wait five seconds.
Another may wait ten.
Another may use sleep().
Another may check exists.
Another may check isHittable.
Reusable utilities create a common automation vocabulary.
Utilities vs Page Objects
Utilities and Page Objects solve different problems.
For example:
LoginTests
↓
LoginPage
↓
WaitUtility
↓
XCUIElement
The Page Object knows what screen behavior is required.
The utility knows how a generic operation should be performed.
Recommended Project Structure
A scalable XCUITest target can use:
UITests/
│
├── Tests/
│ ├── LoginTests.swift
│ ├── CheckoutTests.swift
│ └── SearchTests.swift
│
├── Pages/
│ ├── LoginPage.swift
│ ├── HomePage.swift
│ └── CheckoutPage.swift
│
├── Components/
│ ├── ProductCard.swift
│ └── NavigationBar.swift
│
├── Utilities/
│ ├── WaitUtility.swift
│ ├── ScreenshotUtility.swift
│ ├── ScrollUtility.swift
│ ├── KeyboardUtility.swift
│ └── ElementUtility.swift
│
└── Base/
└── BaseUITest.swift
This structure prevents generic helpers from becoming mixed with screen-specific automation.
Utility Design Principle: One Responsibility
A good utility should have a narrow purpose.
Good:
WaitUtility
ScreenshotUtility
ScrollUtility
KeyboardUtility
ElementUtility
Avoid:
TestAutomationManager
containing:
wait()
scroll()
login()
captureScreenshot()
callAPI()
createUser()
tapButton()
verifyCheckout()
That class eventually becomes a God Utility.
Building a Wait Utility
Synchronization is one of the most important areas for reusable automation infrastructure.
Apple provides waitForExistence(timeout:) for waiting until an element exists. (Apple Developer)
A simple helper:
enum WaitUtility {
@discardableResult
static func forExistence(
_ element: XCUIElement,
timeout: TimeInterval = 10
) -> Bool {
element.waitForExistence(
timeout: timeout
)
}
}
Usage:
let loginButton =
app.buttons["login.button"]
XCTAssertTrue(
WaitUtility.forExistence(
loginButton
)
)
This creates one consistent waiting mechanism.
Avoid Fixed Sleeps
Avoid:
sleep(5)
Fixed delays do not represent application state.
If the application becomes ready after one second, four seconds are wasted.
If the application needs seven seconds, five seconds may be insufficient.
Prefer:
loginButton.waitForExistence(
timeout: 10
)
This makes synchronization condition-based.
Waiting for Disappearance
Utilities can also support elements that should disappear.
enum WaitUtility {
static func forDisappearance(
_ element: XCUIElement,
timeout: TimeInterval = 10
) -> Bool {
element.waitForNonExistence(
timeout: timeout
)
}
}
👉 Continue reading the full article on skakarh.com →
Originally published at skakarh.com/xcuitest-test-utilities.
Subscribe to QA Pulse by SK —
weekly signal for QA, Test Automation and AI in Software Engineering.
Top comments (0)