滚动直到元素可见iOS UI自动化与xcode7

时间:2015-09-18 07:44:08

标签: ios objective-c xcode-ui-testing

因此,使用新的xcode更新,apple改进了我们进行UI测试的方式。在乐器中,我们使用java脚本函数“isVisible”来确定我们的目标元素是否可见。

我试图在目标c中复制这个,但我似乎无法找到相当于此的东西。我有一个表视图,一个原型单元格,上面有两个标签。这个原型单元可以重复使用50次。

我正在尝试滚动,直到最后一个单元格可见,我这样做:

if (![[[[[[XCUIApplication alloc] init].tables childrenMatchingType:XCUIElementTypeCell] matchingIdentifier:@"cell"] elementBoundByIndex:49].staticTexts[@"text"] exists]) {
        [[[[[[XCUIApplication alloc] init].tables childrenMatchingType:XCUIElementTypeCell] matchingIdentifier:@"cell"] elementBoundByIndex:0].staticTexts[@"text"] swipeUp];
}

但是这不会滑动,因为加载视图时元素存在。请帮助因为这让我发疯。

10 个答案:

答案 0 :(得分:46)

您应该扩展XCUIElement的方法列表。第一个方法(scrollToElement:)将在tableView上调用,第二个扩展方法可帮助您确定元素是否在主窗口上。

extension XCUIElement {

    func scrollToElement(element: XCUIElement) {
        while !element.visible() {
            swipeUp()
        }
    }

    func visible() -> Bool {
        guard self.exists && !CGRectIsEmpty(self.frame) else { return false }
        return CGRectContainsRect(XCUIApplication().windows.elementBoundByIndex(0).frame, self.frame)
    }

}

滚动代码应该如下所示(例如滚动到最后一个单元格):

func testScrollTable() {
    let app = XCUIApplication()
    let table = app.tables.elementBoundByIndex(0)
    let lastCell = table.cells.elementBoundByIndex(table.cells.count-1)
    table.scrollToElement(lastCell)
}

斯威夫特3:

extension XCUIElement {
    func scrollToElement(element: XCUIElement) {
        while !element.visible() {
            swipeUp()
        }
    }

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

答案 1 :(得分:18)

之前的所有答案都不是100%失败证明。我遇到的问题是swipeUp()有一个更大的偏移量,当我在视图端口中有元素时,我找不到停止滚动的方法。有时元素会因滚动过多而滚动,因此测试用例失败。 但是我设法使用以下代码控制滚动。

/**
Scrolls to a particular element until it is rendered in the visible rect
- Parameter elememt: the element we want to scroll to
*/
func scrollToElement(element: XCUIElement)
{
    while element.visible() == false
    {
        let app = XCUIApplication()
        let startCoord = app.collectionViews.element.coordinateWithNormalizedOffset(CGVector(dx: 0.5, dy: 0.5))
        let endCoord = startCoord.coordinateWithOffset(CGVector(dx: 0.0, dy: -262));
        startCoord.pressForDuration(0.01, thenDragToCoordinate: endCoord)
    }
}

func visible() -> Bool
{
    guard self.exists && self.hittable && !CGRectIsEmpty(self.frame) else
    {
        return false
    }

    return CGRectContainsRect(XCUIApplication().windows.elementBoundByIndex(0).frame, self.frame)
}

注意:如果您的视图是基于tableview的

,请使用app.tables

答案 2 :(得分:7)

使用swipeUp()swipeDown()的解决方案并不理想,因为它们可能会因滑动动量而滚过目标元素。经过多次搜索和挫折,我在XCUICoordinate上找到了一个神奇的方法:

func press(forDuration duration: TimeInterval, thenDragTo otherCoordinate: XCUICoordinate)

所以我们可以这样做:

let topCoordinate = XCUIApplication().statusBars.firstMatch.coordinate(withNormalizedOffset: .zero)
let myElement = XCUIApplication().staticTexts["My Element"].coordinate(withNormalizedOffset: .zero)
// drag from element to top of screen (status bar)
myElement.press(forDuration: 0.1, thenDragTo: topCoordinate)

至于检查某些内容是否可见,您希望将isHittableexists结合使用。请参阅以下扩展程序中的scrollDownToElement

这是一个方便的扩展,它将滚动直到元素在屏幕上,然后将该元素滚动到屏幕顶部:)

extension XCUIApplication {
    private struct Constants {
        // Half way accross the screen and 10% from top
        static let topOffset = CGVector(dx: 0.5, dy: 0.1)

