我喜欢上面@jeprubio 的回答,但是我遇到了与cmets 中提到的@desgraci 相同的问题,他们的匹配器一直在寻找旧的、陈旧的根视图的视图。当您尝试在测试中的活动之间进行转换时,这种情况经常发生。
我对传统“隐式等待”模式的实现存在于下面的两个 Kotlin 文件中。
EspressoExtensions.kt 包含一个函数searchFor,一旦在提供的根视图中找到匹配项,它就会返回一个 ViewAction。
class EspressoExtensions {
companion object {
/**
* Perform action of waiting for a certain view within a single root view
* @param matcher Generic Matcher used to find our view
*/
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)
// Look for the match in the tree of childviews
childViews.forEach {
tries++
if (matcher.matches(it)) {
// found the view
return
}
}
throw NoMatchingViewException.Builder()
.withRootView(view)
.withViewMatcher(matcher)
.build()
}
}
}
}
}
BaseRobot.kt 调用searchFor() 方法,检查是否返回了匹配器。如果没有返回匹配,它会休眠一点点,然后获取一个新的根进行匹配,直到它尝试了 X 次,然后它抛出一个异常并且测试失败。对什么是“机器人”感到困惑?查看this fantastic talk by 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)
}
}
/**
* Perform action of implicitly waiting for a certain view.
* This differs from EspressoExtensions.searchFor in that,
* upon failure to locate an element, it will fetch a new root view
* in which to traverse searching for our @param match
*
* @param viewMatcher ViewMatcher used to find our view
*/
fun waitForView(
viewMatcher: Matcher<View>,
waitMillis: Int = 5000,
waitMillisPerTry: Long = 100
): ViewInteraction {
// Derive the max tries
val maxTries = waitMillis / waitMillisPerTry.toInt()
var tries = 0
for (i in 0..maxTries)
try {
// Track the amount of times we've tried
tries++
// Search the root for the view
onView(isRoot()).perform(searchFor(viewMatcher))
// If we're here, we found our view. Now return it
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 测试中处理异步事件的方法,但它通常要求您在应用代码中嵌入测试特定代码(即挂钩)以同步测试。这对我来说似乎很奇怪,并且在一个拥有成熟应用程序和多个开发人员每天提交代码的团队中工作,似乎为了测试而在应用程序中的任何地方改造空闲资源将是很多额外的工作。就个人而言,我更喜欢将应用程序和测试代码尽可能分开。
/结束咆哮