XCUIElement is the core XCUITest object for locating and interacting with UI elements in an iOS application. For SDETs, understanding XCUIElement means moving beyond simple tap() calls toward reliable element queries, accessibility identifiers, synchronization, state validation, and maintainable page-object architecture.
What is XCUIElement?
XCUIElement represents a UI element in the application hierarchy exposed through XCUITest. It provides APIs for querying, inspecting, and interacting with elements such as buttons, text fields, labels, images, switches, cells, and other controls.
A basic interaction looks like this:
let app = XCUIApplication()
app.launch()
let loginButton = app.buttons["loginButton"]
loginButton.tap()
The architecture is:
XCTestCase
↓
XCUIApplication
↓
XCUIElementQuery
↓
XCUIElement
↓
Interaction / Assertion
XCUIApplication controls the application.
XCUIElement represents the specific UI object that the test needs to inspect or manipulate.
Key Points
-
XCUIElementrepresents a UI element. - Elements are normally obtained through
XCUIElementQuery. - Accessibility identifiers provide stable selectors.
-
existschecks whether an element currently exists. -
isHittablechecks whether an element can currently receive interaction. -
tap()performs a tap interaction. -
typeText()enters text into supported elements. -
clearText()removes existing text from supported text fields. -
waitForExistence(timeout:)provides condition-based synchronization. -
XCUIElementsupports assertions against UI state. - Reliable selectors are more important than simply finding an element.
XCUIElement vs XCUIElementQuery
These concepts are closely related but have different responsibilities.
XCUIElementQuery
A query identifies elements:
let loginButton = app.buttons["loginButton"]
XCUIElement
The resulting object represents the element that the test can inspect or interact with:
loginButton.tap()
Conceptually:
XCUIElementQuery
↓
Find
↓
XCUIElement
↓
Inspect / Interact
This distinction becomes important when designing scalable XCUITest frameworks.
Finding UI Elements
XCUITest provides several query strategies.
Buttons
let loginButton = app.buttons["loginButton"]
Text Fields
let emailField = app.textFields["emailField"]
Secure Text Fields
let passwordField = app.secureTextFields["passwordField"]
Static Text
let welcomeMessage = app.staticTexts["Welcome"]
Images
let logo = app.images["appLogo"]
Switches
let notificationsSwitch = app.switches["notificationsSwitch"]
Cells
let firstCell = app.cells.element(boundBy: 0)
The selector should describe the UI element’s automation identity rather than relying on its visual position.
Accessibility Identifiers
For production-grade automation, accessibility identifiers are usually the preferred way to create stable selectors.
In the application:
loginButton.accessibilityIdentifier = "loginButton"
In XCUITest:
let loginButton = app.buttons["loginButton"]
loginButton.tap()
This is significantly more maintainable than depending on visible text:
app.buttons["Log In"].tap()
Why?
The visible text might change because of:
- Localization
- Product wording changes
- A/B testing
- Branding changes
- UX redesign
The automation identifier can remain stable.
Recommended Selector Hierarchy
For maintainable XCUITest automation, prefer selectors roughly in this order:
Accessibility Identifier
↓
Accessibility Label / Exact Text
↓
Predicate Query
↓
Hierarchy-Based Query
↓
Index-Based Query
The further down the list you go, the more fragile the selector can become.
For example:
app.buttons["loginButton"]
is generally preferable to:
app.buttons.element(boundBy: 3)
because the third button can change when the UI changes.
Interacting With XCUIElement
Once an element has been located, XCUITest provides interaction APIs.
Tap
let loginButton = app.buttons["loginButton"]
loginButton.tap()
Double Tap
loginButton.doubleTap()
Long Press
loginButton.press(forDuration: 2)
Swipe
app.swipeUp()
For element-specific gestures:
loginButton.swipeLeft()
The interaction should normally occur only after the test has established that the element is available.
Waiting for an Element
One of the most important synchronization APIs is:
waitForExistence(timeout:)
Example:
let loginButton = app.buttons["loginButton"]
XCTAssertTrue(
loginButton.waitForExistence(timeout: 10)
)
loginButton.tap()
This is better than:
sleep(5)
loginButton.tap()
The difference is fundamental.
sleep()
↓
Wait fixed duration
waitForExistence()
↓
Wait until condition becomes true
Condition-based synchronization generally produces more resilient automation.
Checking Element Existence
You can inspect:
if loginButton.exists {
loginButton.tap()
}
For assertions:
XCTAssertTrue(
loginButton.exists
)
However, exists is an immediate state check.
For asynchronous UI loading, prefer:
XCTAssertTrue(
loginButton.waitForExistence(timeout: 10)
)
Checking Whether an Element Is Hittable
An element can exist without being interactable.
Use:
XCTAssertTrue(
loginButton.isHittable
)
👉 Continue reading the full article on skakarh.com →
Originally published at skakarh.com/xcuielement-finding-ios-ui-elements.
Subscribe to QA Pulse by SK —
weekly signal for QA, Test Automation and AI in Software Engineering.
Top comments (0)