        // Half way accross the screen and 90% from top
        static let bottomOffset = CGVector(dx: 0.5, dy: 0.9)
    }

    var screenTopCoordinate: XCUICoordinate {
        return windows.firstMatch.coordinate(withNormalizedOffset: Constants.topOffset)
    }

    var screenBottomCoordinate: XCUICoordinate {
        return windows.firstMatch.coordinate(withNormalizedOffset: Constants.bottomOffset)
    }

    func scrollDownToElement(element: XCUIElement, maxScrolls: Int = 5) {
        for _ in 0..<maxScrolls {
            if element.exists && element.isHittable { element.scrollToTop(); break }
            scrollDown()
        }
    }

    func scrollDown() {
        screenBottomCoordinate.press(forDuration: 0.1, thenDragTo: screenTopCoordinate)
    }
}

extension XCUIElement {
    func scrollToTop() {
        let topCoordinate = XCUIApplication().screenTopCoordinate
        let elementCoordinate = coordinate(withNormalizedOffset: .zero)

        // Adjust coordinate so that the drag is straight up, otherwise
        // an embedded horizontal scrolling element will get scrolled instead
        let delta = topCoordinate.screenPoint.x - elementCoordinate.screenPoint.x
        let deltaVector = CGVector(dx: delta, dy: 0.0)

        elementCoordinate.withOffset(deltaVector).press(forDuration: 0.1, thenDragTo: topCoordinate)
    }
}

使用添加的scrollUp方法

来点燃here

答案 3 :(得分:4)

扩展@Kade's answer,在我的情况下,必须考虑scrollToElement中的tabbar,否则如果视图位于tabbar下,则可能会点击一个tabbar按钮:

    func scrollToElement(element: XCUIElement) {
        while !element.visible() {
            swipeUp()
        }
        // Account for tabBar
        let tabBar = XCUIApplication().tabBars.element(boundBy: 0)
        if (tabBar.visible()) {
            while element.frame.intersects(tabBar.frame) {
                swipeUp()
           }
       }
    }

答案 4 :(得分:3)

不幸的是.exists没有确认某个元素当前是否可见 - 这样的事情仍然不完美,但它将提供更可靠的验证,使用表格或集合视图单元格:

extension XCUIElement {
    var displayed: Bool {
        guard self.exists && !CGRectIsEmpty(frame) else { return false }
        return CGRectContainsRect(XCUIApplication().windows.elementBoundByIndex(0).frame, frame)
    }
}

然后你可以编写一个简单的循环:

func scrollDownUntilVisible(element: XCUIElement) {
    while !element.displayed {
        swipeDown()
    }
}

答案 5 :(得分:3)

这是我的版本,我认为是防弹(swift 4.0):

import XCTest

enum TestSwipeDirections {
    case up
    case down
    case left
    case right
}

fileprivate let min = 0.05
fileprivate let mid = 0.5
fileprivate let max = 0.95

fileprivate let leftPoint = CGVector(dx: min, dy: mid)
fileprivate let rightPoint = CGVector(dx: max, dy: mid)
fileprivate let topPoint = CGVector(dx: mid, dy: min)
fileprivate let bottomPoint = CGVector(dx: mid, dy: max)

extension TestSwipeDirections {
    var vector: (begin: CGVector, end: CGVector) {
        switch self {
        case .up:
            return (begin: bottomPoint,
                    end:   topPoint)
        case .down:
            return (begin: topPoint,
                    end:   bottomPoint)
        case .left:
            return (begin: rightPoint,
                    end:   leftPoint)
        case .right:
            return (begin: leftPoint,
                    end:   rightPoint)
        }
    }
}

extension XCUIElement {
    @discardableResult func swipeOnIt(_ direction: TestSwipeDirections,
                                      swipeLimit: Int = 6,
                                      swipeDuration: TimeInterval = 1.0,
                                      until: () -> Bool) -> Bool {
        XCTAssert(exists)

        let begining = coordinate(withNormalizedOffset: direction.vector.begin)
        let ending = coordinate(withNormalizedOffset: direction.vector.end)

        var swipesRemaining = swipeLimit
        while !until() && swipesRemaining > 0 {
            begining.press(forDuration: swipeDuration, thenDragTo: ending)
            swipesRemaining = swipesRemaining - 1
        }
        return !until()
    }

