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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/32646539/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 07:41:36  来源:igfitidea点击:

Scroll until element is visible iOS UI Automation with xcode7

iosobjective-cxcode-ui-testing

提问by CodeGeass

So with the new xcode update apple has revamped the way we do UI testing. In instruments we used java script function "isVisible" to determine if our targeted element is visible.

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

I'm trying to replicate this in objective c but i can't seem to find the equivalent of this. I have a table view, a prototype cell with two labels on it. This prototype cell is reused 50 times lets say.

我试图在目标 c 中复制它,但我似乎无法找到与此等效的方法。我有一个表格视图,一个带有两个标签的原型单元格。可以说,这个原型单元被重复使用了 50 次。

I'm trying to scroll until the last cell is visible, i did this by doing this:

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

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

But this won't swipe since the element exists when the view is loaded. Please help cause this is driving me crazy.

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

回答by Kádi

You should extend the XCUIElement's method list. The first method (scrollToElement:) will be called on the tableView, the second extension method helps you decide if the element is on the main window.

您应该扩展 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)
    }

}

The scrolling code should look like this (e.g. scrolling to last cell):

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

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

Swift 3:

斯威夫特 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)
    }
}

回答by ravisekahrp

All the previous answers are not 100% fail proof. The problem I was facing is that swipeUp() has a larger offset and I couldn't find a way to stop the scrolling when I have the element in view port. Sometimes the element gets scrolled away because of the excessive scroll and as a result test case fails. However I managed to control the scroll using the following piece of code.

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

Note : Please use app.tables if your view is tableview based

注意:如果您的视图基于 tableview,请使用 app.tables

回答by RyanM

Solutions using swipeUp()and swipeDown()are not ideal because they can potentially scroll past the target element due to the momentum of the swipe. After much searching and frustration I found a magical method on XCUICoordinate:

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

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

So we can do something like:

所以我们可以这样做:

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)

As far as checking whether something is visible goes, you want to use isHittablein conjunction with exists. see scrollDownToElementin the extension below

至于检查某些东西是否可见,您希望isHittableexists. 请参阅scrollDownToElement下面的扩展名

Here's a handy extension that will scroll until an element is on screen and then scroll that element to the top of the screen :)

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

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

Gist over herewith added scrollUpmethods

这里添加scrollUp方法的要点

回答by Justin Zealand

Expanding on @Kade's answer, in my case, had to account for tabbar in scrollToElement, else might get a tabbar button tapped if the view was under the tabbar:

扩展@Kade 的答案,在我的情况下,必须考虑 tabbar in scrollToElement,否则如果视图位于 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()
        }
    }
}

回答by Marek R

Here is my version which I think is bullet proof (swift 4.0):

这是我认为防弹的版本(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 }
    }
}

It take into account that item may not be found (in this case it should not hang). Also scroll is performed in steps of size of the item so search element will not pass through visible area what is possible in case of swipe.

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

回答by Tucker Sherman

Unfortunately .existsdoesn't confirm that an element is currently visible - something like this still isn't perfect but it will provide more reliable validation working with table or collection view cells:

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

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

then you can write a simple loop like:

那么你可以写一个简单的循环,如:

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

回答by Andrey

you can do something like this:

你可以做这样的事情:

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

and than use scrollToElement to find element

然后使用 scrollToElement 查找元素

回答by Ashim Dahal

in swift 4.2, if your element exist at bottom frame of table view or top frame of table view you can use this command to scroll up and scroll down to find element

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

let app = XCUIApplication()
app.swipeUp()

or

或者

app.swipeDown()

回答by Sunkas

Update to @ravisekahrp's answer for newer Swift:

更新@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)
        }
    }
}