I am automating an app using UI Testing in Xcode 7. I have a scrollview with XCUIElements (including buttons, etc) all the way down it. Sometimes the XCUIElements are visible, sometimes they hidden too far up or down the scrollview (depending on where I am on the scrollview).
Is there a way to scroll items into view or maybe tell if they are visible or not?
Thanks
Unfortunately Apple hasn't provided any scrollTo
method or a .visible
parameter on XCUIElement
. That said, you can add a couple helper methods to achieve some of this functionality. Here is how I've done it in Swift.
First for checking if an element is visible:
func elementIsWithinWindow(element: XCUIElement) -> Bool {
guard element.exists && !CGRectIsEmpty(element.frame) && element.hittable else { return false }
return CGRectContainsRect(XCUIApplication().windows.elementBoundByIndex(0).frame, element.frame)
}
Unfortunately .exists
returns true if an element has been loaded but is not on screen. Additionally we have to check that the target element has a frame larger than 0 by 0 (also sometimes true) - then we can check if this frame is within the main window.
Then we need a method for scrolling a controllable amount up or down:
func scrollDown(times: Int = 1) {
let topScreenPoint = app.mainWindow().coordinateWithNormalizedOffset(CGVector(dx: 0.5, dy: 0.05))
let bottomScreenPoint = app.mainWindow().coordinateWithNormalizedOffset(CGVector(dx: 0.5, dy: 0.90))
for _ in 0..<times {
bottomScreenPoint.pressForDuration(0, thenDragToCoordinate: topScreenPoint)
}
}
func scrollUp(times: Int = 1) {
let topScreenPoint = app.mainWindow().coordinateWithNormalizedOffset(CGVector(dx: 0.5, dy: 0.05))
let bottomScreenPoint = app.mainWindow().coordinateWithNormalizedOffset(CGVector(dx: 0.5, dy: 0.90))
for _ in 0..<times {
topScreenPoint.pressForDuration(0, thenDragToCoordinate: bottomScreenPoint)
}
}
Changing the CGVector values for topScreenPoint and bottomScreenPoint will change the scale of the scroll action - be aware if you get too close to the edges of the screen you will pull out one of the OS menus.
With these two methods in place you can write a loop that scrolls to a given threshold one way until an element becomes visible, then if it doesn't find its target it scrolls the other way:
func scrollUntilElementAppears(element: XCUIElement, threshold: Int = 10) {
var iteration = 0
while !elementIsWithinWindow(element) {
guard iteration < threshold else { break }
scrollDown()
iteration++
}
if !elementIsWithinWindow(element) { scrollDown(threshold) }
while !elementIsWithinWindow(element) {
guard iteration > 0 else { break }
scrollUp()
iteration--
}
}
This last method isn't super efficient, but it should at least enable you to find elements off screen. Of course if you know your target element is always going to be above or below your starting point in a given test you could just write a scrollDownUntil
or a scrollUpUntill
method without the threshold logic here.
Hope this helps!
What i had to do to address this problem is to actually swipe up or down in my UI testing code. Have you tried this?
XCUIApplication().swipeUp()
Or you can also do WhateverUIElement.swipUp() and it will scroll up/down with respect to that element.
Hopefully they will fix the auto scroll or auto find feature so we don't have to do this manually.
You should check isHittable
property.
If view is not hidden, the corresponding XCUIElement is hittable. But there is a caveat. "View 1" can be overlapped by "View 2", but the element corresponding to "View 1" can be hittable.
Since you have some XCUIElements in the bottom of the tableview (table footer view), the way of scrolling the tableview all the way to the bottom in the UI test, supposing your tableview has a lot data, is by tap()
.
.swipeUp()
also does the job but the problem is when your test data is huge, it takes forever to swipe, as oppose to .tap()
which directly jumps to the bottom of the tableView.
More specially:
XCUIElementsInTheBottomOrTableFooterView.tap()
XCTAssert(XCUIElementsInTheBottomOrTableFooterView.isHittable, "message")
Looks like this is a known bug :-(
https://forums.developer.apple.com/thread/9934