【Android Test】特定のViewが表示されるまで待機＋RootViewWithoutFocusExceptionの対策

# Viewが表示されるまで待機する
ユーザの操作に応じてメッセージを表示するDialogを表示したり、別のActivityへ移動したりと新しいViewを表示するケースは多々あると思います。この挙動をUIテストで確認しようとすると、特定のViewが表示されるまで待機するようなActionが必要となります。

実装例
1. 引数`Matcher<View>`で待機するViewを指定する
2. `onView()`
3. 取得した`ViewInteraction`に対応するViewが存在するか確認する  
   関数`isExists()`は [StackOverflow - Espresso does not wait until dialog is shown and fail](https://stackoverflow.com/questions/42766596/espresso-does-not-wait-until-dialog-is-shown-and-fail)を参考に実装
4. Viewが存在しない場合は一定時間待機して再度2.から繰り返す
```kotlin
fun waitForView(matcher: Matcher<View>, timeout: Long = 3000L): ViewInteraction {
    val start = System.currentTimeMillis()
    do {
        val view = onView(matcher)
        if (view.isExists()) return view
        Thread.sleep(200L)
    } while (System.currentTimeMillis() < start + timeout)
    throw RuntimeException("timeout, no view found: $matcher")
}
```

多くの場合はこれで動きます

### IdlingResourceの使用の検討
`Thread.sleep`を直接使うのはあまりお行儀が良くないので`IdlingResource`を使用する方法もあるようです  
[Medium - Wait for it… IdlingResource and ConditionWatcher](https://medium.com/azimolabs/wait-for-it-idlingresource-and-conditionwatcher-602055f32356)  
ただし、状態の変換を検知する時間幅が5秒で固定となっており、テスト項目が増えると待機の時間が長くなりすぎる問題があります。

# RootViewWithoutFocusException
待機したいViewが新たに表示するDialogや別Activity上にある場合、時々問題が発生します
```
androidx.test.espresso.base.RootViewPicker$RootViewWithoutFocusException: Waited for the root of the view hierarchy to have window focus and not request layout for 10 seconds. If you specified a non default root matcher, it may be picking a root that never takes focus. Root:
```

待機したいViewが乗っているwindowにfocusが当たる前に`waitForView()`を実行するとダメっぽいです。

そもそも`ViewInteraction`はViewインスタンスの実態に紐付いている訳ではなく、まだ存在しないViewを指定した`Matcher<View>`に対して`onView`関数を呼び出しても例外は発生しません。しかし、存在していないViewに対する`ViewInteraction#perform()`の挙動が想定できないケースがある様子。

# RootViewから検索する
そこで`onView(isRoot())`で必ず存在するViewTreeのルート要素を取ってきて、そこから対象Viewを検索するようなactionを`perform()`する手法に置き換えます。

Viewを検索するような`ViewAction`は [StackOverflow - Android Espresso wait for text to appear](https://stackoverflow.com/questions/49796132/android-espresso-wait-for-text-to-appear) を参考に実装。

```kotlin
fun waitForView(viewMatcher: Matcher<View>, rootMatcher: Matcher<Root>, timeout: Long = 3000L): ViewInteraction {
    val start = System.currentTimeMillis()
    do {
        try {
            // Activityの遷移などRootViewが変化する場合に対応するため onView　を毎度繰り返す
            onView(ViewMatchers.isRoot()).inRoot(rootMatcher).perform(searchView(viewMatcher))
            return onView(viewMatcher).inRoot(rootMatcher)
        } catch (e: NoMatchingViewException) {
            Thread.sleep(200L)
        }
    } while (System.currentTimeMillis() < start + timeout)
    throw RuntimeException("timeout, no view found: $viewMatcher")
}

fun searchView(matcher: Matcher<View>) = object : ViewAction {
    override fun getConstraints(): Matcher<View> {
        return ViewMatchers.isRoot()
    }

    override fun getDescription(): String {
        return "wait for view: $matcher"
    }

    override fun perform(uiController: UiController, view: View) {
        for (child in TreeIterables.breadthFirstViewTraversal(view)) {
            if (matcher.matches(child)) {
                return
            }
        }

        throw NoMatchingViewException.Builder()
            .withRootView(view)
            .withViewMatcher(matcher)
            .build()
    }
}
```

調べると色々な人が試行錯誤している様子…求むベストプラクティス