Ensuring Visibility of XCUIElements in Xcode UI Testing

Ensuring Visibility of XCUIElements in Xcode UI Testing

11 April 2024 Stephan Petzl Leave a comment Tech-Help

When automating user interface testing in Xcode 7, it’s crucial to determine whether certain elements within a UIScrollView, such as buttons and other controls, are visible on the screen. This can be challenging when elements may be out of view due to the user’s current scroll position. In this guide, we will explore how to programmatically scroll elements into view and check if they are visible within the UI.

Checking Element Visibility

To ascertain if an XCUIElement is visible, we can use a helper method that checks whether the element exists, has a non-zero frame, and is hittable. Here’s a Swift function that accomplishes this:

func elementIsWithinWindow(element: XCUIElement) -> Bool {
    guard element.exists && !element.frame.isEmpty && element.isHittable else { return false }
    return XCUIApplication().windows.element(boundBy: 0).frame.contains(element.frame)
}

This function checks that the element exists within the application, is not an empty frame, and is hittable (i.e., can be tapped). If these conditions are met, it then verifies that the element’s frame is within the bounds of the main application window.

Scrolling to Make Elements Visible

Since there’s no built-in scrollTo method, we need to create our own methods to scroll the view. The following functions allow you to scroll up or down within the main window:

func scrollDown(times: Int = 1) {
    let mainWindow = XCUIApplication().windows.firstMatch
    let topScreenPoint = mainWindow.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.05))
    let bottomScreenPoint = mainWindow.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.90))
    for _ in 0..<times {
        bottomScreenPoint.press(forDuration: 0, thenDragTo: topScreenPoint)
    }
}

func scrollUp(times: Int = 1) {
    let mainWindow = XCUIApplication().windows.firstMatch
    let topScreenPoint = mainWindow.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.05))
    let bottomScreenPoint = mainWindow.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.90))
    for _ in 0..<times {
        topScreenPoint.press(forDuration: 0, thenDragTo: bottomScreenPoint)
    }
}

These functions simulate a user’s swipe action to scroll the view up or down. You can adjust the number of times the scroll occurs by changing the ‘times’ parameter.

Looping Until an Element is Visible

To find an element that may be off-screen, you can use a loop to scroll until the element appears within the window frame:

func scrollUntilElementAppears(element: XCUIElement, threshold: Int = 10) {
    var iteration = 0

    while !elementIsWithinWindow(element: element) {
        guard iteration < threshold else { break }
        scrollDown()
        iteration += 1
    }

    if !elementIsWithinWindow(element: element) { 
        scrollDown(times: threshold)
    }

    while !elementIsWithinWindow(element: element) {
        guard iteration > 0 else { break }
        scrollUp()
        iteration -= 1
    }
}

This function attempts to scroll down until the element is visible. If the element is not found after reaching the threshold, it scrolls back up. This ensures that the element is searched for in both directions within the UIScrollView.

Conclusion

While Xcode UI Testing does not provide direct methods to scroll elements into view or check their visibility, the above custom helper methods enable you to work around these limitations. With these methods, you can ensure that your automated tests interact with elements that are visible on the screen, thereby increasing the reliability of your UI tests.

Like this article? there’s more where that came from!