package com.example.test import android.os.SystemClock import android.view.View import android.view.ViewGroup import androidx.test.espresso.PerformException import androidx.test.espresso.UiController import androidx.test.espresso.ViewAction import androidx.test.espresso.matcher.ViewMatchers.isRoot import org.hamcrest.Matcher import java.util.ArrayDeque import java.util.concurrent.TimeoutException fun waitUntil(matcher: Matcher, timeoutMs: Long = 5_000): ViewAction = WaitUntil(matcher, timeoutMs) private class WaitUntil( private val matcher: Matcher, private val timeoutMs: Long ) : ViewAction { override fun getConstraints(): Matcher = isRoot() override fun getDescription(): String = "wait up to $timeoutMs ms for: $matcher" override fun perform(uiController: UiController, view: View) { val endTime = SystemClock.uptimeMillis() + timeoutMs do { if (findMatchingView(view, matcher) != null) { return } uiController.loopMainThreadForAtLeast(50) } while (SystemClock.uptimeMillis() < endTime) throw PerformException.Builder() .withActionDescription(getDescription()) .withViewDescription(view.toString()) .withCause( TimeoutException( "No matching view appeared within $timeoutMs ms" ) ) .build() } private fun findMatchingView(root: View, matcher: Matcher): View? { val queue = ArrayDeque() queue.add(root) while (!queue.isEmpty()) { val current = queue.removeFirst() if (matcher.matches(current)) { return current } if (current is ViewGroup) { for (index in 0 until current.childCount) { queue.add(current.getChildAt(index)) } } } return null } }