首页 > 解决方案 > Android Espresso UI 测试 - 使用 Idling 资源等待屏幕上的元素

问题描述

Android我开始使用Espresso框架为应用程序编写 UI 测试。我面临一个问题,测试通常会失败,因为检查屏幕上是否存在某些元素是在显示该元素之前发生的。在我的情况下 - 如果我运行我的测试 10 次 - 3-4 次它会失败,因为它不会在屏幕上找到一个元素

我知道Google建议使用这样的东西idling resources。但他们也建议以修改应用程序代码的方式使用它——这是一个糟糕的建议,我不想通过将我的应用程序代码与测试代码混合来创建意大利面条代码

所以我的问题 - 是否可以通过使用空闲资源“强制”espresso等待屏幕上的元素?

我也在考虑创建一些带有一些流利等待元素的辅助方法 - 然后像使用它一样使用它

waitForElement(onView(withId(R.id.elementID))

所以我非常感谢这方面的任何建议。谢谢

标签: androidandroid-espresso

解决方案


您可以尝试使用 和 创建自定义等待IdlingResource操作ViewAction。最简单的方法是创建一个IdlingResource回调,ViewTreeObserver.OnDrawListener通过与 a 匹配来侦听并确定应用何时空闲Matcher<View>

private class ViewPropertyChangeCallback(private val matcher: Matcher<View>, private val view: View) : IdlingResource, ViewTreeObserver.OnDrawListener {

    private lateinit var callback: IdlingResource.ResourceCallback
    private var matched = false

    override fun getName() = "View property change callback"

    override fun isIdleNow() = matched

    override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback) {
        this.callback = callback
    }

    override fun onDraw() {
        matched = matcher.matches(view)
        callback.onTransitionToIdle()
    }
}

然后创建一个自定义ViewAction等待匹配:

fun waitUntil(matcher: Matcher<View>): ViewAction = object : ViewAction {

    override fun getConstraints(): Matcher<View> {
        return any(View::class.java)
    }

    override fun getDescription(): String {
        return StringDescription().let {
            matcher.describeTo(it)
            "wait until: $it"
        }
    }

    override fun perform(uiController: UiController, view: View) {
        if (!matcher.matches(view)) {
            ViewPropertyChangeCallback(matcher, view).run {
                try {
                    IdlingRegistry.getInstance().register(this)
                    view.viewTreeObserver.addOnDrawListener(this)
                    uiController.loopMainThreadUntilIdle()
                } finally {
                    view.viewTreeObserver.removeOnDrawListener(this)
                    IdlingRegistry.getInstance().unregister(this)
                }
            }
        }
    }
}

并在根视图上执行此操作:

fun waitForElement(matcher: Matcher<View>) {
    onView(isRoot()).perform(waitUntil(hasDescendant(matcher))
}

...

waitForElement(allOf(withId(R.id.elementID), isDisplayed()))

或者,如果视图的属性可以异步更改,您可以执行以下操作:

onView(withId(R.id.elementID)).perform(waitUntil(withText("textChanged!")))

此外,此操作可能不适合活动转换。


推荐阅读