使用 Espresso 进行测试时,如何在 Android 上的 NavigationView 中单击菜单项?
How do you click a menuItem in NavigationView on Android when testing with Espresso?
如何使用 Espresso 从设计库中单击 NavigationView 中的菜单项?它在整个菜单可见的较大设备上工作正常,但在横向或较小的设备上,我的菜单底部不在屏幕上,我无法找到 scroll/swipe 这些 menuItems 的方法,所以我可以点击他们。
通话中:
onView(withText(R.string.contact_us)).perform(scrollTo()).check(matches(isDisplayed())).perform(click());
生成以下内容:
Caused by: java.lang.RuntimeException: Action will not be performed
because the target view does not match one or more of the following
constraints: (view has effective visibility=VISIBLE and is descendant
of a: (is assignable from class: class android.widget.ScrollView or is
assignable from class: class android.widget.HorizontalScrollView))
Target view: "NavigationMenuItemView{id=-1, visibility=VISIBLE,
width=888, height=144, 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, root-is-layout-requested=false,
has-input-connection=false, x=0.0, y=1653.0, text=Contact Us,
input-type=0, ime-target=false, has-links=false}"
NavigationView 继承自 FrameLayout 并正在执行 canvas 滚动切换。
https://developer.android.com/reference/android/support/design/widget/NavigationView.html
我尝试编写一个 customViewAction 以使用以下操作缓慢滚动屏幕并检查 menuItem 是否可见,如果不可见则继续滚动,但我无法让它工作。
CoordinatesProvider coordinatesProvider = new CoordinatesProvider() {
public float[] calculateCoordinates(View view) {
float[] xy = GeneralLocation.BOTTOM_CENTER.calculateCoordinates(view);
xy[0] += 0.0F * (float)view.getWidth();
xy[1] += -0.083F * (float)view.getHeight();
return xy;
}
};
onView(withId(R.id.navigation_view)).perform(new GeneralSwipeAction(Swipe.SLOW,
coordinatesProvider, GeneralLocation.TOP_CENTER, Press.FINGER));
onView(withText(R.string.contact_us)).check(matches(isDisplayed())).perform(click());
有没有人知道如何在 Espresso 上测试 navigationView?我很确定 customViewAction 背后的逻辑会起作用,但我无法让它起作用。
我想通了,我使用了自定义 viewAction
public static ViewAction swipeForTextToBeVisible(final String text, final long millis, final View navigationView) {
return new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return isRoot();
}
@Override
public String getDescription() {
return "swipe for a specific view with text <" + text + "> during " + millis + " millis.";
}
@Override
public void perform(final UiController uiController, final View view) {
uiController.loopMainThreadUntilIdle();
final long startTime = System.currentTimeMillis();
final long endTime = startTime + millis;
final Matcher<View> viewMatcher = withText(text);
do {
boolean returnAfterOneMoreSwipe = false; //not sure why I have to do one more swipe but it works
for (View child : TreeIterables.breadthFirstViewTraversal(view)) {
// found view with required ID
if (viewMatcher.matches(child) && child.getVisibility() == View.VISIBLE) {
returnAfterOneMoreSwipe = true;
}
}
CoordinatesProvider coordinatesProvider = new CoordinatesProvider() {
public float[] calculateCoordinates(View view) {
float[] xy = GeneralLocation.BOTTOM_CENTER.calculateCoordinates(view);
xy[0] += 0.0F * (float)view.getWidth();
xy[1] += -0.083F * (float)view.getHeight();
return xy;
}
};
//Below code is c/p from perform in android/support/test/espresso/action/GeneralSwipeAction.class
float[] startCoordinates = coordinatesProvider.calculateCoordinates(navigationView);
float[] endCoordinates = GeneralLocation.TOP_CENTER.calculateCoordinates(navigationView);
float[] precision = Press.FINGER.describePrecision();
Swiper.Status status = Swiper.Status.FAILURE;
for(int tries = 0; tries < 3 && status != Swiper.Status.SUCCESS; ++tries) {
try {
status = Swipe.SLOW.sendSwipe(uiController, startCoordinates, endCoordinates, precision);
} catch (RuntimeException var9) {
throw (new PerformException.Builder()).withActionDescription(this.getDescription()).withViewDescription(HumanReadables.describe(navigationView)).withCause(var9).build();
}
int duration = ViewConfiguration.getPressedStateDuration();
if(duration > 0) {
uiController.loopMainThreadForAtLeast((long)duration);
}
}
//End c/p from android/support/test/espresso/action/GeneralSwipeAction.class
if (returnAfterOneMoreSwipe) {
return;
}
uiController.loopMainThreadForAtLeast(50);
}
while (System.currentTimeMillis() < endTime);
// timeout happens
throw new PerformException.Builder()
.withActionDescription(this.getDescription())
.withViewDescription(HumanReadables.describe(view))
.withCause(new TimeoutException())
.build();
}
};
}
我不喜欢这个解决方案,它不是最干净的,因为出于某种原因我不得不在它说它可见但它有效后再次滑动。
要使用它,请按以下方式调用它:
onView(isRoot()).perform(CustomViewActions.swipeForTextToBeVisible(getActivity().getString(R.string.contact_us),2000, getActivity().findViewById(R.id.navigation_view)));
您需要使用 espresso-contrib:
import android.support.test.espresso.contrib.DrawerActions;
要打开菜单,您可以这样做:
onView(withId(R.id.drawer_layout)).perform(DrawerActions.open());
您可以向下滚动到菜单项并检查它是否显示:
onView(withId(R.id.my_menu_item)).perform(scrollTo()).check(matches(isDisplayed()));
然后单击菜单项:
onView(withId(R.id.my_menu_item)).perform(click());
即使菜单项不可见(在列表下方的某处),这对我也有效
onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_login));
如何使用 Espresso 从设计库中单击 NavigationView 中的菜单项?它在整个菜单可见的较大设备上工作正常,但在横向或较小的设备上,我的菜单底部不在屏幕上,我无法找到 scroll/swipe 这些 menuItems 的方法,所以我可以点击他们。
通话中:
onView(withText(R.string.contact_us)).perform(scrollTo()).check(matches(isDisplayed())).perform(click());
生成以下内容:
Caused by: java.lang.RuntimeException: Action will not be performed because the target view does not match one or more of the following constraints: (view has effective visibility=VISIBLE and is descendant of a: (is assignable from class: class android.widget.ScrollView or is assignable from class: class android.widget.HorizontalScrollView)) Target view: "NavigationMenuItemView{id=-1, visibility=VISIBLE, width=888, height=144, 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, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=1653.0, text=Contact Us, input-type=0, ime-target=false, has-links=false}"
NavigationView 继承自 FrameLayout 并正在执行 canvas 滚动切换。 https://developer.android.com/reference/android/support/design/widget/NavigationView.html
我尝试编写一个 customViewAction 以使用以下操作缓慢滚动屏幕并检查 menuItem 是否可见,如果不可见则继续滚动,但我无法让它工作。
CoordinatesProvider coordinatesProvider = new CoordinatesProvider() {
public float[] calculateCoordinates(View view) {
float[] xy = GeneralLocation.BOTTOM_CENTER.calculateCoordinates(view);
xy[0] += 0.0F * (float)view.getWidth();
xy[1] += -0.083F * (float)view.getHeight();
return xy;
}
};
onView(withId(R.id.navigation_view)).perform(new GeneralSwipeAction(Swipe.SLOW,
coordinatesProvider, GeneralLocation.TOP_CENTER, Press.FINGER));
onView(withText(R.string.contact_us)).check(matches(isDisplayed())).perform(click());
有没有人知道如何在 Espresso 上测试 navigationView?我很确定 customViewAction 背后的逻辑会起作用,但我无法让它起作用。
我想通了,我使用了自定义 viewAction
public static ViewAction swipeForTextToBeVisible(final String text, final long millis, final View navigationView) {
return new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return isRoot();
}
@Override
public String getDescription() {
return "swipe for a specific view with text <" + text + "> during " + millis + " millis.";
}
@Override
public void perform(final UiController uiController, final View view) {
uiController.loopMainThreadUntilIdle();
final long startTime = System.currentTimeMillis();
final long endTime = startTime + millis;
final Matcher<View> viewMatcher = withText(text);
do {
boolean returnAfterOneMoreSwipe = false; //not sure why I have to do one more swipe but it works
for (View child : TreeIterables.breadthFirstViewTraversal(view)) {
// found view with required ID
if (viewMatcher.matches(child) && child.getVisibility() == View.VISIBLE) {
returnAfterOneMoreSwipe = true;
}
}
CoordinatesProvider coordinatesProvider = new CoordinatesProvider() {
public float[] calculateCoordinates(View view) {
float[] xy = GeneralLocation.BOTTOM_CENTER.calculateCoordinates(view);
xy[0] += 0.0F * (float)view.getWidth();
xy[1] += -0.083F * (float)view.getHeight();
return xy;
}
};
//Below code is c/p from perform in android/support/test/espresso/action/GeneralSwipeAction.class
float[] startCoordinates = coordinatesProvider.calculateCoordinates(navigationView);
float[] endCoordinates = GeneralLocation.TOP_CENTER.calculateCoordinates(navigationView);
float[] precision = Press.FINGER.describePrecision();
Swiper.Status status = Swiper.Status.FAILURE;
for(int tries = 0; tries < 3 && status != Swiper.Status.SUCCESS; ++tries) {
try {
status = Swipe.SLOW.sendSwipe(uiController, startCoordinates, endCoordinates, precision);
} catch (RuntimeException var9) {
throw (new PerformException.Builder()).withActionDescription(this.getDescription()).withViewDescription(HumanReadables.describe(navigationView)).withCause(var9).build();
}
int duration = ViewConfiguration.getPressedStateDuration();
if(duration > 0) {
uiController.loopMainThreadForAtLeast((long)duration);
}
}
//End c/p from android/support/test/espresso/action/GeneralSwipeAction.class
if (returnAfterOneMoreSwipe) {
return;
}
uiController.loopMainThreadForAtLeast(50);
}
while (System.currentTimeMillis() < endTime);
// timeout happens
throw new PerformException.Builder()
.withActionDescription(this.getDescription())
.withViewDescription(HumanReadables.describe(view))
.withCause(new TimeoutException())
.build();
}
};
}
我不喜欢这个解决方案,它不是最干净的,因为出于某种原因我不得不在它说它可见但它有效后再次滑动。
要使用它,请按以下方式调用它:
onView(isRoot()).perform(CustomViewActions.swipeForTextToBeVisible(getActivity().getString(R.string.contact_us),2000, getActivity().findViewById(R.id.navigation_view)));
您需要使用 espresso-contrib:
import android.support.test.espresso.contrib.DrawerActions;
要打开菜单,您可以这样做:
onView(withId(R.id.drawer_layout)).perform(DrawerActions.open());
您可以向下滚动到菜单项并检查它是否显示:
onView(withId(R.id.my_menu_item)).perform(scrollTo()).check(matches(isDisplayed()));
然后单击菜单项:
onView(withId(R.id.my_menu_item)).perform(click());
即使菜单项不可见(在列表下方的某处),这对我也有效
onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_login));