Mobile Testing

XCUITest Actions: Tap, Type, Swipe, Scroll and Long Press

Master XCUITest actions with practical Swift examples for tapping, typing, swiping, scrolling, and long-press interactions in reliable iOS UI automation.

13 min read
XCUITest Actions: Tap, Type, Swipe, Scroll and Long Press
Advertisement
What You Will Learn
What are XCUITest Actions?
Definition
Key Points
The XCUITest Action Model
⚡ Quick Answer
XCUITest Actions are APIs that QA engineers and SDETs use to programmatically interact with iOS UI elements during automated tests. These actions, including tap, typeText, swipe, and long press, enable you to simulate real user behavior to validate application responses and entire user journeys. You apply these powerful interaction methods to specific UI elements after locating them, ensuring robust validation of application states.

XCUITest Actions are the interaction layer of iOS UI automation. After a test locates an XCUIElement, actions such as tap(), typeText(), swipeUp(), swipeDown(), and long press allow the test to reproduce real user behavior and validate application responses.

What are XCUITest Actions?

XCUITest actions are APIs provided by Apple’s XCUITest framework for interacting with UI elements during automated iOS tests.

A typical automation flow looks like this:

Code
XCUIApplication
      ↓
XCUIElementQuery
      ↓
XCUIElement
      ↓
XCUITest Action
      ↓
Application State Change
      ↓
Assertion

For example:

JavaScript
let app = XCUIApplication()

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

loginButton.tap()

The test first identifies the element and then performs an action against it.

Definition

XCUITest actions are interaction methods used to tap, type, swipe, scroll, press, and otherwise manipulate iOS UI elements during automated UI tests.

They allow SDETs to validate complete user journeys instead of testing application screens only through static assertions.

Key Points

  • tap() performs a standard tap.
  • doubleTap() performs a double tap.
  • typeText() enters text into supported controls.
  • swipeUp() and swipeDown() perform common swipe gestures.
  • swipeLeft() and swipeRight() support horizontal gestures.
  • press(forDuration:) performs long press interactions.
  • swipe(to:) supports element-to-element drag interactions.
  • waitForExistence() helps synchronize interactions.
  • isHittable helps determine whether an element can currently receive interaction.
  • Coordinate-based gestures should be used only when element-level interaction is insufficient.
  • Every important action should lead to a meaningful assertion.

The XCUITest Action Model

A reliable test should separate four responsibilities:

Code
Find
  ↓
Wait
  ↓
Act
  ↓
Verify

For example:

JavaScript
let app = XCUIApplication()

let emailField =
    app.textFields["login.emailField"]

XCTAssertTrue(
    emailField.waitForExistence(timeout: 10)
)

emailField.tap()
emailField.typeText("qa@example.com")

XCTAssertEqual(
    emailField.value as? String,
    "qa@example.com"
)

The test does not simply interact with the UI.

It establishes that the UI is ready, performs the action, and validates the resulting state.

1. Tap Actions

The most common interaction is:

Code
element.tap()

Example:

JavaScript
let app = XCUIApplication()

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

XCTAssertTrue(
    loginButton.waitForExistence(timeout: 10)
)

loginButton.tap()

For a test framework, this is preferable to coordinate tapping because the action is associated with the semantic UI element.

Double Tap

Some applications use double-tap interactions.

JavaScript
let image =
    app.images["profile.avatar"]

image.doubleTap()

This can be used for behaviors such as:

  • Zoom
  • Favorite actions
  • Image interactions
  • Custom gestures

Use it only when double tapping is part of the actual product behavior.

2. Type Actions

Text entry is another core XCUITest interaction.

JavaScript
let emailField =
    app.textFields["login.emailField"]

emailField.tap()
emailField.typeText("qa@example.com")

For secure fields:

JavaScript
let passwordField =
    app.secureTextFields["login.passwordField"]

passwordField.tap()
passwordField.typeText("Password123!")

Clear Existing Text

A field may already contain text.

A common approach is:

JavaScript
let field =
    app.textFields["profile.nameField"]

field.tap()

field.press(forDuration: 1.0)

However, long pressing does not universally provide a reliable “select all” behavior across application implementations.

A more robust test architecture is to start from a known application state.

For example:

JavaScript
func testUpdateName() {

    let app = XCUIApplication()
    app.launchArguments = ["-UITestResetState"]
    app.launch()

    let nameField =
        app.textFields["profile.nameField"]

    XCTAssertTrue(
        nameField.waitForExistence(timeout: 10)
    )

    nameField.tap()
    nameField.typeText("Shahnawaz")

    XCTAssertEqual(
        nameField.value as? String,
        "Shahnawaz"
    )
}

