summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorcyberta <cyberta@riseup.net>2023-03-30 08:49:00 +0000
committercyberta <cyberta@riseup.net>2023-03-30 08:49:00 +0000
commit49adad2fabcee3077be729064409bfcfbc99fe01 (patch)
tree6017e01def3ab93167d2e3ddcaad34aaec97a6ff /app
parent26c71bc66038924f07cfba2f578138acaf126b68 (diff)
parent93bb86626e68c9820b7f40eebfc71c959ed0f047 (diff)
Merge branch 'fastlane' into 'master'
fastlane See merge request leap/bitmask_android!241
Diffstat (limited to 'app')
-rw-r--r--app/build.gradle24
-rw-r--r--app/src/androidTest/java/se/leap/bitmaskclient/base/ProviderBaseTest.java157
-rw-r--r--app/src/androidTest/java/utils/CustomInteractions.java82
-rw-r--r--app/src/androidTest/java/utils/CustomMatchers.java31
-rw-r--r--app/src/androidTest/java/utils/CustomViewActions.java89
-rw-r--r--app/src/androidTestCustom/java/se/leap/bitmaskclient/base/CustomProviderTest.java49
-rw-r--r--app/src/androidTestCustom/java/se/leap/bitmaskclient/suite/ScreenshotTest.java17
-rw-r--r--app/src/androidTestNormal/java/se/leap/bitmaskclient/base/BitmaskTest.java42
-rw-r--r--app/src/androidTestNormal/java/se/leap/bitmaskclient/base/ProviderSetupTest.java68
-rw-r--r--app/src/androidTestNormal/java/se/leap/bitmaskclient/suite/ScreenshotTest.java19
-rw-r--r--app/src/debug/AndroidManifest.xml43
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java4
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/views/MainButton.java2
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/testutils/TestSetupHelper.java (renamed from app/src/sharedTest/java/se.leap.bitmaskclient/testutils/TestSetupHelper.java)0
-rw-r--r--app/src/test/resources/v4/riseup.net.cert96
15 files changed, 661 insertions, 62 deletions
diff --git a/app/build.gradle b/app/build.gradle
index 07210528..5e5de9d0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -276,12 +276,13 @@ android {
applicationIdSuffix ".beta"
appSuffix = " Beta"
buildConfigField "Boolean", "DEBUG_MODE", "true"
+ testCoverageEnabled = false
// tor-android doesn't know this build-type, fallback to release in that case
matchingFallbacks = ['release']
}
debug {
- testCoverageEnabled = true
+ testCoverageEnabled = false
buildConfigField "Boolean", "DEBUG_MODE", "true"
}
}
@@ -306,11 +307,6 @@ android {
test {
resources.srcDirs += ['src/test/resources']
- java.srcDirs += ['src/sharedTest/java']
- }
-
- androidTest {
- java.srcDirs += ['src/sharedTest/java']
}
fatweb {
@@ -410,13 +406,17 @@ dependencies {
testImplementation 'org.powermock:powermock-module-junit4-rule:2.0.9'
testImplementation group: 'com.tngtech.java', name: 'junit-dataprovider', version: '1.10.0'
- androidTestImplementation 'org.mockito:mockito-core:3.6.0'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
- androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.4.0'
- androidTestImplementation 'androidx.test.espresso:espresso-intents:3.4.0'
- //TODO: remove that library
- androidTestImplementation 'com.jayway.android.robotium:robotium-solo:5.6.3'
+ androidTestImplementation 'org.mockito:mockito-core:3.9.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.5.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-intents:3.5.0'
+
+ androidTestImplementation 'tools.fastlane:screengrab:2.1.1'
+ testImplementation 'tools.fastlane:screengrab:2.1.1'
+
+
testImplementation 'org.json:json:20180813'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3'
annotationProcessor 'com.squareup.dagger:dagger-compiler:1.2.2'
diff --git a/app/src/androidTest/java/se/leap/bitmaskclient/base/ProviderBaseTest.java b/app/src/androidTest/java/se/leap/bitmaskclient/base/ProviderBaseTest.java
new file mode 100644
index 00000000..bbfcdc8b
--- /dev/null
+++ b/app/src/androidTest/java/se/leap/bitmaskclient/base/ProviderBaseTest.java
@@ -0,0 +1,157 @@
+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 abstract class ProviderBaseTest {
+
+ @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() throws InterruptedException {
+ 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());
+ Thread.sleep(50);
+ 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 abstract boolean configureProviderIfNeeded();
+}
diff --git a/app/src/androidTest/java/utils/CustomInteractions.java b/app/src/androidTest/java/utils/CustomInteractions.java
new file mode 100644
index 00000000..9e3a8f9d
--- /dev/null
+++ b/app/src/androidTest/java/utils/CustomInteractions.java
@@ -0,0 +1,82 @@
+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);
+ exception.printStackTrace();
+ 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
diff --git a/app/src/androidTestCustom/java/se/leap/bitmaskclient/base/CustomProviderTest.java b/app/src/androidTestCustom/java/se/leap/bitmaskclient/base/CustomProviderTest.java
new file mode 100644
index 00000000..1a0814b6
--- /dev/null
+++ b/app/src/androidTestCustom/java/se/leap/bitmaskclient/base/CustomProviderTest.java
@@ -0,0 +1,49 @@
+package se.leap.bitmaskclient.base;
+
+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.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.withText;
+import static utils.CustomInteractions.tryResolve;
+
+import androidx.test.espresso.ViewInteraction;
+
+import org.junit.Test;
+
+import se.leap.bitmaskclient.R;
+import tools.fastlane.screengrab.Screengrab;
+
+public class CustomProviderTest extends ProviderBaseTest {
+
+ @Test
+ @Override
+ public void test01_vpnStartTest() throws InterruptedException {
+ ViewInteraction mainButtonStop;
+ mainButtonStop = tryResolve(
+ onView(withId(R.id.main_button)),
+ matches(isDisplayed()),
+ 30);
+ Screengrab.screenshot("VPN_connected");
+
+ mainButtonStop.perform(click());
+ Screengrab.screenshot("VPN_ask_disconnect");
+
+ ViewInteraction alertDialogOKbutton = tryResolve(onView(withText(android.R.string.yes))
+ .inRoot(isDialog()),
+ matches(isDisplayed()));
+ alertDialogOKbutton.perform(click());
+ Screengrab.screenshot("VPN_disconnected");
+
+ mainButtonStop.perform(click());
+ Thread.sleep(50);
+ Screengrab.screenshot("VPN_connecting");
+ }
+
+ @Override
+ public boolean configureProviderIfNeeded() {
+ return false;
+ }
+} \ No newline at end of file
diff --git a/app/src/androidTestCustom/java/se/leap/bitmaskclient/suite/ScreenshotTest.java b/app/src/androidTestCustom/java/se/leap/bitmaskclient/suite/ScreenshotTest.java
new file mode 100644
index 00000000..a19b0ffd
--- /dev/null
+++ b/app/src/androidTestCustom/java/se/leap/bitmaskclient/suite/ScreenshotTest.java
@@ -0,0 +1,17 @@
+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.CustomProviderTest;
+
+@LargeTest
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ CustomProviderTest.class
+})
+public class ScreenshotTest {
+}
diff --git a/app/src/androidTestNormal/java/se/leap/bitmaskclient/base/BitmaskTest.java b/app/src/androidTestNormal/java/se/leap/bitmaskclient/base/BitmaskTest.java
new file mode 100644
index 00000000..aa437c74
--- /dev/null
+++ b/app/src/androidTestNormal/java/se/leap/bitmaskclient/base/BitmaskTest.java
@@ -0,0 +1,42 @@
+package se.leap.bitmaskclient.base;
+
+
+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.containsString;
+import static org.hamcrest.Matchers.hasToString;
+import static utils.CustomInteractions.tryResolve;
+
+import androidx.test.espresso.DataInteraction;
+import androidx.test.espresso.NoMatchingViewException;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+
+import org.junit.FixMethodOrder;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+
+import se.leap.bitmaskclient.R;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class BitmaskTest extends ProviderBaseTest {
+
+ @Override
+ 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/androidTestNormal/java/se/leap/bitmaskclient/base/ProviderSetupTest.java b/app/src/androidTestNormal/java/se/leap/bitmaskclient/base/ProviderSetupTest.java
new file mode 100644
index 00000000..23db8582
--- /dev/null
+++ b/app/src/androidTestNormal/java/se/leap/bitmaskclient/base/ProviderSetupTest.java
@@ -0,0 +1,68 @@
+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.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;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import se.leap.bitmaskclient.R;
+import se.leap.bitmaskclient.providersetup.ProviderListActivity;
+import tools.fastlane.screengrab.Screengrab;
+import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy;
+import tools.fastlane.screengrab.locale.LocaleTestRule;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class ProviderSetupTest {
+
+ @ClassRule
+ public static final LocaleTestRule localeTestRule = new LocaleTestRule();
+
+ @Rule
+ public ActivityScenarioRule<ProviderListActivity> mActivityScenarioRule =
+ new ActivityScenarioRule<>(ProviderListActivity.class);
+
+ @Before
+ public void setup() {
+ Screengrab.setDefaultScreenshotStrategy(new UiAutomatorScreenshotStrategy());
+ SharedPreferences preferences = getApplicationContext().getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE);
+ preferences.edit().clear().commit();
+ }
+
+ @Test
+ public void testConfigureRiseupVPNScreenshot() {
+ 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() {
+ onData(anything()).inAdapterView(withId(R.id.provider_list)).atPosition(3).perform(click());
+ Screengrab.screenshot("ProviderListActivity_addManuallyNewProvider");
+ }
+}
diff --git a/app/src/androidTestNormal/java/se/leap/bitmaskclient/suite/ScreenshotTest.java b/app/src/androidTestNormal/java/se/leap/bitmaskclient/suite/ScreenshotTest.java
new file mode 100644
index 00000000..5fa45a95
--- /dev/null
+++ b/app/src/androidTestNormal/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.BitmaskTest;
+
+@LargeTest
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ ProviderSetupTest.class,
+ BitmaskTest.class,
+})
+public class ScreenshotTest {
+}
diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml
new file mode 100644
index 00000000..51e17015
--- /dev/null
+++ b/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="se.leap.bitmaskclient"
+ android:requestLegacyExternalStorage="true"
+ >
+ <!-- package is overwritten in build.gradle -->
+
+ <!-- The following permissions are required by fastlane / espresso -->
+ <!-- _____________________________________________________________ -->
+ <!-- Allows unlocking your device and activating its screen so UI tests can succeed -->
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+ <uses-permission android:name="android.permission.WAKE_LOCK"/>
+
+ <!-- Allows for storing and retrieving screenshots -->
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+ tools:ignore="ScopedStorage" />
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+ <!-- Allows changing locales -->
+ <uses-permission android:name="android.permission.CHANGE_CONFIGURATION"
+ tools:ignore="ProtectedPermissions" />
+
+ <uses-permission android:name="android.permission.DUMP"
+ tools:ignore="ProtectedPermissions" />
+ <!-- _____________________________________________________________ -->
+
+</manifest>
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java
index 68435fec..aa894cca 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java
@@ -424,11 +424,11 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen
Provider currentProvider = ProviderObservable.getInstance().getCurrentProvider();
account.setText(currentProvider.getName());
initManualGatewayEntry();
- }
+ }
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- if (key.equals(PREFERRED_CITY)) {
+ if (key != null && key.equals(PREFERRED_CITY)) {
initManualGatewayEntry();
}
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/views/MainButton.java b/app/src/main/java/se/leap/bitmaskclient/base/views/MainButton.java
index fc86fc0b..c7273613 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/views/MainButton.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/views/MainButton.java
@@ -48,10 +48,12 @@ public class MainButton extends RelativeLayout {
public void updateState(boolean isOn, boolean isProcessing) {
if (isProcessing) {
button.setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.button_circle_cancel));
+ button.setTag("button_circle_cancel");
} else {
button.setImageDrawable(
ContextCompat.getDrawable(getContext(),
isOn ? R.drawable.button_circle_stop : R.drawable.button_circle_start));
+ button.setTag(isOn ? "button_circle_stop" : "button_circle_start");
}
}
}
diff --git a/app/src/sharedTest/java/se.leap.bitmaskclient/testutils/TestSetupHelper.java b/app/src/test/java/se/leap/bitmaskclient/testutils/TestSetupHelper.java
index 72791ba6..72791ba6 100644
--- a/app/src/sharedTest/java/se.leap.bitmaskclient/testutils/TestSetupHelper.java
+++ b/app/src/test/java/se/leap/bitmaskclient/testutils/TestSetupHelper.java
diff --git a/app/src/test/resources/v4/riseup.net.cert b/app/src/test/resources/v4/riseup.net.cert
index 1beff4bd..2708a8c8 100644
--- a/app/src/test/resources/v4/riseup.net.cert
+++ b/app/src/test/resources/v4/riseup.net.cert
@@ -1,54 +1,54 @@
-----BEGIN RSA PRIVATE KEY-----
-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
+MIIEpAIBAAKCAQEA29qrZxVQz0crl9USbBpzZCMfbDno8tZVjI/imqLgdfgmaW2D
+5YEmDMPrr3GEqVBKAYT9LHRgmTSi63PW3N8gb9CKMdoQtSL35XcLbXgtdsJEzSSy
+CbtXmB4Yrp2NhzMS9qhFAjbLjjnZkaECLmZDka5dy6j6JWjRNlpE4umqGSCzBtaA
+IXuUjMF3hyZ9kY2ojdRDSo3I/gebo2vwaUj/lwYbs1lkDf0LVCcwmpLkppxnf+Cg
+T8MA9HRvOk8TZJpShAPWWOBNWVjh79OF41MCbIAr9ynrNR8grOTJV57q1V04/Gi+
+Ue0BkvxsGtNGxTrS4lC672a3Z8nW/NCuMnVTHwIDAQABAoIBAQC8jGuFK32zVlkn
+jL+Q4JpnncucGIoUgQa7VsbDUb5ozdm7fwWn9Tu5pOji/NsGDep6JSCvWFtj6QV0
+IlN59w2td06ddGPxxLyPGao+RtvOxssUmEzsFbQIrH8EefBfq8iuqx8LyAyIvEpA
+H7JsMp3uOXkNaaymGp+aGo6LgFO12YMNq4OzGeyvetqwkFNkowYOLzAXa99MruZA
+xl2lJ4Pm3P7sQspUOxaWrSUYImcg0yM9vpDPxn46f1N3voNGXc2v0okizCOIyrk9
+GF8BHZra8v5oinReCD0by7Xg4X2OfMuOV46lN/TlZ5kmNmsPxORMpuu4NkhAyGRj
+dPEU/rHxAoGBAPcTKQk3HZoOf+z/ax7OpLvt+qkWuQrKzjKa+AfiE6cqiLG3oN9I
+5qhZYI9vFszEw0Ak5Oouc2EWv1/m0Um/w8CQdbeH0AvxQZDKKLdothDwxokVB0fQ
+Ish34lZyHrccsF96GzZMmepjYqiaebsv0ptkn1a0Umc87ao90UdDUL6nAoGBAOPL
+yNLADh3LlY2FUVztDG4We2d6Wjr+SI9H4SShXZhz6067I+TwWeXl4mtPAUhAr6ex
+/TLE+cRvq8SxDPiKSu4h2UKm5h0HMSpCbFQaqOnPgYihwSTm8/C9uKr2e9mqNSsw
+VJYyO1Gbrsbb7NOZFmm9uivg9X5aMwF/1wWQGY7JAoGABoDVmq19tPleuqE6c5Qi
+1+N6roqvki4mYUSc9LAprkO7V1oq/NWRZKr9lKjq47bmIMEX2WYhmVOc8+xCY/uN
+Lnte7dbATiAqhqIbkkBKUoXT4/XOvEApOjeVmIrmbhFuPwUaxEId5wJ4rVFrlNa8
+Z2StoP2cEaWT5+A6qvKFpI8CgYEAx/N6pbM7MOAguAaL8puIy6EkVSJKzXmiy1H2
+yCZ0d3tY0tTlnvFyl5//7N1+bKOLDBHqBIRuEQVMquwWTJtnRjuj7yN83YIQn92K
+JRD5r7IbK4mAdhnbijeeP0L4V4lV/kEAHo6dDvcupRMqgFniGJMXNajTFEOsfeZv
+IUzpgjECgYAj4L4boXvCmZIkI46MU9JxQk0eHusR2uGwpdrMiokIsz7uN+MogK1U
+1ijIZp/tDH9W1K4t2sTrmRXBee8bZ6iIvj5qPFJqmj+uil20UbyCPQvpAxHn+VNt
+gUOLvqlGa2uQ9aymKjnG9bmg7Uk8qXIFsQf2DUYLwrmJjsF6cI2xig==
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
-MIIEmzCCAoOgAwIBAgIQejc1yqfKGehUKDMdybDH+DANBgkqhkiG9w0BAQsFADB1
+MIIEmzCCAoOgAwIBAgIQcAUo8ZeIKRBNyIAyEg7QFDANBgkqhkiG9w0BAQsFADB1
MRgwFgYDVQQKDA9SaXNldXAgTmV0d29ya3MxGzAZBgNVBAsMEmh0dHBzOi8vcmlz
ZXVwLm5ldDE8MDoGA1UEAwwzUmlzZXVwIE5ldHdvcmtzIFJvb3QgQ0EgKGNsaWVu
-dCBjZXJ0aWZpY2F0ZXMgb25seSEpMB4XDTIyMTAxNDAwMDAwMFoXDTIzMDExNDAw
-MDAwMFowLTErMCkGA1UEAwwiVU5MSU1JVEVEMTMxaXF4cHB0cHljYnN3eGRueXRu
-aHhiMDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkpHeVGyofh21kh
-+tNzCTYEpKJngZz6KBNHZcWFyDkGvx505efuwleOyQ1oxVko9Yfs3gJl3IJEMQ0R
-da22fqKL9ukmMhop4AUIJ/ar6AmcCHXrBDeV4Xb7dtxSvHnWpwjDf7Z9Qp8mjucb
-x3Vkb4aZhH/cwLFcKPan8db5kbCX873Jx6x6AoB958cytVWZucTRkXQJvYwNqHWG
-CLkijdYUmII3J11bzR5t21rPgeEc4La2Rr8vr7EknegGmBkOLAo9wzrijTlql2fM
-haX424tK2LDps8RBYmYnqrsYa2n0jPtrsjW3T7D1yndL3mYdQKQxB1v+iPqj9G5x
-xOg7a/sCAwEAAaNvMG0wHQYDVR0OBBYEFEFB2IaA9Z5TOAPtMw3vgKvRR+tpMAsG
+dCBjZXJ0aWZpY2F0ZXMgb25seSEpMB4XDTIyMTIyMDAwMDAwMFoXDTIzMDMyMDAw
+MDAwMFowLTErMCkGA1UEAwwiVU5MSU1JVEVEYnpnMWZicmd3MWdsa29qMWlhZG4w
+d2ZiaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANvaq2cVUM9HK5fV
+Emwac2QjH2w56PLWVYyP4pqi4HX4Jmltg+WBJgzD669xhKlQSgGE/Sx0YJk0outz
+1tzfIG/QijHaELUi9+V3C214LXbCRM0ksgm7V5geGK6djYczEvaoRQI2y4452ZGh
+Ai5mQ5GuXcuo+iVo0TZaROLpqhkgswbWgCF7lIzBd4cmfZGNqI3UQ0qNyP4Hm6Nr
+8GlI/5cGG7NZZA39C1QnMJqS5KacZ3/goE/DAPR0bzpPE2SaUoQD1ljgTVlY4e/T
+heNTAmyAK/cp6zUfIKzkyVee6tVdOPxovlHtAZL8bBrTRsU60uJQuu9mt2fJ1vzQ
+rjJ1Ux8CAwEAAaNvMG0wHQYDVR0OBBYEFF1cHd5fjHXKbGrW0qd4zNg6KquiMAsG
A1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjAJBgNVHRMEAjAAMB8GA1Ud
-IwQYMBaAFBf0G9XlKgEBTWuiXTYKKQmWZYBGMA0GCSqGSIb3DQEBCwUAA4ICAQB0
-x+RhbfmWuKQ7clT2oBhLOep1USJ9y98BLMsEmqZ84q0CnG+tI2QLhZvaMiGiMplI
-saHhK1lIHbO/UATHOhE+ZbY9vAaGK1JaBdlOTshjo+cijZ2nWwfwa9Qj/Cx64sSO
-cee3Lbg5IcsFc/0KGCTw0k9PheD/CudRSnLCxImA8DmJO+1w1NUoBBm48tdD6usD
-fLU35FvChvNwW/7GO/q3TGXF/jrreBnrJZMjsffwFOJC8SWZIAScmF/OrqWyruYb
-ZS9/0ZXR21TT1mvUFjIs+0uY2crfli/f91ZbB9eRoiyHkck8RtotiH9PrmYt7j02
-5cwos2BqRs+kZ7AgYvZjWErwIyCsXSzrk0WztLa2vslqZSZSt12VlW5iIyKTyx8T
-snQLEdHwzRT5C+9daq3PneOvD5KhgyySlwWSoMuJdg2n3cTs/0uMDQE7FIim11XM
-Fz8liZf4PcWv8YLFUBHQ367SvbAiyZLzpXZETQyDaYNYGTwYBCGs9Yhq3KM85Kmz
-f0rgrBpm61ujLQ/OBSCq6/RA9BN8UYdO+YS0vengYtIdc+aw1PzLhcC9dLLXC4ef
-zH25zfe7vkt58UcuG+YCsaIR8Hy3I/yDQWVkbNawEYUboEHHRd8pQmUIkHLaYibt
-eq1SXtuulUUDIkyfRATiA/GkPVPPnJImL8XK12WFPQ==
------END CERTIFICATE-----
+IwQYMBaAFBf0G9XlKgEBTWuiXTYKKQmWZYBGMA0GCSqGSIb3DQEBCwUAA4ICAQCS
+sDwNJmNqgrOeNgEuszIGE5V2wTj89S2TtHggBfWErKGUn7N0+gcIRvxxCHG9X7ne
+WjhTS/kEMblR3OcaB/8kfBGCZD6Hc+d0nXv7G5Iqpy5eJuWnv8L4n7QUxIkYiyRr
+bp3Mg31MCFKlDVi2RoTbm3rvcWYPsfBARu6tZlvfNb2Q6dP9/zPU1N2b69fyNwXV
+IOjF/5KPt4n/abxyFuty6xv1G9B3Bq072wZrUSQbZsOLNUtSyLJVyhHRLBBzOOJ/
+Cw8awAEeXXwF1+qAO4MAq/ILcA/v8hAR6AlZez6nbZOBfk5JvEPumZPdIQN6PQ6J
+jODFuJRFFemea3uYyUEnv5MD3BcsApIrM7eRc82Edc9a73CkuMP4ekG68OFl2qHl
+h+XzEchQNVG3t+Ka+8FN1RyeXhPRjzf1JG0elP+CIuyHBOd1SJLv4elGK2ZJWk2L
+cPfLt+tFATKy3PEuftXasLG9g/K8PVaNh8gmh4en88vM0XxZ7npygPv84Cl5wV4r
+LUuR6Cy8quE8QHx01D2EAspkHqk4d/xClyxfT1KCKEQvOk5gOlSrEFsB8TTqtYfr
+G1LiVDcwEq+ExmeDwGIW0EY2mOre7yTHva0p0JZ4AcyK4lNIPXpGBOQcyZ++7/jh
+vdNyERG7ZAjCsj7YTy72On5cS6DQDEHnKmrAIejLLw==
+-----END CERTIFICATE----- \ No newline at end of file