1

I'm using a group from Constraint Layout to toggle visibility of a set of views. I'm able to achieve the functionality properly but while running espresso tests for this, it somehow fails.

onView(withId(R.id.group)).check(matches(isDisplayed)); always fails.

I've verified that the view is visible with withEffectiveVisibility.

I've also checked and made sure that all views in the group are visible at the time the assertion is made.

Any ideas on why isDisplayed() fails?

Edit:

androidx.test.espresso.base.DefaultFailureHandler$AssertionFailedWithCauseError: 'is displayed on the screen to the user' doesn't match the selected view.
Expected: is displayed on the screen to the user
Got: "Group{id=2131361855, res-name=amount_details_container, visibility=VISIBLE, 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=false, is-selected=false, layout-params=androidx.constraintlayout.widget.ConstraintLayout$LayoutParams@5542159, tag=null, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0}"

at dalvik.system.VMStack.getThreadStackTrace(Native Method)
at java.lang.Thread.getStackTrace(Thread.java:1566)
at androidx.test.espresso.base.DefaultFailureHandler.getUserFriendlyError(DefaultFailureHandler.java:96)
at androidx.test.espresso.base.DefaultFailureHandler.handle(DefaultFailureHandler.java:59)
at androidx.test.espresso.ViewInteraction.waitForAndHandleInteractionResults(ViewInteraction.java:324)
at androidx.test.espresso.ViewInteraction.check(ViewInteraction.java:306)
at <FRAGMENT> 
at java.lang.reflect.Method.invoke(Native Method)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at androidx.test.internal.runner.junit4.statement.RunBefores.evaluate(RunBefores.java:80)
at androidx.test.internal.runner.junit4.statement.RunAfters.evaluate(RunAfters.java:61)
at androidx.test.rule.ActivityTestRule$ActivityStatement.evaluate(ActivityTestRule.java:531)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:56)
at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:392)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1932)
Caused by: junit.framework.AssertionFailedError: 'is displayed on the screen to the user' doesn't match the selected view.
Expected: is displayed on the screen to the user
Got: "Group{id=2131361855, res-name=amount_details_container, visibility=VISIBLE, 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=false, is-selected=false, layout-params=androidx.constraintlayout.widget.ConstraintLayout$LayoutParams@5542159, tag=null, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0}"

at androidx.test.espresso.matcher.ViewMatchers.assertThat(ViewMatchers.java:540)
at androidx.test.espresso.assertion.ViewAssertions$MatchesViewAssertion.check(ViewAssertions.java:105)
at androidx.test.espresso.ViewInteraction$SingleExecutionViewAssertion.check(ViewInteraction.java:425)
at androidx.test.espresso.ViewInteraction$2.call(ViewInteraction.java:288)
at androidx.test.espresso.ViewInteraction$2.call(ViewInteraction.java:272)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
Saurabh
  • 426
  • 1
  • 7
  • 18
  • Usually there is an error message that tells you why it failed - like for onDisplayed you could get the error message that says it couldn't find the view or "more than 80 % of the view has to be visible" and so on. Post that error here. – Steven Aug 20 '19 at 19:39
  • @Steven I've updated the post with the error log – Saurabh Aug 20 '19 at 21:22
  • @Saurabh : Did you get any solution for this? I am facing the same issue. but no luck till now. – srv_sud Sep 17 '19 at 11:54
  • @srv_sud no I just went with verifying all the views in the group instead. – Saurabh Sep 17 '19 at 21:59

2 Answers2

1

If you look at the documentation you will see that withEffectiveVisibility() is explained as:

Returns a matcher that matches {@link View}s that have "effective" visibility set to the given value. Effective visibility takes into account not only the view's visibility value, but also that of its ancestors. In case of View.VISIBLE, this means that the view and all of its ancestors have visibility=VISIBLE. In case of GONE and INVISIBLE, it's the opposite - any GONE or INVISIBLE parent will make all of its children have their effective visibility.

Note: Contrary to what the name may imply, view visibility does not directly translate into whether the view is displayed on screen (use isDisplayed() for that). For example, the view and all of its ancestors can have visibility=VISIBLE, but the view may need to be scrolled to in order to be actually visible to the user. Unless you're specifically targeting the visibility value with your test, use isDisplayed.

EffectiveVisibility only checks whether that view and its ancestors' visibilities are equal to View.VISIBLE. However, View.VISIBLE does not imply that the view is displayed on the screen. Therefore, it is perfectly plausible that withEffectiveVisibility returns true while isDisplayed returns false.

So in your case, your view, and your view's parent's visibility is View.VISIBLE, but it is not displayed on the screen. To fix this issue, you could either setup an IdlingResource to wait until the view is shown like here: Is it possible to use Espresso's IdlingResource to wait until a certain view appears? or if you wanted to, you could just make the espresso thread wait until the view is shown. Hope this helps.

Steven
  • 442
  • 3
  • 9
  • I think I wasn't clear. This isn't the only view in the layout but somewhere in the center of the layout hierarchy. And I have no problem testing those. Just the Group itself. To debug, I checked all the views of the group for `isDisplayed()` before checking the group and all of those passed but the group failed. – Saurabh Aug 21 '19 at 20:44
0

I had a similar issue testing the visibility of a LinearLayout with width matchParent and height wrapContent. Height was 0 at run-time because I had no children inside the layout yet. Changing height to 1 dp made isDisplayed() test pass.

With some exploration, below is part of the source code for isDisplayed:

@Override
public boolean matchesSafely(View view) {
  return view.getGlobalVisibleRect(new Rect())
      && withEffectiveVisibility(Visibility.VISIBLE).matches(view);
}

public boolean getGlobalVisibleRect(Rect r, Point globalOffset) {
    int width = mRight - mLeft;
    int height = mBottom - mTop;
    if (width > 0 && height > 0) {
        r.set(0, 0, width, height);
        if (globalOffset != null) {
            globalOffset.set(-mScrollX, -mScrollY);
        }
        return mParent == null || mParent.getChildVisibleRect(this, r, globalOffset);
    }
    return false;
}

So even if 100% of the view is within the visible screen and the visibility is set to VISIBLE, width and height still needs to be bigger than 0.

edit: I changed height to 1 dp only to make sure test was not passing due to height being 0 dp. I also ended up using withEffectiveVisibility() which was enough for my case.

Gunce Su
  • 13
  • 5
  • I think what you needed was `withEffectiveVisibility` instead of `isDisplayed`. Also it's a pretty bad approach setting the height to 1dp just to make a test pass – Saurabh May 05 '20 at 19:32