DEV Community

Cover image for XCUITest Test Utilities: Build Reusable Helpers for Scalable iOS UI Automation
QAPulse by SK
QAPulse by SK

Posted on Originally published at skakarh.com

XCUITest Test Utilities: Build Reusable Helpers for Scalable iOS UI Automation

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
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Avoid:

TestAutomationManager
Enter fullscreen mode Exit fullscreen mode

containing:

wait()
scroll()
login()
captureScreenshot()
callAPI()
createUser()
tapButton()
verifyCheckout()
Enter fullscreen mode Exit fullscreen mode

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
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

Usage:

let loginButton =
    app.buttons["login.button"]

XCTAssertTrue(
    WaitUtility.forExistence(
        loginButton
    )
)
Enter fullscreen mode Exit fullscreen mode

This creates one consistent waiting mechanism.

Avoid Fixed Sleeps

Avoid:

sleep(5)
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

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
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

👉 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)