I'm having issues operating a RecyclerView with different ViewTypes in my UI tests with Kakao:
The RecyclerView is used for an Auto Completer and has two ViewTypes:
- regular results (auto_completer_item_root)
- special item for vicinity search (gps_search_item_root)
What I want to achieve: click on the first item in the RecyclerView which has a View matching my "regular" items. Caveat: the first item in the RecyclerView does not have this type but the above mentioned "special item" type.
The view xmls look like:
<androidx.constraintlayout.widget.ConstraintLayout
...
android:id="@+id/auto_completer_item_root" ...>
<TextView
android:id="@+id/destination_title"
...
/>
...
</androidx.constraintlayout.widget.ConstraintLayout>
and
<androidx.constraintlayout.widget.ConstraintLayout
...
android:id="@+id/gps_search_item_root" ...>
<TextView
android:id="@+id/gps_title"
...
/>
...
</androidx.constraintlayout.widget.ConstraintLayout>
I've got a screen object for the RecyclerView:
class AutoCompleterScreen : KScreen<AutoCompleterScreen>() {
override val layoutId: Int? = null
override val viewClass: Class<*>? = null
val resultList = KRecyclerView(
builder = { withId(R.id.destinationList) },
itemTypeBuilder = {
itemType(::GpsSearch)
itemType(::AutoCompleterResult)
}
)
internal class AutoCompleterResult(parent: Matcher<View>) : KRecyclerItem<AutoCompleterResult>(parent) {
val title: KTextView = KTextView(parent) { withId(R.id.destination_title) }
}
internal class GpsSearch(parent: Matcher<View>) : KRecyclerItem<GpsSearch>(parent) {
val title: KTextView = KTextView(parent) { withId(R.id.gps_title) }
}
}
I tried different approaches in my test, I was hoping that this will work:
onScreen<AutoCompleterScreen> {
resultList {
firstChild<AutoCompleterScreen.AutoCompleterResult> {
title {
click()
}
}
}
}
But it wasn't.
I kept on experimenting and this one worked once, but I couldn't repeat it, so it might be related to the app not being freshly installed:
onScreen<AutoCompleterScreen> {
resultList {
childWith<AutoCompleterScreen.AutoCompleterResult> {
withId(R.id.auto_completer_item_root)
isFirst()
} perform {
click()
}
}
}
On the other hand, I managed to come up with some Espresso code that actually works -- but as I'm using Kaspresso, I'd like to learn how to leverage that framework:
onView(withId(R.id.destinationList))
.perform(actionOnFirstItemWithId(R.id.auto_completer_item_root, click()))
using
fun actionOnFirstItemWithId(@IdRes viewId: Int, action: ViewAction): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher<View> {
return Matchers.allOf(ViewMatchers.isAssignableFrom(RecyclerView::class.java), ViewMatchers.isDisplayed())
}
override fun getDescription(): String {
return "Perform action on the first item that has a view with the given ID."
}
override fun perform(uiController: UiController, view: View) {
val recyclerView = view as RecyclerView
for (i in 0 until recyclerView.adapter!!.itemCount) {
uiController.loopMainThreadForAtLeast(500)
val viewHolder = recyclerView.findViewHolderForAdapterPosition(i)
if (viewHolder?.itemView?.findViewById<View>(viewId) != null) {
if (i != 0) {
recyclerView.scrollToPosition(i)
uiController.loopMainThreadForAtLeast(500)
}
val targetView = viewHolder.itemView.findViewById<View>(viewId)
action.perform(uiController, targetView)
return
}
}
}
}
}
Maybe, Kakao/Kaspresso has problems with the looping of the MainThread, but it should support going without that because of its internal flakysafe implementations.
Can anyone help me out here to work with Kakao/Kaspresso?
Could you send also log from Kaspresso during those failed runs? I had maybe a similar problem, but I can't say for sure without seeing where is it failing.