Espresso - How to tap on specific item of a BottomNavigationBar

1.2k views Asked by At

I'm trying to click on an item of my BottomNavigationBar but even if I record an Espresso test and then put it on my test it doesn't find the onView.

What record an Espresso test is giving wrote for me is :

private fun childAtPosition(
        parentMatcher: Matcher<View>, position: Int
    ): Matcher<View> {

        return object : TypeSafeMatcher<View>() {
            override fun describeTo(description: Description) {
                description.appendText("Child at position $position in parent ")
                parentMatcher.describeTo(description)
            }

            public override fun matchesSafely(view: View): Boolean {
                val parent = view.parent
                return parent is ViewGroup && parentMatcher.matches(parent)
                    && view == parent.getChildAt(position)
            }
        }
    }


    val bottomNavigationItemView = onView(
            allOf(
                withContentDescription("Home"),
                childAtPosition(
                    childAtPosition(
                        withId(R.id.navigation_bar),
                        0
                    ),
                    1
                ),
                isDisplayed()
            )
        )
        bottomNavigationItemView.perform(click())

And even if I try this, it says :

androidx.test.espresso.PerformException: Error performing 'single click' on view '(with content description text: is "Home" and Child at position 1 in parent Child at position 0 in parent with id is <package_name:id/navigation_bar> and is displayed on the screen to the user)'.

I've tried several ways, with BoundedMatcher but I couldn't get it work. What I'm missing?

fun withTitle(titleTested: String): Matcher<View?>? {
        return object : BoundedMatcher<View?, BottomNavigationItemView>(
            BottomNavigationItemView::class.java
        ) {
            private var triedMatching = false
            private var title: String? = null
            override fun describeTo(description: Description) {
                if (triedMatching) {
                    description.appendText("with title: $titleTested")
                    description.appendText("But was: " + title.toString())
                }
            }

            override fun matchesSafely(item: BottomNavigationItemView): Boolean {
                triedMatching = true
                title = item.itemData.title.toString()
                return title == titleTested
            }
        }
    }

The hierarchy of my xml is :

CoordinatorLayout(id : mainCordinator)
   RelativeLayout (no id)
      com.google.android.material.bottomnavigation.BottomNavigationView(id : navigation_bar) />
         Coordinatorlayout(id: anotherCoordinator)
           FrameLayout(id: framelayout)
           FloatActionButton(id: fab_test)
         />
   />  
/>

I'd like to know the easiest way so I can call a function passing the index of the bottomNav and click on it, or even sending as a parameter the ContentDescription/Title/Text, whatever it is.

I'm creating the items dynamically as follows :

        ArrayList<String> items...
        ....Create....
        Menu menu = binding.navigationBar.getMenu();
        menu.clear();
        for (int i = 0; i < items.size(); i++) {
            menu.add(
                0,
                items.get(i),
                i,
                bottomBarTitles.get(i));
            menu.getItem(i).setIcon(bottomBarImages.getItemIcon(items.get(i)));
            bottomMenu.add(items.get(i));

Items is a ArrayList<String> with for instance "Home, Content, Images, More" And then I have another ArrayList with the images.

Edit

I could make it work using UiDevice and UiSelector but I would like to use Espresso for this one.

val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
            device.findObject(UiSelector().text("Home")).click()
2

There are 2 answers

0
Rinat Diushenov On

Can you try this one:

onView( allOf( withText("Home), isDescendantOfA(withId(R.id.navigation_bar)) ).perform( click() )

basically we are looking for a view with text "Home" which also happens to be descendant of a view with id of your bottom bar.

0
Shawn On

I would think using withID would for you, I know espresso recorded tests are flaky and it's best to write your own code.

onView(withId(R.id.home).perform(click())

I'm not sure what IDs you've assigned to navigation items, so I used home.

If the multiple views have the same ID you can use.

onView(withId(R.id.home), withText("Home")).performClick()

If you use withText() by itself it will try and perform the click action on all Views that have the word "Home".