UITesting Xcode 7:如何判断XCUIElement是否可见?

14

我正在使用 Xcode 7 的 UI 测试自动化一个应用程序。我有一个滚动视图,其中包含了 XCUIElements (包括按钮等)。这些元素有时是可见的,有时它们被隐藏得太深而无法在滚动视图上下滑动(这取决于我在滚动视图中的位置)。

是否有一种方法可以将这些元素滚动到视图中或者判断它们是否可见?

谢谢。

5个回答

19

不幸的是,苹果没有提供任何scrollTo方法或者XCUIElement上的.visible参数。 尽管如此,您可以添加一些辅助方法来实现部分功能。以下是我在Swift中的实现方式。

首先,检查元素是否可见:

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

不幸的是,.exists会返回true,即使元素已经加载但不在屏幕上。此外,我们还需要检查目标元素的框架大于0乘以0(有时也为真)-然后我们可以检查该框架是否在主窗口内。

然后我们需要一个方法来向上或向下滚动可控数量:

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

改变topScreenPoint和bottomScreenPoint的CGVector值将改变滚动操作的比例 - 注意,如果靠近屏幕边缘太近,您将拉出一个OS菜单。

使用这两种方法,您可以编写一个循环,向一个方向滚动到给定的阈值,直到元素变得可见,然后如果它没有找到目标,就向另一个方向滚动:

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

这种方法不是非常高效,但至少可以让您找到屏幕外的元素。当然,如果您知道您的目标元素在给定测试中始终会在起始点上方或下方,那么您可以只编写一个scrollDownUntilscrollUpUntill方法,而无需使用此处的阈值逻辑。 希望这可以帮助您!

Swift 5 更新

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

func scrollDown(times: Int = 1) {
    let mainWindow = app.windows.firstMatch
    let topScreenPoint = mainWindow.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.05))
    let bottomScreenPoint = mainWindow.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.90))
    for _ in 0..<times {
        bottomScreenPoint.press(forDuration: 0, thenDragTo: topScreenPoint)
    }
}

func scrollUp(times: Int = 1) {
    let mainWindow = app.windows.firstMatch
    let topScreenPoint = mainWindow.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.05))
    let bottomScreenPoint = mainWindow.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.90))
    for _ in 0..<times {
        topScreenPoint.press(forDuration: 0, thenDragTo: bottomScreenPoint)
    }
}  
    
func scrollUntilElementAppears(element: XCUIElement, threshold: Int = 10) {
    var iteration = 0

    while !elementIsWithinWindow(element: element) {
        guard iteration < threshold else { break }
        scrollDown()
        iteration += 1
    }

    if !elementIsWithinWindow(element: element) { 
        scrollDown(times: threshold)
    }

    while !elementIsWithinWindow(element: element) {
        guard iteration > 0 else { break }
        scrollUp()
        iteration -= 1
    }
}

这个方法是可行的,但我必须使用不同的方法获取主窗口: func mainWindow() -> XCUIElement { return XCUIApplication().windows.elementBoundByIndex(0) }另外,你所命名的“scrollUp”更传统地应该被命名为“scrollDown”。 - isoiphone
好的观点isoiphone!我忘记在这个代码示例中添加XCUIApplication.mainWindow()扩展,我已经编辑了答案以使其更完整,并为清晰起见交换了scrollUp和scrollDown。 - Tucker Sherman

6

我需要在我的UI测试代码中实际上向上或向下滑动来解决这个问题。你试过这样做吗?

XCUIApplication().swipeUp()

或者您也可以执行WhateverUIElement.swipUp(),它将相对于该元素向上/向下滚动。

希望他们能修复自动滚动或自动查找功能,这样我们就不必手动操作了。


嗨,Narine。是的,我使用了swipeUp()和down(),但无法知道元素何时处于视图中.. :-( 正如你所说,希望未来能找到解决这个问题的方法。 - Charlie S

4
你应该检查isHittable属性。如果视图没有隐藏,相应的XCUIElement是可点击的。但是有一个警告。"View 1"可以被"View 2"覆盖,但对应于"View 1"的元素可能是可点击的。

1
苹果文档指出,当一个元素被另一个元素覆盖时,“isHittable”将为“false”。参考:https://developer.apple.com/documentation/xctest/xcuielement/1500561-ishittable - Ting Yi Shih

2

由于您在tableview(表尾视图)的底部有一些XCUIElements,在UI测试中将tableview滚动到底部的方法,假设您的tableview有很多数据,是通过tap()实现的。

.swipeUp()也可以完成任务,但问题是当您的测试数据非常大时,它需要花费很长时间才能向上滑动,而.tap()直接跳转到tableView的底部。

更具体地说:

XCUIElementsInTheBottomOrTableFooterView.tap()
XCTAssert(XCUIElementsInTheBottomOrTableFooterView.isHittable, "message") 

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接