package de.dennisguse.opentracks; import android.app.ActivityManager; import android.app.Instrumentation; import android.content.Context; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.widget.ListView; import androidx.test.espresso.Espresso; import androidx.test.espresso.IdlingResource; import androidx.test.espresso.UiController; import androidx.test.espresso.ViewAction; import androidx.test.espresso.ViewInteraction; import androidx.test.espresso.action.MotionEvents; import androidx.test.espresso.assertion.ViewAssertions; import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.GrantPermissionRule; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import de.dennisguse.opentracks.services.TrackDeleteService; import static androidx.test.espresso.Espresso.onData; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.Espresso.pressBack; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.action.ViewActions.scrollTo; import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.anything; @LargeTest @RunWith(AndroidJUnit4.class) public class EspressoDeleteTrackTest { @Rule public ActivityScenarioRule mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class); @Rule public GrantPermissionRule mGrantPermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION); private MyIdlingResource idlingResource; @Before public void registerIntentServiceIdlingResource() { Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); idlingResource = new MyIdlingResource(instrumentation.getTargetContext()); Espresso.registerIdlingResources(idlingResource); } @After public void unregisterIntentServiceIdlingResource() { Espresso.unregisterIdlingResources(idlingResource); } @Ignore("Test fails permanently") @Deprecated @Test public void espressoDeleteTrackTest() { int countBefore; { // TrackListActivity: start recording ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record)); trackControllerRecordButton.perform(click()); } { // TrackRecordingActivity ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record)); ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop)); // wait; stay recording trackControllerRecordButton.perform(waitFor(5000)); // stop; trackControllerStopButton.perform(veryLongTouch(1600)); } // back pressBack(); // get number of items before deleting a track countBefore = numberOfItemsListView(); // select track onData(anything()).inAdapterView(withId(R.id.track_list)).atPosition(0).perform(veryLongTouch(5000)); // open menu and delete selected track ViewInteraction overflowMenuButton = onView( allOf(withContentDescription("More options"), childAtPosition( childAtPosition( withId(R.id.toolbar), 2), 2), isDisplayed())); overflowMenuButton.perform(click()); ViewInteraction appCompatTextView = onView( allOf(withId(R.id.title), withText("Delete"), childAtPosition( childAtPosition( withId(R.id.content), 0), 0), isDisplayed())); appCompatTextView.perform(click()); ViewInteraction appCompatButton = onView( allOf(withId(android.R.id.button1), withText("Yes"), childAtPosition( childAtPosition( withId(R.id.buttonPanel), 0), 3))); appCompatButton.perform(scrollTo(), click()); // check number of items after delete onView(withId(R.id.track_list)).check(ViewAssertions.matches(withListSize(countBefore - 1))); } private static Matcher childAtPosition( final Matcher parentMatcher, final int position) { return new TypeSafeMatcher() { @Override public void describeTo(Description description) { description.appendText("Child at position " + position + " in parent "); parentMatcher.describeTo(description); } @Override public boolean matchesSafely(View view) { ViewParent parent = view.getParent(); return parent instanceof ViewGroup && parentMatcher.matches(parent) && view.equals(((ViewGroup) parent).getChildAt(position)); } }; } public static Matcher withListSize (final int size) { return new TypeSafeMatcher () { @Override public boolean matchesSafely (final View view) { return ((ListView) view).getCount () == size; } @Override public void describeTo (final Description description) { description.appendText ("ListView should have " + size + " items"); } }; } private static ViewAction waitFor(final long duration_ms) { return new ViewAction() { @Override public String getDescription() { return "Wait for milliseconds."; } @Override public Matcher getConstraints() { return isDisplayed(); } @Override public void perform(UiController uiController, final View view) { uiController.loopMainThreadForAtLeast(duration_ms); } }; } private static ViewAction veryLongTouch(final int duration_ms) { return new ViewAction() { @Override public String getDescription() { return "Perform long touch."; } @Override public Matcher getConstraints() { return isDisplayed(); } @Override public void perform(UiController uiController, final View view) { // Get view absolute position int[] location = new int[2]; view.getLocationOnScreen(location); // Offset coordinates by view position float[] coordinates = new float[]{location[0] + 1, location[1] + 1}; // Send down event, pause, and send up MotionEvent down = MotionEvents.sendDown(uiController, coordinates, new float[]{1f, 1f}).down; uiController.loopMainThreadForAtLeast(duration_ms); MotionEvents.sendUp(uiController, down, coordinates); } }; } private int numberOfItemsListView() { final int[] counts = new int[1]; onView(withId(R.id.track_list)).check(matches(new TypeSafeMatcher() { @Override public boolean matchesSafely(View view) { ListView listView = (ListView) view; counts[0] = listView.getCount(); return true; } @Override public void describeTo(Description description) { } })); return counts[0]; } private static class MyIdlingResource implements IdlingResource { private final Context context; private ResourceCallback resourceCallback; public MyIdlingResource(Context context) { this.context = context; } @Override public String getName() { return MyIdlingResource.class.getName(); } @Override public void registerIdleTransitionCallback(IdlingResource.ResourceCallback resourceCallback) { this.resourceCallback = resourceCallback; } @Override public boolean isIdleNow() { boolean idle = !isDeleteServiceRunning(); if (idle && resourceCallback != null) { resourceCallback.onTransitionToIdle(); } return idle; } private boolean isDeleteServiceRunning() { ActivityManager manager = (ActivityManager) context.getSystemService( Context.ACTIVITY_SERVICE); for (ActivityManager.RunningServiceInfo info : manager.getRunningServices(Integer.MAX_VALUE)) { if (TrackDeleteService.class.getName().equals( info.service.getClassName())) { return true; } } return false; } } }