Android Espresso 等待文本出现

新手上路,请多包涵

我正在尝试自动化一个 Android 应用程序,它是一个使用 Espresso 的聊天机器人。我可以说我对 Android 应用程序自动化完全陌生。现在我在等待中挣扎。如果我使用 Thread.sleep ,它工作得很好。但是,我想等到特定文本出现在屏幕上。我怎样才能做到这一点?

 @Rule
public ActivityTestRule<LoginActivity> mActivityTestRule = new ActivityTestRule<>(LoginActivity.class);

@Test
public void loginActivityTest() {
ViewInteraction loginName = onView(allOf(withId(R.id.text_edit_field),
childAtPosition(childAtPosition(withId(R.id.email_field),0), 1)));
loginName.perform(scrollTo(), replaceText("test@test.test"), closeSoftKeyboard());

ViewInteraction password= onView(allOf(withId(R.id.text_edit_field),
childAtPosition(childAtPosition(withId(R.id.password_field),0), 1)));
password.perform(scrollTo(), replaceText("12345678"), closeSoftKeyboard());

ViewInteraction singInButton = onView(allOf(withId(R.id.sign_in), withText("Sign In"),childAtPosition(childAtPosition(withId(R.id.scrollView), 0),2)));
singInButton .perform(scrollTo(), click());

//Here I need to wait for the text "Hi ..."

一些解释:按下登录按钮后,聊天机器人说“嗨”并提供更多信息。我想等待最后一条消息出现在屏幕上。

原文由 Anna Puskarjova 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 430
1 个回答

我喜欢@jeprubio 上面的回答,但是我遇到了评论中提到的相同问题@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 次,然后它抛出异常并且测试失败。对什么是“机器人”感到困惑?查看 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 是谷歌鼓吹的在 Espresso 测试中处理异步事件的方法,但它通常要求您在应用程序代码中嵌入测试特定代码(即挂钩)以同步测试。这对我来说似乎很奇怪,并且在一个拥有成熟应用程序和多个开发人员每天提交代码的团队中工作,似乎仅仅为了测试而改造应用程序中各处的闲置资源将需要大量额外工作。就个人而言,我更喜欢将应用程序和测试代码尽可能分开。 /结束咆哮

原文由 manbradcalf 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