Espresso 会检测主线程何时处于空闲状态,以便可以在适当的时间运行测试命令,从而提高测试的可靠性。
先上示例代码:来自官网
@Test
fun greeterSaysHello() {
onView(withId(R.id.name_field)).perform(typeText("Steve"))
onView(withId(R.id.greet_button)).perform(click())
onView(withText("Hello Steve!")).check(matches(isDisplayed()))
}
很简单的代码。
场景:如果按钮 greet_button 的点击事件触发后,“Hello Steve!” 文本将在访问接口后才会显示,这个时间是不确定性的,所以给它添加 Thread.sleep() 也就不可行了。
空闲资源:
Espresso的空闲资源表示结果会影响界面测试中后续操作的异步操作。通过向 Espresso 注册空闲资源,您可以在测试应用时更可靠地验证这些异步操作。
⚠️ 这里我们虽然可以使用Espresso空闲资源来处理(使用 IdlingRegistry 类),但是 应用的生产代码中存在空闲资源逻辑,这是不推荐的。
处理方案:
根据如下匹配方法: ViewAssertions.matches()
/**
* Returns a generic {@link ViewAssertion} that asserts that a view exists in the view hierarchy
* and is matched by the given view matcher.
*/
public static ViewAssertion matches(final Matcher<? super View> viewMatcher) {
return new MatchesViewAssertion(checkNotNull(viewMatcher));
}
重写 MatchesViewAssertion() :
/**
* 如果需要等待视图匹配,在等待期间抛出此错误
* @property message
*/
class WaitingNotMatchViewException(override val message: String) : RuntimeException()
// 简便易使用
fun waitMatches(viewMatcher: Matcher<View>): ViewAssertion {
return WaitMatchesViewAssertion(checkNotNull(viewMatcher))
}
@VisibleForTesting
class WaitMatchesViewAssertion(private val viewMatcher: Matcher<View>) : ViewAssertion {
private val TAG = WaitMatchesViewAssertion::class.java.simpleName
override fun check(view: View?, noViewException: NoMatchingViewException?) {
val description = StringDescription()
description.appendText("'")
viewMatcher.describeTo(description)
if (noViewException != null) {
description.appendText(
String.format(
Locale.ROOT,
"' check could not be performed because view '%s' was not found.\n",
noViewException.viewMatcherDescription
)
)
Log.e(TAG, description.toString())
throw noViewException
} else {
// 添加 try-catch
try {
description.appendText("' doesn't match the selected view.")
ViewMatchers.assertThat(description.toString(), view, viewMatcher)
}catch (e: AssertionFailedError){
// 抛出异常 WaitingNotMatchViewException
throw WaitingNotMatchViewException("doesn't match the selected view.")
}
}
}
override fun toString(): String {
return String.format(Locale.ROOT, "MatchesViewAssertion{viewMatcher=%s}", viewMatcher)
}
}
封装:
/**
* 页面上只有一个视图与表达式匹配的情况。
* 等待匹配元素直到超时。如果找不到匹配的元素,这不会抛出异常,它只是等待。
*
* @param elementToCheck 要检查的元素
* @param viewMatcher 匹配项
* @param secondsToWait 等待的最大秒数
*/
fun waitForElementWithMatcher(
elementToCheck: ViewInteraction, // 要检查的元素
viewMatcher: Matcher<View>, // 匹配项
secondsToWait: Int = 30 // 超时时间
) {
var i = 0
var elementMatched = false
while (i <= secondsToWait) {
if (IntegrationUtil.waitElementToViewMatcher(elementToCheck, viewMatcher)) {
elementMatched = true
break
}
Thread.sleep(1000) // 这里的时间可以自己设定
i++
}
if (!elementMatched) {
throw Exception("Expected to find the element on Screen .Waited for $i seconds")
}
}
/**
* 检查元素匹配项。
* 在您知道屏幕上只有一个匹配元素的情况下使用它
*
* @param elementToCheck 要检查的元素
* @param viewMatcher 元素匹配项
* @return returns 如果元素匹配,则返回 true
*/
fun waitElementToViewMatcher(
elementToCheck: ViewInteraction,
viewMatcher: Matcher<View>
): Boolean {
try {
val elementList = mutableListOf(elementToCheck.check(waitMatches(viewMatcher)))
if (elementList.count() == 1)
return true
} catch (e: NoMatchingViewException) {
return false
} catch (e: WaitingNotMatchViewException) {
return false
} catch (e: Exception) {
throw Exception(e.message)
}
return false
}
使用:
@Test
fun greeterSaysHello() {
onView(withId(R.id.name_field)).perform(typeText("Steve"))
onView(withId(R.id.greet_button)).perform(click())
// onView(withText("Hello Steve!")).check(matches(isDisplayed()))
waitForElementWithMatcher(onView(withText("Hello Steve!")), isDisplayed(), 30)
}
最后,欢迎讨论。