Reputation: 42959
I struggle with the implementation of an instrumented test based on androidx and espresso.
I am trying to prepare a ViewInteraction
in a generic way, since I require the same in multiple actions performed by a robot using the UI. Most of those actions have to interact with a specific view element inside a hierarchy. That view hierarchy is created by a library (a vertical stepper), so I do not really have an influence on that. A number of views in that hierarchy do not have IDs unfortunately, which is why I have to dig around a bit in the hierarchy ...
This is the (shortened) test case:
@Test
fun testFlow_ABC() {
<some preparations>
createRobot<ABCServiceRobot>()
.<some robot actions>
.checkThatFailMessageIsShownInFirstStep()
.<some robot actions>
verify(activityService).<some method>(any())
}
The robot method checkThatFailMessageIsShownInFirstStep()
in the ABCServiceRobot
class (just the relevant methods):
fun checkThatFailMessageIsShownInFirstStep(): ABCServiceRobot {
awaitInStepperStep(
1,
withId(R.id.step_error_container),
hasDescendant(
allOf(
withText(R.string.abc_failed_reason_unknown),
isDisplayed(),
)
)
)
return this
}
private fun awaitInStepperStep(stepNumber: Int, viewMatcher: Matcher<View>, matchToAwait: Matcher<View>) {
onStepperStep(stepNumber, viewMatcher).perform(waitFor(matchToAwait))
}
private fun onStepperStep(stepNumber: Int, viewMatcher: Matcher<View>): ViewInteraction {
return onView(
allOf(
// a view as specified
viewMatcher,
// inside the stepper form
isDescendantOfA(
withId(R.id.authorization_service_vertical_stepper_form),
),
// inside the specified stepper step
isDescendantOfA(
allOf(
// unfortunately this does not have an ID
instanceOf(LinearLayout::class.java),
// limit to two LinearLayouts inside each stepper step
withParent(
withId(R.id.steps_scroll)
),
// pick the step with the specified number in the header
hasDescendant(
allOf(
withId(R.id.step_header),
hasDescendant(
allOf(
withId(R.id.step_number),
withText("$stepNumber"),
)
),
),
),
),
),
)
)
}
Here is a screenshot of the view structure (sorry, no way to represent as text in a sane manner):
This is the error message I receive about the two matched view elements:
androidx.test.espresso.AmbiguousViewMatcherException: '(view.getId() is <2131231925/xxx.yyy.zzz:id/step_error_container> and is descendant of a view matching (an instance of android.widget.LinearLayout and (view is an instance of android.view.ViewGroup and has descendant matching (view.getId() is <2131231929/xxx.yyy.zzz:id/step_number> and an instance of android.widget.TextView and view.getText() with or without transformation to match: is "1"))))' matches 2 views in the hierarchy:
- [1] LinearLayout{id=2131231925, res-name=step_error_container, visibility=VISIBLE, width=544, height=81, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, layout-params=android.widget.LinearLayout$LayoutParams@YYYYYY, tag=null, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=2}
- [2] LinearLayout{id=2131231925, res-name=step_error_container, visibility=GONE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, layout-params=android.widget.LinearLayout$LayoutParams@YYYYYY, tag=null, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=2}
Problem views are marked with '****MATCHES****' below.
This is the textual representation of the clause interpretation as espresso logs it, which read correct to me (as intended):
(view.getId() is <2131231925/xxx.yyy.zzz:id/step_error_container>
and is descendant of a view matching (
an instance of android.widget.LinearLayout
and (
view is an instance of android.view.ViewGroup
and has descendant matching (
view.getId() is <2131231929/xxx.yyy.zzz:id/step_number>
and an instance of android.widget.TextView
and view.getText() with or without transformation to match: is "1"
)
)
)
)
My issue and question here:
I fail to understand why this results in two matching views. Yes, there are two view elements with the ID I look for, one in each step. Which is why I have the clause that is meant to limit the result set to the match in the specified, the first step (with "1" in the step header). That clause appears to have no effect. Why is what? What can I do to achieve the desired result to limit the matches to those inside the view hierarchy of the specified step?
Upvotes: 0
Views: 59