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

问题描述 投票:31回答:9

因此,通过新的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];
}

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

ios objective-c xcode-ui-testing
9个回答
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)
    }
}

19
投票

以前的所有答案都不是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


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)
    }
}

here方法加入scrollUp


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()
           }
       }
    }

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()
    }
}

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 }
    }
}

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


1
投票

你可以这样做:

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

而不是使用scrollToElement来查找元素


1
投票

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

let app = XCUIApplication()
app.swipeUp()

要么

app.swipeDown()

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)
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.