summaryrefslogtreecommitdiff
path: root/app/src/androidTest/java
diff options
context:
space:
mode:
authorcyberta <cyberta@riseup.net>2023-01-19 18:47:14 +0100
committercyberta <cyberta@riseup.net>2023-01-19 18:47:14 +0100
commit108b886f43feeb861807deddfcb1ab241330e242 (patch)
treea52ea42ad366067c93faaadc4093ed05cb0dd640 /app/src/androidTest/java
parentb0f96ee9ca2c612836f1a59222b1148b763fbedd (diff)
improve screenshot tests, run in different languages, test more Fragments/Activities
Diffstat (limited to 'app/src/androidTest/java')
-rw-r--r--app/src/androidTest/java/se/leap/bitmaskclient/base/ProviderSetupTest.java (renamed from app/src/androidTest/java/base/ProviderSetupTest.java)19
-rw-r--r--app/src/androidTest/java/se/leap/bitmaskclient/base/VpnStartTest.java176
-rw-r--r--app/src/androidTest/java/se/leap/bitmaskclient/suite/ScreenshotTest.java19
-rw-r--r--app/src/androidTest/java/utils/CustomInteractions.java81
-rw-r--r--app/src/androidTest/java/utils/CustomMatchers.java31
-rw-r--r--app/src/androidTest/java/utils/CustomViewActions.java89
6 files changed, 409 insertions, 6 deletions
diff --git a/app/src/androidTest/java/base/ProviderSetupTest.java b/app/src/androidTest/java/se/leap/bitmaskclient/base/ProviderSetupTest.java
index d5a363aa..23db8582 100644
--- a/app/src/androidTest/java/base/ProviderSetupTest.java
+++ b/app/src/androidTest/java/se/leap/bitmaskclient/base/ProviderSetupTest.java
@@ -1,4 +1,4 @@
-package base;
+package se.leap.bitmaskclient.base;
import static android.content.Context.MODE_PRIVATE;
@@ -7,10 +7,15 @@ import static androidx.test.espresso.Espresso.onData;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.Matchers.anything;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.hasToString;
import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES;
+import static utils.CustomInteractions.tryResolve;
import android.content.SharedPreferences;
+import androidx.test.espresso.DataInteraction;
+import androidx.test.espresso.NoMatchingViewException;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
@@ -47,15 +52,17 @@ public class ProviderSetupTest {
@Test
public void testConfigureRiseupVPNScreenshot() {
- Screengrab.screenshot("configureRiseupVPN_before_button_click");
- onData(anything()).inAdapterView(withId(R.id.provider_list)).atPosition(2).perform(click());
- Screengrab.screenshot("configureRiseupVPN_after_button_click");
+ DataInteraction linearLayout = tryResolve(onData(hasToString(containsString("riseup.net")))
+ .inAdapterView(withId(R.id.provider_list)),
+ 2);
+ Screengrab.screenshot("ProviderListActivity");
+ linearLayout.perform(click());
+ Screengrab.screenshot("ProviderListActivity_configureRiseup");
}
@Test
public void testaddManuallyNewProviderScreenshot() {
- Screengrab.screenshot("addManuallyNewProvider_before_button_click");
onData(anything()).inAdapterView(withId(R.id.provider_list)).atPosition(3).perform(click());
- Screengrab.screenshot("addManuallyNewProvider_after_button_click");
+ Screengrab.screenshot("ProviderListActivity_addManuallyNewProvider");
}
}
diff --git a/app/src/androidTest/java/se/leap/bitmaskclient/base/VpnStartTest.java b/app/src/androidTest/java/se/leap/bitmaskclient/base/VpnStartTest.java
new file mode 100644
index 00000000..6c99b90e
--- /dev/null
+++ b/app/src/androidTest/java/se/leap/bitmaskclient/base/VpnStartTest.java
@@ -0,0 +1,176 @@
+package se.leap.bitmaskclient.base;
+
+
+import static android.content.Context.MODE_PRIVATE;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.espresso.Espresso.onData;
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.contrib.DrawerMatchers.isClosed;
+import static androidx.test.espresso.matcher.RootMatchers.isDialog;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withTagValue;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.anything;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.hasToString;
+import static org.hamcrest.Matchers.is;
+import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES;
+import static utils.CustomInteractions.tryResolve;
+
+import android.content.SharedPreferences;
+import android.view.Gravity;
+
+import androidx.test.espresso.DataInteraction;
+import androidx.test.espresso.NoMatchingViewException;
+import androidx.test.espresso.ViewInteraction;
+import androidx.test.espresso.contrib.DrawerActions;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.FixMethodOrder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+
+import se.leap.bitmaskclient.R;
+import tools.fastlane.screengrab.Screengrab;
+import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy;
+import tools.fastlane.screengrab.locale.LocaleTestRule;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class VpnStartTest {
+
+ @ClassRule
+ public static final LocaleTestRule localeTestRule = new LocaleTestRule();
+
+ @Rule
+ public ActivityScenarioRule<StartActivity> mActivityScenarioRule =
+ new ActivityScenarioRule<>(StartActivity.class);
+
+ @Before
+ public void setup() {
+ Screengrab.setDefaultScreenshotStrategy(new UiAutomatorScreenshotStrategy());
+ SharedPreferences preferences = getApplicationContext().getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE);
+ preferences.edit().clear().commit();
+ }
+
+ @Test
+ public void test01_vpnStartTest() {
+ boolean configurationNeeded = configureProviderIfNeeded();
+
+ ViewInteraction mainButtonStop;
+ if (!configurationNeeded) {
+ // click on Main on/off button and start VPN
+ ViewInteraction mainButton = tryResolve(
+ onView(withId(R.id.main_button)),
+ matches(isDisplayed())
+ );
+
+ mainButton.perform(click());
+ tryResolve(
+ onView(allOf(
+ withId(R.id.button),
+ withTagValue(is("button_circle_cancel")))),
+ matches(isDisplayed()),
+ 2);
+ Screengrab.screenshot("VPN_connecting");
+
+ mainButtonStop = tryResolve(
+ onView(allOf(
+ withId(R.id.button),
+ withTagValue(is("button_circle_stop")))),
+ matches(isDisplayed()),
+ 20);
+ Screengrab.screenshot("VPN_connected");
+ } else {
+ // on new configurations the VPN is automatically started
+ Screengrab.screenshot("VPN_connecting");
+ mainButtonStop = tryResolve(
+ onView(allOf(
+ withId(R.id.button),
+ withTagValue(is("button_circle_stop")))),
+ matches(isDisplayed()),
+ 20);
+ Screengrab.screenshot("VPN_connected");
+ }
+
+ mainButtonStop.perform(click());
+ Screengrab.screenshot("VPN_ask_disconnect");
+
+ onView(withText(android.R.string.yes))
+ .inRoot(isDialog())
+ .check(matches(isDisplayed()))
+ .perform(click());
+ Screengrab.screenshot("VPN_disconnected");
+ }
+
+ @Test
+ public void test02_SettingsFragmentScreenshots() {
+ onView(withId(R.id.drawer_layout))
+ .check(matches(isClosed(Gravity.LEFT))) // Left Drawer should be closed.
+ .perform(DrawerActions.open()); // Open Drawer
+
+ Screengrab.screenshot("navigationDrawer");
+
+ // Start the screen of your activity.
+ onView(withId(R.id.advancedSettings))
+ .perform(click());
+
+ Screengrab.screenshot("settingsFragment");
+ }
+
+ @Test
+ public void test03_LocationSelectionFragmentScreenshots() {
+ onView(withId(R.id.drawer_layout))
+ .check(matches(isClosed(Gravity.LEFT))) // Left Drawer should be closed.
+ .perform(DrawerActions.open()); // Open Drawer
+
+ onView(withId(R.id.manualGatewaySelection))
+ .perform(click());
+
+ Screengrab.screenshot("GatewaySelectionFragment");
+ }
+
+ @Test
+ public void test04_AppExclusionFragmentScreenshots() {
+ onView(withId(R.id.drawer_layout))
+ .check(matches(isClosed(Gravity.LEFT))) // Left Drawer should be closed.
+ .perform(DrawerActions.open()); // Open Drawer
+
+ onView(withId(R.id.advancedSettings)).perform(click());
+
+ onView(withId(R.id.exclude_apps)).perform(click());
+
+ tryResolve(
+ onData(anything()).inAdapterView(withId(android.R.id.list)).atPosition(2),
+ matches(isDisplayed()),
+ 5);
+
+ Screengrab.screenshot("App_Exclusion_Fragment");
+ }
+
+ public boolean configureProviderIfNeeded() {
+ try {
+ DataInteraction linearLayout = tryResolve(onData(hasToString(containsString("riseup.net")))
+ .inAdapterView(withId(R.id.provider_list)),
+ 2);
+ linearLayout.perform(click());
+ return true;
+ } catch (NoMatchingViewException e) {
+ // it might be that the provider was already configured, so we print the stack
+ // trace here and try to continue
+ e.printStackTrace();
+ }
+ return false;
+ }
+}
diff --git a/app/src/androidTest/java/se/leap/bitmaskclient/suite/ScreenshotTest.java b/app/src/androidTest/java/se/leap/bitmaskclient/suite/ScreenshotTest.java
new file mode 100644
index 00000000..186a50d1
--- /dev/null
+++ b/app/src/androidTest/java/se/leap/bitmaskclient/suite/ScreenshotTest.java
@@ -0,0 +1,19 @@
+package se.leap.bitmaskclient.suite;
+
+
+import androidx.test.filters.LargeTest;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+import se.leap.bitmaskclient.base.ProviderSetupTest;
+import se.leap.bitmaskclient.base.VpnStartTest;
+
+@LargeTest
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ ProviderSetupTest.class,
+ VpnStartTest.class,
+})
+public class ScreenshotTest {
+}
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<View> childAtPosition(
+ final Matcher<View> parentMatcher, final int position) {
+
+ return new TypeSafeMatcher<View>() {
+ @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<View> 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<View> 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