The principle is important:

Control test state instead of relying on unpredictable editing behavior.

Advertisement

3. Swipe Actions

XCUITest provides directional swipe methods.

Code
element.swipeUp()
element.swipeDown()
element.swipeLeft()
element.swipeRight()

Example:

JavaScript
let app = XCUIApplication()

let table =
    app.tables["settings.table"]

table.swipeUp()

A swipe is useful for:

  • Moving through lists
  • Revealing content
  • Navigating collection views
  • Testing horizontally scrolling interfaces
  • Triggering swipe-based UI behavior

4. Scroll Actions

Scrolling deserves special attention because a scroll is often used to make another element available for interaction.

Example:

JavaScript
let app = XCUIApplication()

let settings =
    app.tables["settings.table"]

settings.swipeUp()

let logoutButton =
    app.buttons["settings.logoutButton"]

XCTAssertTrue(
    logoutButton.waitForExistence(timeout: 5)
)

logoutButton.tap()

The test performs:

Code
Settings Table
      ↓
Swipe Up
      ↓
Logout Becomes Available
      ↓
Wait
      ↓
Tap
      ↓
Verify

Repeated Scrolling

Avoid arbitrary fixed numbers of swipes when possible.

Weak:

Code
settings.swipeUp()
settings.swipeUp()
settings.swipeUp()
settings.swipeUp()

This assumes the UI always has the same layout and content.

A better approach is to synchronize against the target state.

JavaScript
let logoutButton =
    app.buttons["settings.logoutButton"]

for _ in 0..<5 {

    if logoutButton.exists &&
       logoutButton.isHittable {
        break
    }

    settings.swipeUp()
}

XCTAssertTrue(
    logoutButton.waitForExistence(timeout: 5)
)

XCTAssertTrue(
    logoutButton.isHittable
)

logoutButton.tap()

The loop has a bounded limit, preventing an infinite test.

5. Long Press

Long press is available through:

Code
element.press(forDuration:)

Example:

JavaScript
let message =
    app.staticTexts["message.item"]

message.press(forDuration: 1.5)

This can be useful for:

  • Context menus
  • Reordering
  • Selection
  • Text interaction
  • Custom long-press features

The duration should reflect the application’s intended interaction rather than being arbitrarily large.

For example:

Code
message.press(forDuration: 1.0)

is usually easier to reason about than:

Code
message.press(forDuration: 7.0)

6. Drag and Drop

Some interactions require moving one UI element toward another.

Conceptually:

JavaScript
let source =
    app.otherElements["item.source"]

let destination =
    app.otherElements["item.destination"]

source.press(
    forDuration: 0.5,
    thenDragTo: destination
)

This type of interaction is useful for:

  • Reordering lists
  • Moving cards
  • Drag-and-drop workflows
  • Custom UI interactions

The exact API available depends on the XCUITest/XCUIElement APIs used by your Xcode version.

Action Comparison

ActionPrimary UseStabilityCommon Example
tap()Button/control interactionHighSubmit
doubleTap()Double-tap behaviorHighZoom
typeText()Text entryHighLogin
swipeUp()Vertical navigationHighScroll list
swipeDown()Reverse vertical navigationHighRefresh/reveal
swipeLeft()Horizontal interactionHighDelete/reveal
swipeRight()Horizontal interactionHighNavigation
press(forDuration:)Long pressHighContext menu
Drag interactionReordering/movingMediumMove item
Coordinate gestureExceptional casesLowCustom canvas
Visualisation of five primary XCUITest actions
Visualisation of five primary XCUITest actions

Synchronization Before Actions

One of the biggest causes of unreliable UI tests is interacting with an element before it is ready.

Weak:

Code
sleep(5)

app.buttons["checkout.payButton"].tap()

Better:

JavaScript
let payButton =
    app.buttons["checkout.payButton"]

XCTAssertTrue(
    payButton.waitForExistence(timeout: 10)
)

payButton.tap()

waitForExistence(timeout:) provides a condition-based mechanism for waiting for an element to exist.

Existence Is Not the Same as Hittability

An element can exist in the accessibility hierarchy but not currently be interactable.

Advertisement

For example:

Code
XCTAssertTrue(
    button.exists
)

does not necessarily mean:

Code
button.isHittable

is true.

For interaction-heavy tests:

Code
XCTAssertTrue(
    button.waitForExistence(timeout: 10)
)

