简体   繁体   English

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

[英]Scroll until element is visible iOS UI Automation with xcode7

So with the new xcode update apple has revamped the way we do UI testing.因此,通过新的 xcode 更新,苹果改进了我们进行 UI 测试的方式。 In instruments we used java script function "isVisible" to determine if our targeted element is visible.在仪器中,我们使用 java 脚本函数“isVisible”来确定我们的目标元素是否可见。

I'm trying to replicate this in objective c but i can't seem to find the equivalent of this.我试图在目标 c 中复制它,但我似乎无法找到与此等效的方法。 I have a table view, a prototype cell with two labels on it.我有一个表格视图,一个带有两个标签的原型单元格。 This prototype cell is reused 50 times lets say.可以说,这个原型单元被重复使用了 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.请帮助因为这让我发疯。

You should extend the XCUIElement's method list.您应该扩展 XCUIElement 的方法列表。 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.第一个方法 ( 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 (eg 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)
    }
}

All the previous answers are not 100% fail proof.之前的所有答案都不是 100% 的失败证明。 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.我面临的问题是 swipeUp() 具有更大的偏移量,当我在视口中有元素时,我找不到停止滚动的方法。 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.但是我设法使用以下代码来控制滚动。

/**
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

Solutions using swipeUp() and swipeDown() are not ideal because they can potentially scroll past the target element due to the momentum of the swipe.使用swipeUp()swipeDown()解决方案并不理想,因为它们可能会由于滑动的动量而滚动超过目标元素。 After much searching and frustration I found a magical method on XCUICoordinate :经过多次搜索和挫折,我在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 isHittable in conjunction with exists .至于检查某些东西是否可见,您希望将isHittableexists结合使用。 see scrollDownToElement in the extension below请参阅下面扩展中的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 here with added scrollUp methods这里添加scrollUp方法的要点

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 的答案,在我的情况下,必须考虑scrollToElement的标签栏,否则如果视图位于标签栏下方,则可能会点击标签栏按钮:

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

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.此外,滚动是按项目大小的步骤执行的,因此在滑动的情况下,搜索元素不会穿过可见区域。

Unfortunately .exists doesn'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()
    }
}

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

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 查找元素

The problem here is, the swipe momentum scrolls the required cell out of the screen in most cases.这里的问题是,在大多数情况下,滑动动量会将所需的单元格滚动出屏幕。 So we will end up with the false swipes in our searching part.所以我们最终会在搜索部分出现错误的滑动​​。

Try to scroll to the cell which is placed after the last hittable cell in your current table view with all above answers.尝试使用上述所有答案滚动到当前表格视图中最后一个可点击单元格之后的单元格。 They will scroll the required elements and in most cases, we can't find the required cell.他们将滚动所需的元素,在大多数情况下,我们找不到所需的单元格。

Requirements:要求:

  • Exact scroll to the cell which is after the last visible cell精确滚动到最后一个可见单元格之后的单元格
  • Should throw error or stop scroll if we reach the table bottom如果我们到达表格底部,应该抛出错误或停止滚动

My solution:我的解决方案:

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

Now, this should work:现在,这应该有效:

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

Pros:优点:

  • It controls the momentum of the swipe它控制滑动的动量
  • Notifies if we have reached the last cell通知我们是否已到达最后一个单元格

Cons:缺点:

  • For the first time, the calculation part takes time to check the hittable cells第一次,计算部分需要时间检查hittable单元格

Note: This answer will only work with table views and swipes towards the bottom注意:此答案仅适用于表格视图和向底部滑动

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM