XCUITest Page Object Model is a practical design approach for organizing iOS UI automation into reusable screen-level components instead of placing every locator, action, wait, and assertion directly inside test cases. For growing XCUITest suites, this separation improves maintainability, readability, reuse, debugging, and team scalability.
What is XCUITest Page Object Model?
XCUITest Page Object Model is an automation design pattern where each important iOS screen or reusable UI component is represented by a Swift class containing its locators and user actions.
Instead of writing this directly inside every test:
let email = app.textFields["login.email"]
email.tap()
email.typeText("qa@example.com")
let password = app.secureTextFields["login.password"]
password.tap()
password.typeText("Password123")
app.buttons["login.button"].tap()
A Page Object encapsulates the implementation:
let loginPage = LoginPage(app: app)
loginPage.login(
email: "qa@example.com",
password: "Password123"
)
The test focuses on business behavior, while the Page Object manages UI implementation details.
Definition
XCUITest Page Object Model is a Swift-based test architecture that encapsulates iOS screen locators, interactions, synchronization, and reusable UI behavior inside dedicated Page Object classes.
Key Points
- Create one Page Object for each important screen.
- Keep locators inside Page Objects.
- Keep reusable actions inside Page Objects.
- Keep business scenarios inside test classes.
- Prefer accessibility identifiers.
- Centralize synchronization.
- Avoid duplicated UI queries.
- Return Page Objects when navigation changes screens.
- Keep assertions close to the state they validate.
- Avoid putting test-specific business logic into Page Objects.
- Use component objects for reusable UI sections.
- Keep Page Objects small and focused.
- Use dependency injection for
XCUIApplication. - Design Page Objects around user behavior, not implementation details.
- Refactor repeated workflows into reusable methods.
Why Use the Page Object Pattern in XCUITest?
A small UI test suite can survive without an architectural layer.
For example:
func testLogin() {
let app = XCUIApplication()
app.launch()
app.textFields["login.email"]
.tap()
app.textFields["login.email"]
.typeText("qa@example.com")
app.secureTextFields["login.password"]
.tap()
app.secureTextFields["login.password"]
.typeText("Password123")
app.buttons["login.button"]
.tap()
}
The problem appears when dozens of tests repeat the same locators.
Imagine 40 tests containing:
app.textFields["login.email"]
If the accessibility identifier changes, every test potentially needs modification.
With a Page Object:
final class LoginPage {
private let app: XCUIApplication
init(app: XCUIApplication) {
self.app = app
}
private var emailField:
XCUIElement {
app.textFields["login.email"]
}
private var passwordField:
XCUIElement {
app.secureTextFields["login.password"]
}
private var loginButton:
XCUIElement {
app.buttons["login.button"]
}
}
The locator exists in one place.
That is the central architectural advantage.
Page Object Model Architecture
A scalable XCUITest project can follow this structure:
XCUITest Target
│
├── Tests
│ ├── LoginTests.swift
│ ├── CheckoutTests.swift
│ └── SearchTests.swift
│
├── Pages
│ ├── LoginPage.swift
│ ├── HomePage.swift
│ ├── SearchPage.swift
│ └── CheckoutPage.swift
│
├── Components
│ ├── NavigationBar.swift
│ ├── ProductCard.swift
│ └── AlertComponent.swift
│
├── Helpers
│ ├── WaitHelper.swift
│ └── TestData.swift
│
└── Base
└── BaseTest.swift
This separates responsibilities.
Tests
↓
Pages
↓
Components
↓
XCUITest API
↓
iOS Application
Page Objects vs Test Cases
A test case should describe what the user is trying to achieve.
A Page Object should describe how the user interacts with a screen.
Test
func testSuccessfulLogin() {
let loginPage =
LoginPage(app: app)
let homePage =
loginPage.login(
email: "qa@example.com",
password: "Password123"
)
XCTAssertTrue(
homePage.isDisplayed
)
}
Page Object
final class LoginPage {
private let app: XCUIApplication
init(app: XCUIApplication) {
self.app = app
}
func login(
email: String,
password: String
) -> HomePage {
emailField.tap()
emailField.typeText(email)
passwordField.tap()
passwordField.typeText(password)
loginButton.tap()
return HomePage(app: app)
}
}
The test is easier to understand because implementation details are hidden.
Building Your First Page Object
Start with the screen’s elements.
For a login screen:
Login Screen
├── Email
├── Password
├── Login
├── Forgot Password
└── Sign Up
Create:
final class LoginPage {
private let app: XCUIApplication
init(app: XCUIApplication) {
self.app = app
}
}
👉 Continue reading the full article on skakarh.com →
Originally published at skakarh.com/xcuitest-page-object-model.
Subscribe to QA Pulse by SK —
weekly signal for QA, Test Automation and AI in Software Engineering.
Top comments (0)