diff --git a/app/build.gradle b/app/build.gradle
index 359597a456e6ec9a2d9ab567e2ffb193bbbc6b78..3034b062ea47478702d491f994037e04d0609326 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -307,11 +307,6 @@ android {
 
     test {
       resources.srcDirs += ['src/test/resources']
-      java.srcDirs += ['src/sharedTest/java']
-    }
-
-    androidTest {
-      java.srcDirs += ['src/sharedTest/java']
     }
 
     fatweb {
diff --git a/app/src/androidTest/java/base/ProviderSetupTest.java b/app/src/androidTest/java/se/leap/bitmaskclient/base/ProviderSetupTest.java
similarity index 72%
rename from app/src/androidTest/java/base/ProviderSetupTest.java
rename to app/src/androidTest/java/se/leap/bitmaskclient/base/ProviderSetupTest.java
index d5a363aa009c0065413e05db44484ab51d6f7793..23db85821c527235350349cad9914704aa26fd62 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 0000000000000000000000000000000000000000..6c99b90ee96f7019a9f088cda2ec10dc2e92eff3
--- /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 0000000000000000000000000000000000000000..186a50d1d6f2fbdd2a28c4eb2fb37e74276f876b
--- /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 0000000000000000000000000000000000000000..896e8d9b0d67c0986bb3e55cea1c37d7702fd57f
--- /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 0000000000000000000000000000000000000000..5fe11cd2c52c8db3affdc5749c8f7d13751ce18b
--- /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 0000000000000000000000000000000000000000..fb0dcc276cda72181939a1ee8ca592e056e46daa
--- /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/main/java/se/leap/bitmaskclient/base/views/MainButton.java b/app/src/main/java/se/leap/bitmaskclient/base/views/MainButton.java
index fc86fc0bc409f1cdde4b27451b1b7b3d6def638a..c72736135c383759816d98e7efdc927330c690f6 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
similarity index 100%
rename from app/src/sharedTest/java/se.leap.bitmaskclient/testutils/TestSetupHelper.java
rename to app/src/test/java/se/leap/bitmaskclient/testutils/TestSetupHelper.java
diff --git a/fastlane/Screengrabfile b/fastlane/Screengrabfile
index 4c46de1a83244883a9b743d8a47dc8283d5963f3..8a149163e25f34c13a833343f053106eed673226 100644
--- a/fastlane/Screengrabfile
+++ b/fastlane/Screengrabfile
@@ -1,10 +1,10 @@
 # remove the leading '#' to uncomment lines
 
 # app_package_name('your.app.package')
-# use_tests_in_packages(['your.screenshot.tests.package'])
+use_tests_in_packages(['se.leap.bitmaskclient.suite'])
 
-app_apk_path('app/build/outputs/apk/normalProductionFat/debug/Bitmask_debug.apk')
-# tests_apk_path('app/build/intermediates/apk/androidTest/normalProductionFat/debug/app-normal-production-fat-debug-androidTest.apk')
+app_apk_path('app/build/intermediates/apk/normalProductionFat/debug/Bitmask_debug.apk')
+tests_apk_path('app/build/intermediates/apk/androidTest/normalProductionFat/debug/app-normal-production-fat-debug-androidTest.apk')
 
 # all locales
 # locales(['ar', 'az', 'bg', 'bn', 'br', 'ca', 'cs', 'de', 'el', 'es', 'es-AR', 'et', 'eu', 'fa-IR', 'fi', 'fr', 'gl', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'my', 'nl', 'no', 'pl', 'pt-BR', 'pt-PT', 'ro', 'ru', 'tr', 'ug', 'uk', 'vi', 'zh-CN', 'zh-TW'])