XCTAssertTrue(
    button.isHittable
)

button.tap()

This creates a stronger interaction contract.

Waiting for Scroll Targets

Suppose a button is initially outside the visible area.

This can fail:

JavaScript
let deleteButton =
    app.buttons["item.deleteButton"]

deleteButton.tap()

Instead:

JavaScript
let deleteButton =
    app.buttons["item.deleteButton"]

let list =
    app.collectionViews["items.collection"]

for _ in 0..<6 {

    if deleteButton.exists &&
       deleteButton.isHittable {
        break
    }

    list.swipeUp()
}

XCTAssertTrue(
    deleteButton.exists
)

XCTAssertTrue(
    deleteButton.isHittable
)

deleteButton.tap()

This pattern combines:

  • Locator
  • Bounded scrolling
  • Visibility detection
  • Interaction
  • Assertion

Testing a Complete Login Interaction

A technical XCUITest should model the complete workflow.

JavaScript
func testSuccessfulLogin() {

    let app = XCUIApplication()
    app.launch()

    let email =
        app.textFields["login.emailField"]

    let password =
        app.secureTextFields["login.passwordField"]

    let login =
        app.buttons["login.submitButton"]

    XCTAssertTrue(
        email.waitForExistence(timeout: 10)
    )

    email.tap()
    email.typeText("qa@example.com")

    password.tap()
    password.typeText("Password123!")

    XCTAssertTrue(
        login.isHittable
    )

    login.tap()

    let dashboard =
        app.staticTexts["Dashboard"]

    XCTAssertTrue(
        dashboard.waitForExistence(timeout: 10)
    )
}

The action sequence is:

Code
Launch
  ↓
Find Email
  ↓
Tap
  ↓
Type
  ↓
Find Password
  ↓
Tap
  ↓
Type
  ↓
Tap Login
  ↓
Wait for Dashboard
  ↓
Assert

This is the core pattern behind most end-to-end mobile UI tests.

Combining Actions With Assertions

An action without verification is incomplete.

Weak:

Code
loginButton.tap()

Better:

Code
loginButton.tap()

XCTAssertTrue(
    app.staticTexts["Dashboard"]
        .waitForExistence(timeout: 10)
)

For scrolling:

Code
settings.swipeUp()

XCTAssertTrue(
    app.buttons["settings.logoutButton"]
        .waitForExistence(timeout: 5)
)

For typing:

Code
emailField.tap()
emailField.typeText("qa@example.com")

XCTAssertEqual(
    emailField.value as? String,
    "qa@example.com"
)

The test should validate the result of the action, not merely execute it.

Action Abstraction With Helper Methods

Large automation suites should avoid repeating synchronization and interaction code.

Example:

Code
extension XCUIElement {

    func tapWhenReady(
        timeout: TimeInterval = 10
    ) {
        XCTAssertTrue(
            waitForExistence(timeout: timeout)
        )

        XCTAssertTrue(isHittable)

        tap()
    }
}

Now tests can use:

Code
app.buttons["login.submitButton"]
    .tapWhenReady()

Text input can also be abstracted:

Code
extension XCUIElement {

    func typeTextWhenReady(
        _ text: String,
        timeout: TimeInterval = 10
    ) {
        XCTAssertTrue(
            waitForExistence(timeout: timeout)
        )

        XCTAssertTrue(isHittable)

        tap()
        typeText(text)
    }
}

Test:

Code
app.textFields["login.emailField"]
    .typeTextWhenReady("qa@example.com")

This creates a reusable action layer.

Page Object Action Layer

A Page Object can expose business actions instead of low-level UI interactions.

Code
final class LoginPage {

    private let app: XCUIApplication

    init(app: XCUIApplication) {
        self.app = app
    }

    private var emailField:
        XCUIElement {
        app.textFields["login.emailField"]
    }

    private var passwordField:
        XCUIElement {
        app.secureTextFields[
            "login.passwordField"
        ]
    }

    private var loginButton:
        XCUIElement {
        app.buttons["login.submitButton"]
    }

    func enterEmail(_ email: String) {

        emailField.tapWhenReady()
        emailField.typeText(email)
    }

    func enterPassword(_ password: String) {

        passwordField.tapWhenReady()
        passwordField.typeText(password)
    }

    func submit() {

        loginButton.tapWhenReady()
    }
}

Test:

