iOS16模拟器中isHittable返回false但tap()可正常执行的UI测试问题
iOS 16/Xcode 14下XCUIElement.isHittable返回false但元素可交互的问题解决
我在UI测试中使用以下滚动函数,该函数在iOS 15模拟器上运行正常,但升级至Xcode14和iOS16模拟器后,toElementQuery.isHittable开始返回false——即便调用tap()可以正常与该元素交互;而在iOS16以下的模拟器中,isHittable返回true。
func scroll(_ elementQuery: XCUIElement, to toElementQuery: XCUIElement, direction: Direction, swipesCount: Int) { var swipe = 0 let swipeClosure: () -> Void = { switch direction { case .up: elementQuery.swipeUp() case .left: elementQuery.swipeLeft() case .right: elementQuery.swipeRight() case .down: elementQuery.swipeDown() } swipe += 1 } while !toElementQuery.exists && swipe <= swipesCount { swipeClosure() } if toElementQuery.exists { let elementHittable = toElementQuery.isHittable while !toElementQuery.isHittable && swipe <= swipesCount { swipeClosure() } } if !toElementQuery.exists { XCTFail(errorMessage) } else if !toElementQuery.isHittable { XCTFail(errorMessage) } }
问题原因
iOS 16的XCTest框架对isHittable的判定逻辑做了调整:
- 更严格的可见区域计算,比如元素边缘被安全区、导航栏/工具栏轻微遮挡时,即使视觉上可点击,框架也会判定为不可点击
- UI渲染延迟,滚动后元素还未完全处于可被正确判定的状态,导致
isHittable返回false
解决方案
1. 自定义可见性判定替代isHittable
扩展XCUIElement,用元素与窗口的交集来判断可见性,替代原生的isHittable:
extension XCUIElement { var isVisible: Bool { guard exists, !frame.isEmpty else { return false } guard let mainWindow = XCUIApplication().windows.firstMatch else { return false } return frame.intersects(mainWindow.frame) } }
修改原函数中的判定逻辑,将toElementQuery.isHittable替换为toElementQuery.isVisible,同时保留exists检查:
if toElementQuery.exists { while !toElementQuery.isVisible && swipe <= swipesCount { swipeClosure() } } // 最终断言也修改为 if !toElementQuery.exists { XCTFail(errorMessage) } else if !toElementQuery.isVisible { XCTFail(errorMessage) }
2. 增加滚动后的等待时间
iOS16的UI渲染可能存在延迟,滚动后添加短暂等待,让元素状态稳定后再检查:
let swipeClosure: () -> Void = { switch direction { case .up: elementQuery.swipeUp() case .left: elementQuery.swipeLeft() case .right: elementQuery.swipeRight() case .down: elementQuery.swipeDown() } swipe += 1 // 添加短暂等待 Thread.sleep(forTimeInterval: 0.3) }
也可以用XCTWaiter实现更优雅的等待:
let predicate = NSPredicate(format: "isHittable == true") let expectation = XCTNSPredicateExpectation(predicate: predicate, object: toElementQuery) XCTWaiter().wait(for: [expectation], timeout: 1)
3. 直接以交互结果作为判定标准
既然元素可以正常tap(),可以跳过isHittable检查,直接尝试交互,仅在交互失败时继续滚动:
if toElementQuery.exists { var canInteract = false while swipe <= swipesCount && !canInteract { do { try toElementQuery.tap() canInteract = true } catch { swipeClosure() } } }
内容的提问来源于stack exchange,提问作者gafurovK
相关产品推荐
相关产品推荐

