From 108b886f43feeb861807deddfcb1ab241330e242 Mon Sep 17 00:00:00 2001 From: cyberta Date: Thu, 19 Jan 2023 18:47:14 +0100 Subject: improve screenshot tests, run in different languages, test more Fragments/Activities --- .../androidTest/java/utils/CustomInteractions.java | 81 ++++++++++++++++++++ app/src/androidTest/java/utils/CustomMatchers.java | 31 ++++++++ .../androidTest/java/utils/CustomViewActions.java | 89 ++++++++++++++++++++++ 3 files changed, 201 insertions(+) create mode 100644 app/src/androidTest/java/utils/CustomInteractions.java create mode 100644 app/src/androidTest/java/utils/CustomMatchers.java create mode 100644 app/src/androidTest/java/utils/CustomViewActions.java (limited to 'app/src/androidTest/java/utils') diff --git a/app/src/androidTest/java/utils/CustomInteractions.java b/app/src/androidTest/java/utils/CustomInteractions.java new file mode 100644 index 00000000..896e8d9b --- /dev/null +++ b/app/src/androidTest/java/utils/CustomInteractions.java @@ -0,0 +1,81 @@ +package utils; + +import androidx.annotation.Nullable; +import androidx.test.espresso.DataInteraction; +import androidx.test.espresso.NoMatchingViewException; +import androidx.test.espresso.ViewAssertion; +import androidx.test.espresso.ViewInteraction; + +public class CustomInteractions { + + public static @Nullable + ViewInteraction tryResolve(ViewInteraction viewInteraction, int maxTries) { + return tryResolve(viewInteraction, null, maxTries); + } + + public static @Nullable + ViewInteraction tryResolve(ViewInteraction viewInteraction, ViewAssertion assertion) { + return tryResolve(viewInteraction, assertion, 10); + } + + public static @Nullable ViewInteraction tryResolve(ViewInteraction viewInteraction, ViewAssertion assertion, int maxTries) { + ViewInteraction resolvedViewInteraction = null; + int attempt = 0; + boolean hasFound = false; + while (!hasFound && attempt < maxTries) { + try { + resolvedViewInteraction = viewInteraction; + if (assertion != null) { + resolvedViewInteraction.check(assertion); + } + hasFound = true; + } catch (NoMatchingViewException exception) { + System.out.println("NoMatchingViewException attempt: " + attempt); + attempt++; + if (attempt == maxTries) { + throw exception; + } + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + break; + } + } + } + return resolvedViewInteraction; + } + + public static @Nullable + DataInteraction tryResolve(DataInteraction dataInteraction, ViewAssertion assertion, int maxTries) { + DataInteraction resolvedDataInteraction = null; + int attempt = 0; + boolean hasFound = false; + while (!hasFound && attempt < maxTries) { + try { + resolvedDataInteraction = dataInteraction; + if (assertion != null) { + resolvedDataInteraction.check(assertion); + } + hasFound = true; + } catch (Exception exception) { + // TODO: specify expected exception + attempt++; + if (attempt == maxTries) { + throw exception; + } + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + break; + } + } + } + return resolvedDataInteraction; + } + public static @Nullable + DataInteraction tryResolve(DataInteraction dataInteraction, int maxTries) { + return tryResolve(dataInteraction, null, maxTries); + } +} diff --git a/app/src/androidTest/java/utils/CustomMatchers.java b/app/src/androidTest/java/utils/CustomMatchers.java new file mode 100644 index 00000000..5fe11cd2 --- /dev/null +++ b/app/src/androidTest/java/utils/CustomMatchers.java @@ -0,0 +1,31 @@ +package utils; + +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +public class CustomMatchers { + + public 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)); + } + }; + } +} diff --git a/app/src/androidTest/java/utils/CustomViewActions.java b/app/src/androidTest/java/utils/CustomViewActions.java new file mode 100644 index 00000000..fb0dcc27 --- /dev/null +++ b/app/src/androidTest/java/utils/CustomViewActions.java @@ -0,0 +1,89 @@ +package utils; + +import static androidx.test.espresso.matcher.ViewMatchers.isRoot; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; + +import android.view.View; + +import androidx.annotation.StringRes; +import androidx.test.espresso.UiController; +import androidx.test.espresso.ViewAction; +import androidx.test.espresso.util.TreeIterables; + +import org.hamcrest.Matcher; + +public class CustomViewActions { + + public static ViewAction waitForView(int viewId, long timeout) { + return new ViewAction() { + + @Override + public String getDescription() { + return "Wait for view with specific id \n@param viewId: resource ID \n@param timeout: timeout in milli seconds."; + } + + @Override + public Matcher getConstraints() { + return isRoot(); + } + + @Override + public void perform(UiController uiController, View view) { + uiController.loopMainThreadUntilIdle(); + long startTime = System.currentTimeMillis(); + long endTime = startTime + timeout; + Matcher viewMatcher = withId(viewId); + + while (System.currentTimeMillis() < endTime) { + // Iterate through all views on the screen and see if the view we are looking for is there already + for (View child : TreeIterables.breadthFirstViewTraversal(view)) { + // found view with required ID + if (viewMatcher.matches(child)) { + return; + } + } + // Loops the main thread for a specified period of time. + // Control may not return immediately, instead it'll return after the provided delay has passed and the queue is in an idle state again. + uiController.loopMainThreadForAtLeast(100); + } + } + }; + } + + public static ViewAction waitForText(@StringRes int textId, long timeout) { + return new ViewAction() { + + @Override + public String getDescription() { + return "Wait for view with specific id \n@param viewId: resource ID \n@param timeout: timeout in milli seconds."; + } + + @Override + public Matcher getConstraints() { + return isRoot(); + } + + @Override + public void perform(UiController uiController, View view) { + uiController.loopMainThreadUntilIdle(); + long startTime = System.currentTimeMillis(); + long endTime = startTime + timeout; + Matcher viewMatcher = withText(textId); + + while (System.currentTimeMillis() < endTime) { + // Iterate through all views on the screen and see if the view we are looking for is there already + for (View child : TreeIterables.breadthFirstViewTraversal(view)) { + // found view with required ID + if (viewMatcher.matches(child)) { + return; + } + } + // Loops the main thread for a specified period of time. + // Control may not return immediately, instead it'll return after the provided delay has passed and the queue is in an idle state again. + uiController.loopMainThreadForAtLeast(100); + } + } + }; + } +} \ No newline at end of file -- cgit v1.2.3