Advertisement
JavaScript
func testLogin() {

    let loginPage =
        LoginPage(app: app)

    loginPage.enterEmail(
        "qa@example.com"
    )

    loginPage.enterPassword(
        "Password123!"
    )

    loginPage.submit()

    XCTAssertTrue(
        app.staticTexts["Dashboard"]
            .waitForExistence(timeout: 10)
    )
}

The test now expresses business behavior rather than implementation details.

Handling Swipe and Scroll Reliability

Scrolling can be particularly sensitive to UI layout.

Avoid:

Code
for _ in 0..<20 {
    list.swipeUp()
}

unless the test genuinely requires 20 gestures.

Instead, define a bounded search:

Code
func scrollTo(
    _ element: XCUIElement,
    in container: XCUIElement,
    maxSwipes: Int = 8
) -> Bool {

    for _ in 0..<maxSwipes {

        if element.exists &&
           element.isHittable {
            return true
        }

        container.swipeUp()
    }

    return element.exists &&
           element.isHittable
}

Usage:

JavaScript
let logoutButton =
    app.buttons["settings.logoutButton"]

let settings =
    app.tables["settings.table"]

XCTAssertTrue(
    scrollTo(
        logoutButton,
        in: settings
    )
)

logoutButton.tap()

This approach is reusable across screens.

Gesture Strategy

A practical gesture strategy is:

Code
Semantic Element Interaction
          ↓
      tap / type
          ↓
Element Swipe / Scroll
          ↓
Long Press
          ↓
Drag Interaction
          ↓
Coordinate Gesture

The closer the action is to the semantic UI element, the easier the test is generally to maintain.

6 Core Pillars of XCUITest Actions

1. Semantic Interaction

Interact with elements through their meaningful UI representation.

2. Synchronization

Wait for the element to exist and become actionable.

3. Realistic Gestures

Use gestures that represent actual user behavior.

4. Bounded Scrolling

Avoid infinite or arbitrary gesture loops.

5. Action Abstraction

Centralize repeated interaction patterns.

6. Result Validation

Every important action should produce a testable outcome.

Core Pillars of XCUITest Actions
Core Pillars of XCUITest Actions

Key Architectural Takeaways for SDETs

Actions Are Not Test Outcomes

Calling:

Code
button.tap()

does not prove the feature works.

The test should verify what happened after the interaction.

Synchronization Belongs in the Action Layer

Instead of repeating:

Code
wait
tap

throughout hundreds of tests, centralize reliable interaction patterns.

Scrolling Should Be State-Driven

Do not scroll because a fixed number of gestures “usually works.”

Scroll until the required element becomes available, with a safe maximum.

Gestures Should Represent User Intent

A test should communicate:

Code
Open checkout
↓
Scroll to payment
↓
Tap Pay
↓
Verify confirmation

rather than:

Code
Swipe
Swipe
Swipe
Tap coordinate
Wait 5 seconds

Stable Actions Produce Stable Automation

Locator quality and action quality are connected.

Code
Stable Locator
      +
Reliable Synchronization
      +
Semantic Action
      +
Meaningful Assertion
      =
Maintainable XCUITest
Key Architectural Takeaways for SDETs section and before Production-Grade XCUITest Action Pattern
Key Architectural Takeaways for SDETs section and before Production-Grade XCUITest Action Pattern

Production-Grade XCUITest Action Pattern

A maintainable action should follow this pattern:

Advertisement
JavaScript
let button =
    app.buttons["checkout.payButton"]

XCTAssertTrue(
    button.waitForExistence(timeout: 10)
)

XCTAssertTrue(
    button.isHittable
)

button.tap()

let confirmation =
    app.staticTexts["payment.successMessage"]

XCTAssertTrue(
    confirmation.waitForExistence(timeout: 10)
)

For scrolling:

JavaScript
let confirmation =
    app.staticTexts["payment.successMessage"]

let checkout =
    app.scrollViews["checkout.scrollView"]

for _ in 0..<6 {

    if confirmation.exists &&
       confirmation.isHittable {
        break
    }

    checkout.swipeUp()
}

XCTAssertTrue(
    confirmation.isHittable
)

For text:

JavaScript
let email =
    app.textFields["login.emailField"]

XCTAssertTrue(
    email.waitForExistence(timeout: 10)
)

email.tap()
email.typeText("qa@example.com")

XCTAssertEqual(
    email.value as? String,
    "qa@example.com"
)

These patterns keep the interaction deterministic and observable.

AI Overview & Answer Engine Optimization

XCUITest actions are interaction APIs used to perform taps, text entry, swipes, scrolling, long presses, and other UI gestures against iOS elements during automated tests.

