我喜欢@jeprubio上面的答案,但是我遇到了与@desgraci在评论中提到的同样的问题,他们的匹配器不断地寻找旧的、陈旧的rootview上的视图。当你试图在测试中的活动之间进行转换时,这种情况经常发生。
我对传统的"隐式等待"模式的实现在下面的两个 Kotlin 文件中。
EspressoExtensions.kt 包含一个 searchFor
函数,一旦在所提供的 rootview 中找到匹配项,就返回一个 ViewAction。
class EspressoExtensions {
companion object {
fun searchFor(matcher: Matcher<View>): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher<View> {
return isRoot()
}
override fun getDescription(): String {
return "searching for view $matcher in the root view"
}
override fun perform(uiController: UiController, view: View) {
var tries = 0
val childViews: Iterable<View> = TreeIterables.breadthFirstViewTraversal(view)
childViews.forEach {
tries++
if (matcher.matches(it)) {
return
}
}
throw NoMatchingViewException.Builder()
.withRootView(view)
.withViewMatcher(matcher)
.build()
}
}
}
}
}
BaseRobot.kt 调用 searchFor()
方法,并检查是否返回了匹配项。如果没有返回匹配项,则它会短暂休眠,然后获取新的根节点以进行匹配,直到尝试X次为止,然后它将抛出异常并导致测试失败。对“机器人”模式感到困惑吗?请查看 Jake Wharton 的这个精彩演讲,了解有关机器人模式的详细信息。它与页面对象模型模式非常相似。
open class BaseRobot {
fun doOnView(matcher: Matcher<View>, vararg actions: ViewAction) {
actions.forEach {
waitForView(matcher).perform(it)
}
}
fun assertOnView(matcher: Matcher<View>, vararg assertions: ViewAssertion) {
assertions.forEach {
waitForView(matcher).check(it)
}
}
fun waitForView(
viewMatcher: Matcher<View>,
waitMillis: Int = 5000,
waitMillisPerTry: Long = 100
): ViewInteraction {
val maxTries = waitMillis / waitMillisPerTry.toInt()
var tries = 0
for (i in 0..maxTries)
try {
tries++
onView(isRoot()).perform(searchFor(viewMatcher))
return onView(viewMatcher)
} catch (e: Exception) {
if (tries == maxTries) {
throw e
}
sleep(waitMillisPerTry)
}
throw Exception("Error finding a view matching $viewMatcher")
}
}
使用它
// Click on element withId
BaseRobot().doOnView(withId(R.id.viewIWantToFind), click())
// Assert element withId is displayed
BaseRobot().assertOnView(withId(R.id.viewIWantToFind), matches(isDisplayed()))
我知道IdlingResource是Google推荐在Espresso测试中处理异步事件的方法,但通常需要将测试特定代码(即钩子)嵌入应用程序代码中以同步测试。这对我来说似乎很奇怪,在一个拥有成熟应用和多个开发人员每天提交代码的团队工作,为了测试而到处添加空闲资源似乎会增加很多额外的工作量。就个人而言,我更喜欢尽可能地将应用程序和测试代码分开。
matcher.matches(it)
也会返回true,但是点击操作将失败,因为该元素不可见。解决方法很简单,只需使用matcher.matches(child).and(child.isVisible)
。 - odiggity