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.
This commit is contained in:
Torsten Grote
2021-03-25 13:55:46 -03:00
parent 8a3dd5472b
commit 8a768cf933
8 changed files with 197 additions and 24 deletions

View File

@@ -37,7 +37,7 @@ android {
buildConfigField "Long", "BuildTimestamp", buildConfigField "Long", "BuildTimestamp",
"${getStdout(['git', 'log', '-n', '1', '--format=%ct'], now)}000L" "${getStdout(['git', 'log', '-n', '1', '--format=%ct'], now)}000L"
testInstrumentationRunner 'org.briarproject.briar.android.BriarTestRunner' testInstrumentationRunner 'org.briarproject.briar.android.BriarTestRunner'
testInstrumentationRunnerArguments disableAnalytics: 'true', clearPackageData: 'true' testInstrumentationRunnerArguments disableAnalytics: 'true'
} }
buildTypes { buildTypes {

View File

@@ -4,10 +4,8 @@ import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import org.briarproject.bramble.api.account.AccountManager; 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.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.junit.ClassRule; import org.junit.ClassRule;
@@ -16,9 +14,8 @@ import javax.inject.Inject;
import androidx.test.espresso.intent.rule.IntentsTestRule; 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 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") @SuppressWarnings("WeakerAccess")
@@ -46,6 +43,12 @@ public abstract class UiTest {
protected abstract void inject(BriarUiTestComponent component); protected abstract void inject(BriarUiTestComponent component);
protected void startActivity(Class<? extends Activity> clazz) {
Intent i = new Intent(getApplicationContext(), clazz);
i.addFlags(FLAG_ACTIVITY_NEW_TASK);
getApplicationContext().startActivity(i);
}
@NotNullByDefault @NotNullByDefault
protected class CleanAccountTestRule<A extends Activity> protected class CleanAccountTestRule<A extends Activity>
extends IntentsTestRule<A> { extends IntentsTestRule<A> {
@@ -57,18 +60,14 @@ public abstract class UiTest {
@Override @Override
protected void beforeActivityLaunched() { protected void beforeActivityLaunched() {
super.beforeActivityLaunched(); super.beforeActivityLaunched();
// Android Test Orchestrator already clears existing accounts accountManager.deleteAccount();
accountManager.createAccount(USERNAME, PASSWORD); accountManager.createAccount(USERNAME, PASSWORD);
Intent serviceIntent = Intent serviceIntent =
new Intent(getApplicationContext(), BriarService.class); new Intent(getApplicationContext(), BriarService.class);
getApplicationContext().startService(serviceIntent); getApplicationContext().startService(serviceIntent);
try { try {
lifecycleManager.waitForStartup(); lifecycleManager.waitForStartup();
// do not show doze white-listing dialog } catch (InterruptedException e) {
Settings settings = new Settings();
settings.putBoolean(DOZE_ASK_AGAIN, false);
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
} catch (InterruptedException | DbException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }
} }

View File

@@ -1,6 +1,7 @@
package org.briarproject.briar.android; package org.briarproject.briar.android;
import android.app.Activity; import android.app.Activity;
import android.util.Log;
import android.view.View; import android.view.View;
import org.hamcrest.Matcher; 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.matcher.ViewMatchers.isRoot;
import static androidx.test.espresso.util.HumanReadables.describe; import static androidx.test.espresso.util.HumanReadables.describe;
import static androidx.test.espresso.util.TreeIterables.breadthFirstViewTraversal; 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.lang.System.currentTimeMillis;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
@@ -32,13 +34,22 @@ public class ViewActions {
onView(isRoot()).perform(waitUntilMatches(hasDescendant(viewMatcher))); onView(isRoot()).perform(waitUntilMatches(hasDescendant(viewMatcher)));
} }
public static void waitFor(final Class<? extends Activity> clazz) {
onView(isRoot()).perform(waitForActivity(clazz, RESUMED, TIMEOUT_MS));
}
public static void waitFor(final Class<? extends Activity> clazz,
long timeout) {
onView(isRoot()).perform(waitForActivity(clazz, RESUMED, timeout));
}
public static ViewAction waitUntilMatches(Matcher<View> viewMatcher) { public static ViewAction waitUntilMatches(Matcher<View> viewMatcher) {
return waitUntilMatches(viewMatcher, TIMEOUT_MS); return waitUntilMatches(viewMatcher, TIMEOUT_MS);
} }
private static ViewAction waitUntilMatches(Matcher<View> viewMatcher, private static ViewAction waitUntilMatches(Matcher<View> viewMatcher,
long timeout) { long timeout) {
return new CustomViewAction() { return new CustomViewAction(timeout) {
@Override @Override
protected boolean exitConditionTrue(View view) { protected boolean exitConditionTrue(View view) {
for (View child : breadthFirstViewTraversal(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<? extends Activity> clazz,
Stage stage, long timeout) {
return new CustomViewAction() { return new CustomViewAction() {
@Override @Override
protected boolean exitConditionTrue(View view) { protected boolean exitConditionTrue(View view) {
boolean found = false;
ActivityLifecycleMonitor lifecycleMonitor = ActivityLifecycleMonitor lifecycleMonitor =
ActivityLifecycleMonitorRegistry.getInstance(); 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 @Override
public String getDescription() { public String getDescription() {
return "Wait for activity " + activity.getClass().getName() + return "Wait for activity " + clazz.getName() + " in stage " +
" to resume within " + TIMEOUT_MS + " milliseconds."; stage.name() + " within " + timeout +
" milliseconds.";
} }
}; };
} }
private static abstract class CustomViewAction implements ViewAction { private static abstract class CustomViewAction implements ViewAction {
private final long timeout;
public CustomViewAction() {
this(TIMEOUT_MS);
}
public CustomViewAction(long timeout) {
this.timeout = timeout;
}
@Override @Override
public Matcher<View> getConstraints() { public Matcher<View> getConstraints() {
return isDisplayed(); return isDisplayed();
@@ -81,7 +111,7 @@ public class ViewActions {
@Override @Override
public void perform(UiController uiController, View view) { public void perform(UiController uiController, View view) {
uiController.loopMainThreadUntilIdle(); uiController.loopMainThreadUntilIdle();
long endTime = currentTimeMillis() + TIMEOUT_MS; long endTime = currentTimeMillis() + timeout;
do { do {
if (exitConditionTrue(view)) return; if (exitConditionTrue(view)) return;
uiController.loopMainThreadForAtLeast(WAIT_MS); uiController.loopMainThreadForAtLeast(WAIT_MS);

View File

@@ -4,6 +4,8 @@ import org.briarproject.bramble.BrambleAndroidModule;
import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.account.BriarAccountModule; import org.briarproject.bramble.account.BriarAccountModule;
import org.briarproject.briar.BriarCoreModule; 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.AttachmentModule;
import org.briarproject.briar.android.attachment.media.MediaModule; import org.briarproject.briar.android.attachment.media.MediaModule;
import org.briarproject.briar.android.navdrawer.NavDrawerActivityTest; import org.briarproject.briar.android.navdrawer.NavDrawerActivityTest;
@@ -26,4 +28,8 @@ public interface BriarUiTestComponent extends AndroidComponent {
void inject(NavDrawerActivityTest test); void inject(NavDrawerActivityTest test);
void inject(SignInTestCreateAccount test);
void inject(SignInTestSignIn test);
} }

View File

@@ -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();
}
}

View File

@@ -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)));
}
}

View File

@@ -8,6 +8,9 @@ import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.PendingContact; import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.contact.PendingContactState; import org.briarproject.bramble.api.contact.PendingContactState;
import org.briarproject.briar.R; 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.briarproject.briar.android.splash.SplashScreenActivity;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
import org.junit.Rule; import org.junit.Rule;
@@ -66,6 +69,7 @@ public class PromoVideoTest extends ScreenshotTest {
@Override @Override
protected void inject(BriarUiTestComponent component) { protected void inject(BriarUiTestComponent component) {
component.inject(this); component.inject(this);
accountManager.deleteAccount();
} }
@Test @Test
@@ -80,9 +84,8 @@ public class PromoVideoTest extends ScreenshotTest {
onView(withId(R.id.logoView)) onView(withId(R.id.logoView))
.perform(waitUntilMatches(isDisplayed())); .perform(waitUntilMatches(isDisplayed()));
int duration = getApplicationContext().getResources() if (debug) waitFor(SetupActivity.class, 25_000);
.getInteger(R.integer.splashScreenDuration); sleep(DELAY_LONG);
sleep(Math.max(DELAY_LONG, duration));
// Enter username // Enter username
onView(withText(R.string.setup_title)) onView(withText(R.string.setup_title))
@@ -132,7 +135,8 @@ public class PromoVideoTest extends ScreenshotTest {
sleep(DELAY_SMALL); 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 // clicking the FAB doesn't work, so we click its inner FAB as well
onView(withId(R.id.speedDial)) onView(withId(R.id.speedDial))
@@ -173,8 +177,11 @@ public class PromoVideoTest extends ScreenshotTest {
sleep(DELAY_LONG); sleep(DELAY_LONG);
// wait for pending contact list activity to be shown // wait for pending contact list activity to be shown
waitFor(allOf(withText(R.string.pending_contact_requests), if (debug) {
isDisplayed())); waitFor(PendingContactListActivity.class);
waitFor(allOf(withText(R.string.pending_contact_requests),
isDisplayed()));
}
// remove pending contact // remove pending contact
for (Pair<PendingContact, PendingContactState> p : contactManager for (Pair<PendingContact, PendingContactState> p : contactManager
@@ -187,8 +194,14 @@ public class PromoVideoTest extends ScreenshotTest {
connectionRegistry.registerIncomingConnection(bob.getId(), ID, () -> { connectionRegistry.registerIncomingConnection(bob.getId(), ID, () -> {
}); });
sleep(DELAY_LONG);
// wait for contact list to be shown // 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 // click on new contact
doItemClick(withId(R.id.recyclerView), 0); doItemClick(withId(R.id.recyclerView), 0);

View File

@@ -42,6 +42,7 @@ public class SetupDataTest extends ScreenshotTest {
@Override @Override
protected void inject(BriarUiTestComponent component) { protected void inject(BriarUiTestComponent component) {
component.inject(this); component.inject(this);
accountManager.deleteAccount();
} }
@Test @Test