    @discardableResult func swipeOnIt(_ direction: TestSwipeDirections,
                                      swipeLimit: Int = 6,
                                      swipeDuration: TimeInterval = 1.0,
                                      untilHittable element: XCUIElement) -> Bool {
        return swipeOnIt(direction, swipeLimit: swipeLimit, swipeDuration: swipeDuration) { element.isHittable }
    }

    @discardableResult func swipeOnIt(_ direction: TestSwipeDirections,
                                      swipeLimit: Int = 6,
                                      swipeDuration: TimeInterval = 1.0,
                                      untilExists element: XCUIElement) -> Bool {
        return swipeOnIt(direction, swipeLimit: swipeLimit, swipeDuration: swipeDuration) { element.exists }
    }
}

考虑到可能找不到该项目(在这种情况下,它不应该挂起)。 此外,滚动是按照项目大小的步骤执行的,因此搜索元素将不会通过可见区域,以便在滑动的情况下。

答案 6 :(得分:1)

你可以这样做:

extension XCUIElement {
    internal func scrollToElement(element: XCUIElement) {
        while !element.exists {
            swipeDown()
        }
    }
}

然后使用scrollToElement查找元素

答案 7 :(得分:0)

迅速4.2, 如果您的元素存在于表格视图的底部框架或表格视图的顶部框架,则可以使用此命令上下滚动以查找元素

let app = XCUIApplication()
app.swipeUp()

app.swipeDown()

答案 8 :(得分:0)

更新至@ravisekahrp的答案以获取更新的Swift:

extension XCUIElement {
    func isVisible() -> Bool {
        if !self.exists || !self.isHittable || self.frame.isEmpty {
            return false
        }

        return XCUIApplication().windows.element(boundBy: 0).frame.contains(self.frame)
    }
}

extension XCTestCase {
    func scrollToElement(_ element: XCUIElement) {
        while !element.isVisible() {
            let app = XCUIApplication()
            let startCoord = app.tables.element.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
            let endCoord = startCoord.withOffset(CGVector(dx: 0.0, dy: -262))
            startCoord.press(forDuration: 0.01, thenDragTo: endCoord)
        }
    }
}

答案 9 :(得分:0)

这里的问题是,在大多数情况下,滑动动量会将所需的单元格滚动出屏幕。所以我们最终会在搜索部分得到错误的滑动​​。

尝试滚动到当前表格视图中最后一个可点击单元格之后的单元格,并提供上述所有答案。他们将滚动所需的元素,在大多数情况下,我们找不到所需的单元格。

要求:

  • 精确滚动到最后一个可见单元格之后的单元格
  • 到达表格底部时应该抛出错误或停止滚动

我的解决方案:

public let app = XCUIApplication()

extension XCUIElement {
    
    func scrollTo(_ element: XCUIElement) {
        if self.elementType == .table {
            if element.isHittable { return }
            let lastCell = self.cells.element(boundBy: self.cells.count-1)
            let yOffset = calculatedYOffset()
            
            while !element.isHittable
            {
                if lastCell.isHittable {
                    //Error - Table bottom reached
                }
                
                let start = self.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
                let end = start.withOffset(CGVector(dx: 0.0, dy: -yOffset));
                start.press(forDuration: 0.01, thenDragTo: end)
            }
        } else {
            // Error - Only applicable for table views
        }
    }
    
    func calculatedYOffset() -> Double {
        var indexOfLastVisibleCell = 0
        for i in 0...self.cells.count-1 {
            if self.cells.element(boundBy: i).visible() {
                indexOfLastVisibleCell = i
            } else {
                if indexOfLastVisibleCell != 0 { break }
            }
        }
        
        let lastVisibleCellEndYPosition = Double(self.cells.element(boundBy: indexOfLastVisibleCell).frame.maxY)
        let adjustmentYValue = Double(self.cells.firstMatch.frame.minY)
        let screenScale = Double(UIScreen.main.scale)
        
        return (lastVisibleCellEndYPosition-adjustmentYValue)/screenScale
    }
    
    func visible() -> Bool {
        guard self.exists && self.isHittable && !self.frame.isEmpty else { return false }
        return app.windows.element(boundBy: 0).frame.contains(self.frame)
    }
}

现在,这应该可以了:

App.tables["Bar"].scrollTo(App.cells.staticTexts["Foo"])

优点:

  • 它控制滑动的动量
  • 通知我们是否已到达最后一个单元格

缺点:

  • 第一次,计算部分需要时间来检查可命中的单元格

注意:此答案仅适用于表格视图和向底部滑动