简体   繁体   English

Android Espresso UI 测试 - 使用空闲资源等待屏幕上的元素

[英]Android Espresso UI test - use Idling resource to wait for an element on the screen

I started writing UI tests for Android application using Espresso framework.我开始使用Espresso框架为Android应用程序编写 UI 测试。 I'm facing an issue that, often, test fails because check that some element is present on the screen happens before that element is displayed.我面临的问题通常是测试失败,因为在显示该元素之前检查屏幕上是否存在某个元素。 In my case - if I run my test 10 times - 3-4 times it will fail because it won't find an element on the screen在我的情况下 - 如果我运行我的测试 10 次 - 3-4 次它会失败,因为它不会在屏幕上找到一个元素

I know that Google suggest to use such thing like idling resources .我知道Google建议使用idling resources类的东西。 But they also suggest to use it in a way of modifying your application code - which is kind of a shitty advice, and I don't want to create spaghetti code by mixing my application code with testing code但他们也建议以修改应用程序代码的方式使用它 - 这是一种糟糕的建议,我不想通过将我的应用程序代码与测试代码混合来创建意大利面条式代码

So my question - Is it possible to 'force' espresso to wait for an element on the screen by using Idling resources?所以我的问题 - 是否可以通过使用空闲资源“强制” espresso等待屏幕上的元素?

I was also thinking about creating some helper method with some fluent wait for an element - and then use it like我也在考虑创建一些辅助方法,并使用一些流畅的等待元素 - 然后像这样使用它

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

so I would really appreciate any advice in this direction.所以我真的很感激这方面的任何建议。 Thanks谢谢

You can try to create a custom wait action with both IdlingResource and ViewAction .您可以尝试使用IdlingResourceViewAction创建自定义等待操作。 The simplest way is to create an IdlingResource callback that listens to ViewTreeObserver.OnDrawListener and determines when the app is idling by matching with a Matcher<View> :最简单的方法是创建一个IdlingResource回调,该回调侦听ViewTreeObserver.OnDrawListener并通过匹配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()
    }
}

Then create a custom ViewAction to wait for a match:然后创建一个自定义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)
                }
            }
        }
    }
}

And perform this action on a root view:并在根视图上执行此操作:

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

...

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

Or if the view's property can change asynchronously, you can do:或者,如果视图的属性可以异步更改,您可以执行以下操作:

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

Also, this action may not be suitable for activity transitions.此外,此操作可能不适合活动转换。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM