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",
"${getStdout(['git', 'log', '-n', '1', '--format=%ct'], now)}000L"
testInstrumentationRunner 'org.briarproject.briar.android.BriarTestRunner'
testInstrumentationRunnerArguments disableAnalytics: 'true', clearPackageData: 'true'
testInstrumentationRunnerArguments disableAnalytics: 'true'
}
buildTypes {

View File

@@ -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<? extends Activity> clazz) {
Intent i = new Intent(getApplicationContext(), clazz);
i.addFlags(FLAG_ACTIVITY_NEW_TASK);
getApplicationContext().startActivity(i);
}
@NotNullByDefault
protected class CleanAccountTestRule<A extends Activity>
extends IntentsTestRule<A> {
@@ -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);
}
}

View File

@@ -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<? 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) {
return waitUntilMatches(viewMatcher, TIMEOUT_MS);
}
private static ViewAction waitUntilMatches(Matcher<View> 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<? extends Activity> 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<View> 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);

View File

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

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.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<PendingContact, PendingContactState> 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);

View File

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