From 8a768cf933cda6fec6fd657270a06f27273638bd Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 25 Mar 2021 13:55:46 -0300 Subject: [PATCH] Add a test for sign-in This requires an account to exist before as we can't restart our lifecycle. So we don't automatically clear app data after each test, but rather need to delete an existing account manually before each test. --- briar-android/build.gradle | 2 +- .../briarproject/briar/android/UiTest.java | 19 +++--- .../briar/android/ViewActions.java | 42 ++++++++++-- .../briar/android/BriarUiTestComponent.java | 6 ++ .../account/SignInTestCreateAccount.java | 65 +++++++++++++++++++ .../android/account/SignInTestSignIn.java | 59 +++++++++++++++++ .../briar/android/PromoVideoTest.java | 27 ++++++-- .../briar/android/SetupDataTest.java | 1 + 8 files changed, 197 insertions(+), 24 deletions(-) create mode 100644 briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/account/SignInTestCreateAccount.java create mode 100644 briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/account/SignInTestSignIn.java diff --git a/briar-android/build.gradle b/briar-android/build.gradle index b6a253871..13fcd1969 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -37,7 +37,7 @@ android { buildConfigField "Long", "BuildTimestamp", "${getStdout(['git', 'log', '-n', '1', '--format=%ct'], now)}000L" testInstrumentationRunner 'org.briarproject.briar.android.BriarTestRunner' - testInstrumentationRunnerArguments disableAnalytics: 'true', clearPackageData: 'true' + testInstrumentationRunnerArguments disableAnalytics: 'true' } buildTypes { diff --git a/briar-android/src/androidTest/java/org/briarproject/briar/android/UiTest.java b/briar-android/src/androidTest/java/org/briarproject/briar/android/UiTest.java index 63691223d..5c78c3043 100644 --- a/briar-android/src/androidTest/java/org/briarproject/briar/android/UiTest.java +++ b/briar-android/src/androidTest/java/org/briarproject/briar/android/UiTest.java @@ -4,10 +4,8 @@ import android.app.Activity; import android.content.Intent; import org.briarproject.bramble.api.account.AccountManager; -import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.briar.R; import org.junit.ClassRule; @@ -16,9 +14,8 @@ import javax.inject.Inject; import androidx.test.espresso.intent.rule.IntentsTestRule; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static androidx.test.core.app.ApplicationProvider.getApplicationContext; -import static org.briarproject.briar.android.controller.BriarControllerImpl.DOZE_ASK_AGAIN; -import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; @SuppressWarnings("WeakerAccess") @@ -46,6 +43,12 @@ public abstract class UiTest { protected abstract void inject(BriarUiTestComponent component); + protected void startActivity(Class clazz) { + Intent i = new Intent(getApplicationContext(), clazz); + i.addFlags(FLAG_ACTIVITY_NEW_TASK); + getApplicationContext().startActivity(i); + } + @NotNullByDefault protected class CleanAccountTestRule extends IntentsTestRule { @@ -57,18 +60,14 @@ public abstract class UiTest { @Override protected void beforeActivityLaunched() { super.beforeActivityLaunched(); - // Android Test Orchestrator already clears existing accounts + accountManager.deleteAccount(); accountManager.createAccount(USERNAME, PASSWORD); Intent serviceIntent = new Intent(getApplicationContext(), BriarService.class); getApplicationContext().startService(serviceIntent); try { lifecycleManager.waitForStartup(); - // do not show doze white-listing dialog - Settings settings = new Settings(); - settings.putBoolean(DOZE_ASK_AGAIN, false); - settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE); - } catch (InterruptedException | DbException e) { + } catch (InterruptedException e) { throw new AssertionError(e); } } diff --git a/briar-android/src/androidTest/java/org/briarproject/briar/android/ViewActions.java b/briar-android/src/androidTest/java/org/briarproject/briar/android/ViewActions.java index a1d631e81..1154272ca 100644 --- a/briar-android/src/androidTest/java/org/briarproject/briar/android/ViewActions.java +++ b/briar-android/src/androidTest/java/org/briarproject/briar/android/ViewActions.java @@ -1,6 +1,7 @@ package org.briarproject.briar.android; import android.app.Activity; +import android.util.Log; import android.view.View; import org.hamcrest.Matcher; @@ -20,6 +21,7 @@ import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.isRoot; import static androidx.test.espresso.util.HumanReadables.describe; import static androidx.test.espresso.util.TreeIterables.breadthFirstViewTraversal; +import static androidx.test.runner.lifecycle.Stage.RESUMED; import static java.lang.System.currentTimeMillis; import static java.util.concurrent.TimeUnit.SECONDS; @@ -32,13 +34,22 @@ public class ViewActions { onView(isRoot()).perform(waitUntilMatches(hasDescendant(viewMatcher))); } + public static void waitFor(final Class clazz) { + onView(isRoot()).perform(waitForActivity(clazz, RESUMED, TIMEOUT_MS)); + } + + public static void waitFor(final Class clazz, + long timeout) { + onView(isRoot()).perform(waitForActivity(clazz, RESUMED, timeout)); + } + public static ViewAction waitUntilMatches(Matcher viewMatcher) { return waitUntilMatches(viewMatcher, TIMEOUT_MS); } private static ViewAction waitUntilMatches(Matcher viewMatcher, long timeout) { - return new CustomViewAction() { + return new CustomViewAction(timeout) { @Override protected boolean exitConditionTrue(View view) { for (View child : breadthFirstViewTraversal(view)) { @@ -55,24 +66,43 @@ public class ViewActions { }; } - public static ViewAction waitForActivity(Activity activity, Stage stage) { + public static ViewAction waitForActivity(Class clazz, + Stage stage, long timeout) { return new CustomViewAction() { @Override protected boolean exitConditionTrue(View view) { + boolean found = false; ActivityLifecycleMonitor lifecycleMonitor = ActivityLifecycleMonitorRegistry.getInstance(); - return lifecycleMonitor.getLifecycleStageOf(activity) == stage; + for (Activity a : lifecycleMonitor + .getActivitiesInStage(stage)) { + Log.e("TEST", a.getClass().getSimpleName() + + " is in state " + stage); + if (a.getClass().equals(clazz)) found = true; + } + return found; } @Override public String getDescription() { - return "Wait for activity " + activity.getClass().getName() + - " to resume within " + TIMEOUT_MS + " milliseconds."; + return "Wait for activity " + clazz.getName() + " in stage " + + stage.name() + " within " + timeout + + " milliseconds."; } }; } private static abstract class CustomViewAction implements ViewAction { + private final long timeout; + + public CustomViewAction() { + this(TIMEOUT_MS); + } + + public CustomViewAction(long timeout) { + this.timeout = timeout; + } + @Override public Matcher getConstraints() { return isDisplayed(); @@ -81,7 +111,7 @@ public class ViewActions { @Override public void perform(UiController uiController, View view) { uiController.loopMainThreadUntilIdle(); - long endTime = currentTimeMillis() + TIMEOUT_MS; + long endTime = currentTimeMillis() + timeout; do { if (exitConditionTrue(view)) return; uiController.loopMainThreadForAtLeast(WAIT_MS); diff --git a/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/BriarUiTestComponent.java b/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/BriarUiTestComponent.java index e8c81393f..f4b7a6dee 100644 --- a/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/BriarUiTestComponent.java +++ b/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/BriarUiTestComponent.java @@ -4,6 +4,8 @@ import org.briarproject.bramble.BrambleAndroidModule; import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.bramble.account.BriarAccountModule; import org.briarproject.briar.BriarCoreModule; +import org.briarproject.briar.android.account.SignInTestCreateAccount; +import org.briarproject.briar.android.account.SignInTestSignIn; import org.briarproject.briar.android.attachment.AttachmentModule; import org.briarproject.briar.android.attachment.media.MediaModule; import org.briarproject.briar.android.navdrawer.NavDrawerActivityTest; @@ -26,4 +28,8 @@ public interface BriarUiTestComponent extends AndroidComponent { void inject(NavDrawerActivityTest test); + void inject(SignInTestCreateAccount test); + + void inject(SignInTestSignIn test); + } diff --git a/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/account/SignInTestCreateAccount.java b/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/account/SignInTestCreateAccount.java new file mode 100644 index 000000000..a66583428 --- /dev/null +++ b/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/account/SignInTestCreateAccount.java @@ -0,0 +1,65 @@ +package org.briarproject.briar.android.account; + +import android.view.Gravity; + +import org.briarproject.briar.R; +import org.briarproject.briar.android.BriarUiTestComponent; +import org.briarproject.briar.android.UiTest; +import org.briarproject.briar.android.navdrawer.NavDrawerActivity; +import org.briarproject.briar.android.splash.SplashScreenActivity; +import org.junit.Test; +import org.junit.runner.RunWith; + +import androidx.test.espresso.contrib.DrawerActions; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +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.ViewMatchers.hasDescendant; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.isRoot; +import static androidx.test.espresso.matcher.ViewMatchers.withClassName; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static org.briarproject.briar.android.ViewActions.waitFor; +import static org.briarproject.briar.android.ViewActions.waitUntilMatches; +import static org.hamcrest.Matchers.endsWith; + +@RunWith(AndroidJUnit4.class) +public class SignInTestCreateAccount extends UiTest { + + @Override + protected void inject(BriarUiTestComponent component) { + component.inject(this); + } + + @Test + public void createAccount() throws Exception { + accountManager.deleteAccount(); + accountManager.createAccount(USERNAME, PASSWORD); + + startActivity(SplashScreenActivity.class); + lifecycleManager.waitForStartup(); + waitFor(NavDrawerActivity.class); + + // open nav drawer + onView(withId(R.id.drawer_layout)) + .check(matches(isClosed(Gravity.START))) + .perform(DrawerActions.open()); + + // click onboarding away (once shown) + onView(isRoot()).perform(waitUntilMatches(hasDescendant( + withClassName(endsWith("PromptView"))))); + onView(withClassName(endsWith("PromptView"))) + .perform(click()); + + // sign-out manually + onView(withText(R.string.sign_out_button)) + .check(matches(isDisplayed())) + .perform(click()); + lifecycleManager.waitForShutdown(); + } + +} diff --git a/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/account/SignInTestSignIn.java b/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/account/SignInTestSignIn.java new file mode 100644 index 000000000..30bb9aaad --- /dev/null +++ b/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/account/SignInTestSignIn.java @@ -0,0 +1,59 @@ +package org.briarproject.briar.android.account; + +import android.view.Gravity; + +import org.briarproject.briar.R; +import org.briarproject.briar.android.BriarUiTestComponent; +import org.briarproject.briar.android.UiTest; +import org.briarproject.briar.android.login.StartupActivity; +import org.briarproject.briar.android.navdrawer.NavDrawerActivity; +import org.briarproject.briar.android.splash.SplashScreenActivity; +import org.junit.Test; +import org.junit.runner.RunWith; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.action.ViewActions.replaceText; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.contrib.DrawerMatchers.isClosed; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.isEnabled; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static org.briarproject.briar.android.ViewActions.waitFor; +import static org.hamcrest.CoreMatchers.allOf; + +/** + * This relies on class sorting to run after {@link SignInTestCreateAccount}. + */ +@RunWith(AndroidJUnit4.class) +public class SignInTestSignIn extends UiTest { + + @Override + protected void inject(BriarUiTestComponent component) { + component.inject(this); + } + + @Test + public void signIn() throws Exception { + startActivity(SplashScreenActivity.class); + + waitFor(StartupActivity.class); + + // enter password + onView(withId(R.id.edit_password)) + .check(matches(isDisplayed())) + .perform(replaceText(PASSWORD)); + onView(withId(R.id.btn_sign_in)) + .check(matches(allOf(isDisplayed(), isEnabled()))) + .perform(click()); + + lifecycleManager.waitForStartup(); + waitFor(NavDrawerActivity.class); + + // ensure nav drawer is visible + onView(withId(R.id.drawer_layout)) + .check(matches(isClosed(Gravity.START))); + } +} diff --git a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/PromoVideoTest.java b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/PromoVideoTest.java index 87b69cb4a..0c8034a7f 100644 --- a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/PromoVideoTest.java +++ b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/PromoVideoTest.java @@ -8,6 +8,9 @@ import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.PendingContact; import org.briarproject.bramble.api.contact.PendingContactState; import org.briarproject.briar.R; +import org.briarproject.briar.android.account.SetupActivity; +import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity; +import org.briarproject.briar.android.navdrawer.NavDrawerActivity; import org.briarproject.briar.android.splash.SplashScreenActivity; import org.hamcrest.Matcher; import org.junit.Rule; @@ -66,6 +69,7 @@ public class PromoVideoTest extends ScreenshotTest { @Override protected void inject(BriarUiTestComponent component) { component.inject(this); + accountManager.deleteAccount(); } @Test @@ -80,9 +84,8 @@ public class PromoVideoTest extends ScreenshotTest { onView(withId(R.id.logoView)) .perform(waitUntilMatches(isDisplayed())); - int duration = getApplicationContext().getResources() - .getInteger(R.integer.splashScreenDuration); - sleep(Math.max(DELAY_LONG, duration)); + if (debug) waitFor(SetupActivity.class, 25_000); + sleep(DELAY_LONG); // Enter username onView(withText(R.string.setup_title)) @@ -132,7 +135,8 @@ public class PromoVideoTest extends ScreenshotTest { sleep(DELAY_SMALL); - waitFor(allOf(withId(R.id.speedDial), isDisplayed())); + // wait for contact list to be shown + if (debug) waitFor(NavDrawerActivity.class); // clicking the FAB doesn't work, so we click its inner FAB as well onView(withId(R.id.speedDial)) @@ -173,8 +177,11 @@ public class PromoVideoTest extends ScreenshotTest { sleep(DELAY_LONG); // wait for pending contact list activity to be shown - waitFor(allOf(withText(R.string.pending_contact_requests), - isDisplayed())); + if (debug) { + waitFor(PendingContactListActivity.class); + waitFor(allOf(withText(R.string.pending_contact_requests), + isDisplayed())); + } // remove pending contact for (Pair p : contactManager @@ -187,8 +194,14 @@ public class PromoVideoTest extends ScreenshotTest { connectionRegistry.registerIncomingConnection(bob.getId(), ID, () -> { }); + sleep(DELAY_LONG); + // wait for contact list to be shown - waitFor(allOf(withText(R.string.contact_list_button), isDisplayed())); + if (debug) { + waitFor(NavDrawerActivity.class); + waitFor(allOf(withText(R.string.contact_list_button), + isDisplayed())); + } // click on new contact doItemClick(withId(R.id.recyclerView), 0); diff --git a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/SetupDataTest.java b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/SetupDataTest.java index 4faaf99f1..b0d3f6a4d 100644 --- a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/SetupDataTest.java +++ b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/SetupDataTest.java @@ -42,6 +42,7 @@ public class SetupDataTest extends ScreenshotTest { @Override protected void inject(BriarUiTestComponent component) { component.inject(this); + accountManager.deleteAccount(); } @Test