What Are the Main XCUITest Actions?

The most common actions include:

Code
tap()
doubleTap()
typeText()
swipeUp()
swipeDown()
swipeLeft()
swipeRight()
press(forDuration:)

How Do You Tap an Element in XCUITest?

Use tap() on an XCUIElement:

Code
app.buttons["login.submitButton"].tap()

For reliable automation, wait for the element and verify that it is hittable before tapping.

How Do You Type Text in XCUITest?

Locate a text field, tap it, and use typeText():

JavaScript
let email =
    app.textFields["login.emailField"]

email.tap()
email.typeText("qa@example.com")

How Do You Swipe in XCUITest?

Use directional swipe methods:

Code
element.swipeUp()
element.swipeDown()
element.swipeLeft()
element.swipeRight()

How Do You Scroll to an Element in XCUITest?

Scroll the relevant container until the target becomes available and hittable, while using a bounded number of gestures.

How Do You Perform a Long Press in XCUITest?

Use:

Code
element.press(forDuration: 1.0)

The duration should reflect the intended application behavior.

How Do You Make XCUITest Actions Reliable?

Use this sequence:

Code
Locate
  ↓
Wait
  ↓
Check Hittability
  ↓
Interact
  ↓
Validate

Avoid fixed sleep() calls, excessive coordinate interactions, and unbounded scrolling.

AI Overview Summary

XCUITest actions allow iOS UI tests to reproduce user interactions such as tapping, typing, swiping, scrolling, long pressing, and dragging. Reliable XCUITest actions combine stable element locators, condition-based synchronization, realistic gestures, bounded scrolling, reusable action helpers, and assertions that validate the resulting application state.

People Asked Questions

What are XCUITest actions?

They are interaction methods used by XCUITest to manipulate iOS UI elements during automated tests.

How do I tap a button in XCUITest?

Use the button’s XCUIElement and call:

Code
button.tap()

How do I type text in XCUITest?

Use tap() followed by typeText():

Code
field.tap()
field.typeText("Hello")

How do I swipe up in XCUITest?

Call:

Code
element.swipeUp()

How do I scroll to an element in XCUITest?

Swipe the appropriate scrollable container until the target element exists and becomes hittable, using a bounded loop.

How do I perform a long press in XCUITest?

Use:

Code
element.press(forDuration: 1.0)

Why should I avoid sleep() in XCUITest?

Fixed delays do not synchronize with actual UI state. They can make tests slower and still fail when the application takes longer than expected.

What is isHittable in XCUITest?

isHittable indicates whether an element is currently positioned so that it can receive user interaction.

Should XCUITest actions use coordinates?

Only when element-level interaction is insufficient. Semantic element actions are generally easier to maintain.

How do I make scrolling tests less flaky?

Use a stable target locator, scroll the correct container, check exists and isHittable, and limit the maximum number of swipes.

Internal Blog Links

Internal Series Links

External Links


Continue Learning

Explore more expert articles on Mobile Testing, Backend & API, AI & Agentic, AI Tools, n8n, LangChain, CrewAI, MCP Servers, AI Agents, LlamaIndex, Docker, FastAPI, Playwright, Cypress, Test Automation, DevOps, and Software Engineering at www.skakarh.com.

QAPulse by SK delivers expert release analysis, AI engineering insights, enterprise automation strategies, migration guidance, DevOps best practices, and practical testing knowledge to help software professionals build scalable, intelligent, and production-ready software systems.

Frequently Asked Questions

What are XCUITest Actions?
XCUITest actions are APIs provided by Apple's XCUITest framework that serve as the interaction layer for iOS UI automation. They are interaction methods used to tap, type, swipe, scroll, press, and manipulate iOS UI elements during automated UI tests. This allows SDETs to validate complete user journeys instead of testing application screens only through static assertions.
What is the typical automation flow using XCUITest Actions?
A typical automation flow involves identifying an XCUIElement, performing an XCUITest Action against it, and then observing an application state change followed by an assertion. A reliable test should separate responsibilities into Find, Wait, Act, and Verify stages.
What are some common XCUITest actions for interacting with UI elements?
Common XCUITest actions include tap() for a standard tap, doubleTap() for double-tap interactions, and typeText() for entering text into controls. Swipe gestures are supported by methods such as swipeUp(), swipeDown(), swipeLeft(), and swipeRight(), while press(forDuration:) performs long press interactions.
Advertisement
Found this helpful? Clap to let Shahnawaz know — you can clap up to 50 times.