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