From 4ca286b28ec16ff0a6adbefcfee300537c2b9d71 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 19 Mar 2021 14:24:19 -0300 Subject: [PATCH 01/11] Allow to decide whether test contacts should have alias --- .../briarproject/briar/android/SetupDataTest.java | 2 +- .../briar/api/test/TestDataCreator.java | 3 ++- .../briar/test/TestDataCreatorImpl.java | 13 +++++++------ .../contact/ContactControllerIntegrationTest.kt | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) 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 f40c70aef..04e5a854e 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 @@ -116,7 +116,7 @@ public class SetupDataTest extends ScreenshotTest { throws DbException { Context ctx = getApplicationContext(); String bobName = ctx.getString(R.string.screenshot_bob); - Contact bob = testDataCreator.addContact(bobName, true); + Contact bob = testDataCreator.addContact(bobName, false, true); // TODO add messages diff --git a/briar-api/src/main/java/org/briarproject/briar/api/test/TestDataCreator.java b/briar-api/src/main/java/org/briarproject/briar/api/test/TestDataCreator.java index 0f8ebc2ff..b96100e0f 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/test/TestDataCreator.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/test/TestDataCreator.java @@ -24,5 +24,6 @@ public interface TestDataCreator { int numBlogPosts, int numForums, int numForumPosts); @IoExecutor - Contact addContact(String name, boolean avatar) throws DbException; + Contact addContact(String name, boolean alias, boolean avatar) + throws DbException; } diff --git a/briar-core/src/main/java/org/briarproject/briar/test/TestDataCreatorImpl.java b/briar-core/src/main/java/org/briarproject/briar/test/TestDataCreatorImpl.java index e773d3195..987e4cc28 100644 --- a/briar-core/src/main/java/org/briarproject/briar/test/TestDataCreatorImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/test/TestDataCreatorImpl.java @@ -158,15 +158,15 @@ public class TestDataCreatorImpl implements TestDataCreator { LocalAuthor localAuthor = identityManager.getLocalAuthor(); for (int i = 0; i < numContacts; i++) { LocalAuthor remote = getRandomAuthor(); - Contact contact = - addContact(localAuthor.getId(), remote, avatarPercent); + Contact contact = addContact(localAuthor.getId(), remote, + random.nextBoolean(), avatarPercent); contacts.add(contact); } return contacts; } private Contact addContact(AuthorId localAuthorId, LocalAuthor remote, - int avatarPercent) throws DbException { + boolean alias, int avatarPercent) throws DbException { // prepare to add contact SecretKey secretKey = getSecretKey(); long timestamp = clock.currentTimeMillis(); @@ -179,7 +179,7 @@ public class TestDataCreatorImpl implements TestDataCreator { Contact contact = db.transactionWithResult(false, txn -> { ContactId contactId = contactManager.addContact(txn, remote, localAuthorId, secretKey, timestamp, true, verified, true); - if (random.nextBoolean()) { + if (alias) { contactManager.setContactAlias(txn, contactId, getRandomAuthorName()); } @@ -197,11 +197,12 @@ public class TestDataCreatorImpl implements TestDataCreator { } @Override - public Contact addContact(String name, boolean avatar) throws DbException { + public Contact addContact(String name, boolean alias, boolean avatar) + throws DbException { LocalAuthor localAuthor = identityManager.getLocalAuthor(); LocalAuthor remote = authorFactory.createLocalAuthor(name); int avatarPercent = avatar ? 100 : 0; - return addContact(localAuthor.getId(), remote, avatarPercent); + return addContact(localAuthor.getId(), remote, alias, avatarPercent); } private String getRandomAuthorName() { diff --git a/briar-headless/src/test/java/org/briarproject/briar/headless/contact/ContactControllerIntegrationTest.kt b/briar-headless/src/test/java/org/briarproject/briar/headless/contact/ContactControllerIntegrationTest.kt index ee88dfce1..dca4e336d 100644 --- a/briar-headless/src/test/java/org/briarproject/briar/headless/contact/ContactControllerIntegrationTest.kt +++ b/briar-headless/src/test/java/org/briarproject/briar/headless/contact/ContactControllerIntegrationTest.kt @@ -25,7 +25,7 @@ class ContactControllerIntegrationTest: IntegrationTest() { // add one test contact val testContactName= "testContactName" - testDataCreator.addContact(testContactName, false) + testDataCreator.addContact(testContactName, true, false) // retrieve list with one test contact response = get("$url/contacts") From dcd37f71d1fa9eb5cd2b55123753ad82ef160c6e Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 19 Mar 2021 14:27:10 -0300 Subject: [PATCH 02/11] Turn splash screen duration into a resource variable so screenshot tests can define a different duration --- .../briar/android/splash/SplashScreenActivity.java | 4 +++- briar-android/src/main/res/values/attrs.xml | 2 ++ briar-android/src/screenshotDebug/res/values/attrs.xml | 6 ++++++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 briar-android/src/screenshotDebug/res/values/attrs.xml diff --git a/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java index 85aa5bb42..79e537d29 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/splash/SplashScreenActivity.java @@ -61,6 +61,8 @@ public class SplashScreenActivity extends BaseActivity { startNextActivity(ENTRY_ACTIVITY); finish(); } else { + int duration = + getResources().getInteger(R.integer.splashScreenDuration); new Handler().postDelayed(() -> { if (currentTimeMillis() >= EXPIRY_DATE) { LOG.info("Expired"); @@ -69,7 +71,7 @@ public class SplashScreenActivity extends BaseActivity { startNextActivity(ENTRY_ACTIVITY); } supportFinishAfterTransition(); - }, 500); + }, duration); } } diff --git a/briar-android/src/main/res/values/attrs.xml b/briar-android/src/main/res/values/attrs.xml index 51e0b887f..e02416ad3 100644 --- a/briar-android/src/main/res/values/attrs.xml +++ b/briar-android/src/main/res/values/attrs.xml @@ -3,6 +3,8 @@ @android:integer/config_mediumAnimTime + 500 + diff --git a/briar-android/src/screenshotDebug/res/values/attrs.xml b/briar-android/src/screenshotDebug/res/values/attrs.xml new file mode 100644 index 000000000..e39053ba1 --- /dev/null +++ b/briar-android/src/screenshotDebug/res/values/attrs.xml @@ -0,0 +1,6 @@ + + + + 7500 + + From 31f87f647e7cc3391e0f67ddbdb295bb5ea51802 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 19 Mar 2021 14:27:57 -0300 Subject: [PATCH 03/11] Create an OverlayView so we can show taps in espresso tests --- .../briar/android/OverlayTapViewAction.java | 41 +++++++++ .../briar/android/OverlayView.java | 83 +++++++++++++++++++ .../src/screenshotDebug/AndroidManifest.xml | 3 + 3 files changed, 127 insertions(+) create mode 100644 briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/OverlayTapViewAction.java create mode 100644 briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/OverlayView.java diff --git a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/OverlayTapViewAction.java b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/OverlayTapViewAction.java new file mode 100644 index 000000000..424150f92 --- /dev/null +++ b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/OverlayTapViewAction.java @@ -0,0 +1,41 @@ +package org.briarproject.briar.android; + +import android.view.View; + +import org.hamcrest.Matcher; + +import androidx.test.espresso.UiController; +import androidx.test.espresso.ViewAction; + +import static androidx.test.espresso.action.GeneralLocation.VISIBLE_CENTER; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast; + +public class OverlayTapViewAction implements ViewAction { + + public static ViewAction visualClick(OverlayView overlayView) { + return new OverlayTapViewAction(overlayView); + } + + private final OverlayView overlayView; + + public OverlayTapViewAction(OverlayView overlayView) { + this.overlayView = overlayView; + } + + @Override + public Matcher getConstraints() { + return isDisplayingAtLeast(90); + } + + @Override + public String getDescription() { + return null; + } + + @Override + public void perform(UiController uiController, View view) { + float[] coordinates = VISIBLE_CENTER.calculateCoordinates(view); + overlayView.tap(coordinates); + } +} + diff --git a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/OverlayView.java b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/OverlayView.java new file mode 100644 index 000000000..5ba5417e2 --- /dev/null +++ b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/OverlayView.java @@ -0,0 +1,83 @@ +package org.briarproject.briar.android; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.os.Handler; +import android.view.View; +import android.view.WindowManager; + +import java.util.Random; + +import javax.annotation.Nullable; + +import static android.content.Context.WINDOW_SERVICE; +import static android.provider.Settings.canDrawOverlays; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread; +import static org.junit.Assert.assertTrue; + +public class OverlayView extends View { + + public static OverlayView attach(Context ctx) throws Throwable { + assertTrue(canDrawOverlays(ctx)); + OverlayView view = new OverlayView(getApplicationContext()); + runOnUiThread(() -> attachInternal(ctx, view)); + return view; + } + + private static void attachInternal(Context ctx, OverlayView view) { + WindowManager.LayoutParams params = new WindowManager.LayoutParams( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, + FLAG_NOT_TOUCHABLE | FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + WindowManager wm = (WindowManager) ctx.getSystemService(WINDOW_SERVICE); + wm.addView(view, params); + } + + private final Random random = new Random(); + private final Paint paint; + private final int yOffset; + @Nullable + private float[] coordinates; + + public OverlayView(Context ctx) { + super(ctx); + int resourceId = getResources() + .getIdentifier("status_bar_height", "dimen", "android"); + yOffset = getResources().getDimensionPixelSize(resourceId); + paint = new Paint(); + paint.setAntiAlias(true); + paint.setARGB(175, 255, 0, 0); + setWillNotDraw(false); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + } + + void tap(float[] coordinates) { + this.coordinates = coordinates; + invalidate(); + new Handler().postDelayed(this::untap, 750); + } + + private void untap() { + this.coordinates = null; + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (coordinates == null) return; + float x = coordinates[0] + random.nextInt(42); + float y = coordinates[1] - yOffset + random.nextInt(13); + canvas.drawCircle(x, y, 42, paint); + } +} diff --git a/briar-android/src/screenshotDebug/AndroidManifest.xml b/briar-android/src/screenshotDebug/AndroidManifest.xml index d3e565327..376a33b94 100644 --- a/briar-android/src/screenshotDebug/AndroidManifest.xml +++ b/briar-android/src/screenshotDebug/AndroidManifest.xml @@ -16,4 +16,7 @@ + + + From cdc632e1af920cfc08931fa5fe2eaa569defb543 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 19 Mar 2021 14:28:37 -0300 Subject: [PATCH 04/11] Don't show screen filter and expiry warnings in screenshot tests --- .../org/briarproject/briar/android/activity/BaseActivity.java | 2 ++ .../briar/android/navdrawer/NavDrawerActivity.java | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java index 4f7b0fbde..14a6b9845 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java @@ -44,6 +44,7 @@ import static androidx.lifecycle.Lifecycle.State.STARTED; import static java.util.Collections.emptyList; import static java.util.logging.Level.INFO; import static java.util.logging.Logger.getLogger; +import static org.briarproject.briar.BuildConfig.FLAVOR; import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOTS; import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard; @@ -193,6 +194,7 @@ public abstract class BaseActivity extends AppCompatActivity } private boolean showScreenFilterWarning() { + if (FLAVOR == "screenshot") return false; // If the dialog is already visible, filter the tap ScreenFilterDialogFragment f = findDialogFragment(); if (f != null && f.isVisible()) return false; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java index c16fcd4e1..aa27b6234 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java @@ -71,6 +71,7 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleS import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING; import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; +import static org.briarproject.briar.BuildConfig.FLAVOR; import static org.briarproject.briar.android.BriarService.EXTRA_STARTUP_FAILED; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD; @@ -134,7 +135,7 @@ public class NavDrawerActivity extends BriarActivity implements navDrawerViewModel = provider.get(NavDrawerViewModel.class); pluginViewModel = provider.get(PluginViewModel.class); - if (IS_DEBUG_BUILD) { + if (IS_DEBUG_BUILD && (FLAVOR != "screenshot")) { navDrawerViewModel.showExpiryWarning() .observe(this, this::showExpiryWarning); } From 51624a31e359058dc31dcf750bf504ed802ea803 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 19 Mar 2021 14:29:00 -0300 Subject: [PATCH 05/11] Add a first PromoVideoTest --- .../briar/android/ViewActions.java | 7 + .../briar/android/BriarUiTestComponent.java | 2 + .../briar/android/PromoVideoTest.java | 263 ++++++++++++++++++ briar-android/src/main/res/layout/splash.xml | 1 + 4 files changed, 273 insertions(+) create mode 100644 briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/PromoVideoTest.java 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 dc71aded2..a1d631e81 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 @@ -14,7 +14,10 @@ import androidx.test.runner.lifecycle.ActivityLifecycleMonitor; import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry; import androidx.test.runner.lifecycle.Stage; +import static androidx.test.espresso.Espresso.onView; +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.util.HumanReadables.describe; import static androidx.test.espresso.util.TreeIterables.breadthFirstViewTraversal; import static java.lang.System.currentTimeMillis; @@ -25,6 +28,10 @@ public class ViewActions { private final static long TIMEOUT_MS = SECONDS.toMillis(10); private final static long WAIT_MS = 50; + public static void waitFor(final Matcher viewMatcher) { + onView(isRoot()).perform(waitUntilMatches(hasDescendant(viewMatcher))); + } + public static ViewAction waitUntilMatches(Matcher viewMatcher) { return waitUntilMatches(viewMatcher, TIMEOUT_MS); } diff --git a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/BriarUiTestComponent.java b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/BriarUiTestComponent.java index 9670dd8bc..af6d2ce02 100644 --- a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/BriarUiTestComponent.java +++ b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/BriarUiTestComponent.java @@ -31,4 +31,6 @@ public interface BriarUiTestComponent extends AndroidComponent { void inject(SettingsActivityScreenshotTest test); + void inject(PromoVideoTest test); + } 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 new file mode 100644 index 000000000..f19be7487 --- /dev/null +++ b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/PromoVideoTest.java @@ -0,0 +1,263 @@ +package org.briarproject.briar.android; + +import android.view.View; + +import org.briarproject.bramble.api.Pair; +import org.briarproject.bramble.api.contact.Contact; +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.splash.SplashScreenActivity; +import org.hamcrest.Matcher; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.inject.Inject; + +import androidx.test.ext.junit.rules.ActivityScenarioRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.UiObject; +import androidx.test.uiautomator.UiSelector; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard; +import static androidx.test.espresso.action.ViewActions.replaceText; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition; +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 androidx.test.espresso.matcher.ViewMatchers.withText; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static java.lang.Thread.sleep; +import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID; +import static org.briarproject.briar.android.OverlayTapViewAction.visualClick; +import static org.briarproject.briar.android.ViewActions.waitFor; +import static org.briarproject.briar.android.ViewActions.waitUntilMatches; +import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting; +import static org.hamcrest.CoreMatchers.allOf; +import static org.junit.Assert.assertTrue; + +@RunWith(AndroidJUnit4.class) +public class PromoVideoTest extends ScreenshotTest { + + // we can leave debug to true (to speed up CI) + // and only set it to false when doing recordings + private static final boolean debug = true; + + private static final int DELAY_SMALL = debug ? 0 : 4_000; + private static final int DELAY_MEDIUM = debug ? 0 : 7_500; + private static final int DELAY_LONG = debug ? 0 : 10_000; + + @Rule + public ActivityScenarioRule testRule = + new ActivityScenarioRule<>(SplashScreenActivity.class); + + @Inject + protected ContactManager contactManager; + + private OverlayView overlayView; + + @Override + protected void inject(BriarUiTestComponent component) { + component.inject(this); + accountManager.deleteAccount(); + } + + @Test + public void createAccountAddContact() throws Throwable { + if (!debug) { + // Using this breaks emulator CI tests for some reason. + // Only use it for filming for now until we have time to debug this. + overlayView = OverlayView.attach(getApplicationContext()); + } + + // Splash screen shows logo + onView(withId(R.id.logoView)) + .perform(waitUntilMatches(isDisplayed())); + + int duration = getApplicationContext().getResources() + .getInteger(R.integer.splashScreenDuration); + sleep(Math.max(DELAY_LONG, duration)); + + // Enter username + onView(withText(R.string.setup_title)) + .perform(waitUntilMatches(isDisplayed())); + sleep(DELAY_SMALL); + onView(withId(R.id.nickname_entry)) + .check(matches(isDisplayed())) + .perform(replaceText(USERNAME)); + closeKeyboard(withId(R.id.nickname_entry)); + + sleep(DELAY_SMALL); + + doClick(withId(R.id.next)); + + sleep(DELAY_MEDIUM); + + // Enter password + doClick(withId(R.id.password_entry), 1000); + onView(withId(R.id.password_entry)) + .check(matches(isDisplayed())) + .perform(replaceText(PASSWORD)); + sleep(DELAY_SMALL); + doClick(withId(R.id.password_confirm), 1000); + onView(withId(R.id.password_confirm)) + .check(matches(isDisplayed())) + .perform(replaceText(PASSWORD)); + + sleep(DELAY_SMALL); + // click next or create account + doClick(withId(R.id.next)); + + sleep(DELAY_SMALL); + + // White-list Doze if needed + if (needsDozeWhitelisting(getApplicationContext())) { + doClick(withText(R.string.setup_doze_button)); + UiDevice device = UiDevice.getInstance(getInstrumentation()); + UiObject allowButton = device.findObject( + new UiSelector().className("android.widget.Button") + .index(1)); + allowButton.click(); + doClick(withId(R.id.next)); + } + + lifecycleManager.waitForStartup(); + assertTrue(accountManager.hasDatabaseKey()); + + sleep(DELAY_SMALL); + + waitFor(allOf(withId(R.id.speedDial), isDisplayed())); + + // clicking the FAB doesn't work, so we click its inner FAB as well + onView(withId(R.id.speedDial)) + .check(matches(isDisplayed())) + .perform(click()); + doClick(withId(R.id.fab_main)); // this is inside R.id.speedDial + sleep(DELAY_MEDIUM); + + // click adding contact at a distance menu item + doClick(withText(R.string.add_contact_remotely_title)); + sleep(DELAY_LONG); + + // enter briar:// link + String link = + "briar://ab54fpik6sjyetzjhlwto2fv7tspibx2uhpdnei4tdidkvjpbphvy"; + doClick(withId(R.id.pasteButton)); + onView(withId(R.id.linkInput)) + .perform(waitUntilMatches(isDisplayed())) + .perform(replaceText(link)); + sleep(DELAY_MEDIUM); + + doClick(withId(R.id.addButton)); + sleep(DELAY_MEDIUM); + + // enter contact alias + String contactName = getApplicationContext() + .getString(R.string.screenshot_bob); + doClick(withId(R.id.contactNameInput), 1000); + onView(withId(R.id.contactNameInput)) + .perform(waitUntilMatches(isDisplayed())) + .perform(replaceText(contactName)); + sleep(DELAY_SMALL); + closeKeyboard(withId(R.id.contactNameInput)); + sleep(DELAY_SMALL); + + // add pending contact + doClick(withId(R.id.addButton)); + sleep(DELAY_LONG); + + // wait for pending contact list activity to be shown + waitFor(allOf(withText(R.string.pending_contact_requests), + isDisplayed())); + + // remove pending contact + for (Pair p : contactManager + .getPendingContacts()) { + contactManager.removePendingContact(p.getFirst().getId()); + } + // add contact and make them appear online + Contact bob = testDataCreator.addContact(contactName, false, true); + sleep(DELAY_SMALL); + connectionRegistry.registerIncomingConnection(bob.getId(), ID, () -> { + }); + + // wait for contact list to be shown + waitFor(allOf(withText(R.string.contact_list_button), isDisplayed())); + + // click on new contact + doItemClick(withId(R.id.recyclerView), 0); + + sleep(DELAY_MEDIUM); + + // bring up keyboard + doClick(withId(R.id.input_text), DELAY_SMALL); + + String msg1 = getApplicationContext() + .getString(R.string.screenshot_message_1); + onView(withId(R.id.input_text)) + .perform(waitUntilMatches(isEnabled())) + .perform(replaceText(msg1)); + + sleep(DELAY_SMALL); + + doClick(withId(R.id.compositeSendButton)); + + sleep(DELAY_SMALL); + + // send emoji + doClick(withId(R.id.emoji_toggle), DELAY_SMALL); + onView(withId(R.id.input_text)) + .perform(replaceText("\uD83D\uDE0E")); + sleep(DELAY_SMALL); + doClick(withId(R.id.compositeSendButton)); + + // close keyboard + closeKeyboard(withId(R.id.compositeSendButton)); + + sleep(DELAY_LONG); + } + + private void doClick(final Matcher viewMatcher, long sleepMs) + throws InterruptedException { + doClick(viewMatcher); + if (!debug) sleep(sleepMs); + } + + private void doClick(final Matcher viewMatcher) + throws InterruptedException { + if (!debug) { + onView(viewMatcher) + .perform(waitUntilMatches(isDisplayed())) + .perform(visualClick(overlayView)); + sleep(500); + } + onView(viewMatcher) + .perform(waitUntilMatches(allOf(isDisplayed(), isEnabled()))) + .perform(click()); + } + + private void doItemClick(final Matcher viewMatcher, int pos) + throws InterruptedException { + if (!debug) { + onView(viewMatcher).perform( + actionOnItemAtPosition(pos, visualClick(overlayView))); + sleep(500); + } + onView(viewMatcher).perform( + actionOnItemAtPosition(pos, click())); + } + + private void closeKeyboard(final Matcher viewMatcher) + throws InterruptedException { + if (!debug) sleep(750); + onView(viewMatcher).perform(closeSoftKeyboard()); + } + +} diff --git a/briar-android/src/main/res/layout/splash.xml b/briar-android/src/main/res/layout/splash.xml index 0cd22a43c..d78083132 100644 --- a/briar-android/src/main/res/layout/splash.xml +++ b/briar-android/src/main/res/layout/splash.xml @@ -5,6 +5,7 @@ android:layout_height="match_parent"> Date: Mon, 22 Mar 2021 14:39:51 -0300 Subject: [PATCH 06/11] Use Android Test Orchestrator to have a clean state for each test fixes flaky/broken espresso tests --- .gitlab-ci.yml | 1 + briar-android/build.gradle | 5 +++- .../briarproject/briar/android/UiTest.java | 2 +- .../briar/android/PromoVideoTest.java | 1 - .../briar/android/ScreenshotTest.java | 5 ++++ .../briar/android/SetupDataTest.java | 26 +++++-------------- .../SettingsActivityScreenshotTest.java | 5 ++-- briar-android/witness.gradle | 2 ++ 8 files changed, 22 insertions(+), 25 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 76ecc8a15..c979798b8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -40,6 +40,7 @@ android test: paths: - kernel.log - logcat.txt + - briar-android/build/reports/androidTests/connected/flavors/* expire_in: 3 days when: on_failure when: manual diff --git a/briar-android/build.gradle b/briar-android/build.gradle index 475fdc569..b6a253871 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' + testInstrumentationRunnerArguments disableAnalytics: 'true', clearPackageData: 'true' } buildTypes { @@ -80,6 +80,7 @@ android { } testOptions { + execution 'ANDROIDX_TEST_ORCHESTRATOR' unitTests { includeAndroidResources = true } @@ -148,6 +149,8 @@ dependencies { androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion" androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion" androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion" + androidTestImplementation 'androidx.test:runner:1.3.0' + androidTestUtil 'androidx.test:orchestrator:1.3.0' androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:2.24" androidTestCompileOnly 'javax.annotation:jsr250-api:1.0' androidTestImplementation 'junit:junit:4.13.1' 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 af75f3bbf..030b68dc9 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 @@ -52,7 +52,7 @@ public abstract class UiTest { @Override protected void beforeActivityLaunched() { super.beforeActivityLaunched(); - accountManager.deleteAccount(); + // Android Test Orchestrator already clears existing accounts accountManager.createAccount(USERNAME, PASSWORD); Intent serviceIntent = new Intent(getApplicationContext(), BriarService.class); 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 f19be7487..87b69cb4a 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 @@ -66,7 +66,6 @@ public class PromoVideoTest extends ScreenshotTest { @Override protected void inject(BriarUiTestComponent component) { component.inject(this); - accountManager.deleteAccount(); } @Test diff --git a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/ScreenshotTest.java b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/ScreenshotTest.java index 84fef39b2..61c0a5bbd 100644 --- a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/ScreenshotTest.java +++ b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/ScreenshotTest.java @@ -10,6 +10,7 @@ import org.junit.ClassRule; import javax.inject.Inject; +import androidx.test.ext.junit.rules.ActivityScenarioRule; import tools.fastlane.screengrab.FalconScreenshotStrategy; import tools.fastlane.screengrab.Screengrab; import tools.fastlane.screengrab.locale.LocaleTestRule; @@ -26,6 +27,10 @@ public abstract class ScreenshotTest extends UiTest { @Inject protected Clock clock; + protected void screenshot(String name, ActivityScenarioRule rule) { + rule.getScenario().onActivity(activity -> screenshot(name, activity)); + } + protected void screenshot(String name, Activity activity) { try { Screengrab.screenshot(name, new FalconScreenshotStrategy(activity)); 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 04e5a854e..4faaf99f1 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 @@ -10,7 +10,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import androidx.test.espresso.intent.rule.IntentsTestRule; +import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.uiautomator.UiDevice; import androidx.test.uiautomator.UiObject; @@ -22,26 +22,22 @@ import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.action.ViewActions.typeText; import static androidx.test.espresso.assertion.ViewAssertions.matches; 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 androidx.test.espresso.matcher.ViewMatchers.withText; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID; import static org.briarproject.briar.android.ViewActions.waitUntilMatches; import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting; +import static org.hamcrest.Matchers.allOf; import static org.junit.Assert.assertTrue; @RunWith(AndroidJUnit4.class) public class SetupDataTest extends ScreenshotTest { @Rule - public IntentsTestRule testRule = - new IntentsTestRule(SetupActivity.class) { - @Override - protected void beforeActivityLaunched() { - super.beforeActivityLaunched(); - accountManager.deleteAccount(); - } - }; + public ActivityScenarioRule testRule = + new ActivityScenarioRule<>(SetupActivity.class); @Override protected void inject(BriarUiTestComponent component) { @@ -59,7 +55,7 @@ public class SetupDataTest extends ScreenshotTest { onView(withId(R.id.nickname_entry)) .perform(waitUntilMatches(withText(USERNAME))); - screenshot("manual_create_account", testRule.getActivity()); + screenshot("manual_create_account", testRule); onView(withId(R.id.next)) .check(matches(isDisplayed())) @@ -73,7 +69,7 @@ public class SetupDataTest extends ScreenshotTest { .check(matches(isDisplayed())) .perform(typeText(PASSWORD)); onView(withId(R.id.next)) - .check(matches(isDisplayed())) + .check(matches(allOf(isDisplayed(), isEnabled()))) .perform(click()); // White-list Doze if needed @@ -94,14 +90,6 @@ public class SetupDataTest extends ScreenshotTest { lifecycleManager.waitForStartup(); assertTrue(accountManager.hasDatabaseKey()); createTestData(); - - // close expiry warning - onView(withId(R.id.expiryWarning)) - .perform(waitUntilMatches(isDisplayed())); - onView(withId(R.id.expiryWarningClose)) - .check(matches(isDisplayed())); - onView(withId(R.id.expiryWarningClose)) - .perform(click()); } private void createTestData() { diff --git a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/settings/SettingsActivityScreenshotTest.java b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/settings/SettingsActivityScreenshotTest.java index c99250ad8..c396a835a 100644 --- a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/settings/SettingsActivityScreenshotTest.java +++ b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/settings/SettingsActivityScreenshotTest.java @@ -14,7 +14,6 @@ import org.junit.runner.RunWith; import androidx.recyclerview.widget.RecyclerView; import androidx.test.espresso.contrib.DrawerActions; import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.rule.ActivityTestRule; import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static androidx.test.espresso.Espresso.onView; @@ -38,8 +37,8 @@ import static org.junit.Assume.assumeTrue; public class SettingsActivityScreenshotTest extends ScreenshotTest { @Rule - public ActivityTestRule testRule = - new ActivityTestRule<>(SettingsActivity.class); + public CleanAccountTestRule testRule = + new CleanAccountTestRule<>(SettingsActivity.class); @Override protected void inject(BriarUiTestComponent component) { diff --git a/briar-android/witness.gradle b/briar-android/witness.gradle index e3be69cbc..6b11c5cdc 100644 --- a/briar-android/witness.gradle +++ b/briar-android/witness.gradle @@ -51,9 +51,11 @@ dependencyVerification { 'androidx.test.espresso:espresso-idling-resource:3.3.0:espresso-idling-resource-3.3.0.aar:29519b112731f289cc6e2f9b2eccc5ea72c754b04272bb93370f45d7e170a7c6', 'androidx.test.espresso:espresso-intents:3.3.0:espresso-intents-3.3.0.aar:5b6cd6aadce78edc705d93c1e81ace3b59be97128aca0e88fd9c5c176aa9bf10', 'androidx.test.ext:junit:1.1.2:junit-1.1.2.aar:6c6ab120c640bf16fcaae69cb83c144d0ed6b6298562be0ac35e37ed969c0409', + 'androidx.test.services:test-services:1.3.0:test-services-1.3.0.apk:1b88faab6864baf25c5d0b92a610c283c159a566e7a56c03307117fa1b542993', 'androidx.test.uiautomator:uiautomator:2.2.0:uiautomator-2.2.0.aar:2838e9d961dbffefbbd229a2bd4f6f82ac4fb2462975862a9e75e9ed325a3197', 'androidx.test:core:1.3.0:core-1.3.0.aar:86549cae8c5b848f817e2c716e174c7dab61caf0b4df9848680eeb753089a337', 'androidx.test:monitor:1.3.0:monitor-1.3.0.aar:f73a31306a783e63150c60c49e140dc38da39a1b7947690f4b73387b5ebad77e', + 'androidx.test:orchestrator:1.3.0:orchestrator-1.3.0.apk:676f808d08a3d05050eae30c3b7d92ce5cef1e00a54d68355bb7e7d4b72366fe', 'androidx.test:rules:1.3.0:rules-1.3.0.aar:c1753946c498b0d5d7cf341cfed661f66915c4c9deb4ed10462a08ae33b2429a', 'androidx.test:runner:1.3.0:runner-1.3.0.aar:61d13f5a9fcbbd73ba18fa84e1d6a0111c6e1c665a89b418126966e61fffd93b', 'androidx.tracing:tracing:1.0.0:tracing-1.0.0.aar:07b8b6139665b884a162eccf97891ca50f7f56831233bf25168ae04f7b568612', From f971533a5b7868afb2afba232c0a5687df5f97e0 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 25 Mar 2021 13:52:51 -0300 Subject: [PATCH 07/11] Add a way to detect if code runs as instrumentation test and disable some dialogs to make tests easier to write --- .../briar/android/BriarTestComponentApplication.java | 5 +++++ .../org/briarproject/briar/android/BriarApplication.java | 2 ++ .../org/briarproject/briar/android/BriarApplicationImpl.java | 5 +++++ .../java/org/briarproject/briar/android/BriarService.java | 4 +++- .../briarproject/briar/android/activity/BaseActivity.java | 5 +++-- .../briarproject/briar/android/activity/BriarActivity.java | 4 +++- .../briar/android/controller/BriarControllerImpl.java | 4 +++- .../briar/android/navdrawer/NavDrawerActivity.java | 5 +++-- .../briar/android/navdrawer/NavDrawerViewModel.java | 5 ++++- 9 files changed, 31 insertions(+), 8 deletions(-) diff --git a/briar-android/src/androidTest/java/org/briarproject/briar/android/BriarTestComponentApplication.java b/briar-android/src/androidTest/java/org/briarproject/briar/android/BriarTestComponentApplication.java index 239cd05ee..c60e61cb7 100644 --- a/briar-android/src/androidTest/java/org/briarproject/briar/android/BriarTestComponentApplication.java +++ b/briar-android/src/androidTest/java/org/briarproject/briar/android/BriarTestComponentApplication.java @@ -19,4 +19,9 @@ public class BriarTestComponentApplication extends BriarApplicationImpl { return component; } + @Override + public boolean isInstrumentationTest() { + return true; + } + } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplication.java b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplication.java index b113a195a..d0d863b65 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplication.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplication.java @@ -19,4 +19,6 @@ public interface BriarApplication extends BrambleApplication { SharedPreferences getDefaultSharedPreferences(); boolean isRunningInBackground(); + + boolean isInstrumentationTest(); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java index 927827913..3026d8302 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java @@ -151,4 +151,9 @@ public class BriarApplicationImpl extends Application ActivityManager.getMyMemoryState(info); return (info.importance != IMPORTANCE_FOREGROUND); } + + @Override + public boolean isInstrumentationTest() { + return false; + } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java b/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java index 6e36b1390..4014a73db 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java @@ -301,7 +301,9 @@ public class BriarService extends Service { LOG.info("Interrupted while waiting for shutdown"); } LOG.info("Exiting"); - System.exit(0); + if (!app.isInstrumentationTest()) { + System.exit(0); + } }, "BackgroundShutdown"); }, "BackgroundShutdown"); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java index 14a6b9845..da993afed 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java @@ -44,7 +44,6 @@ import static androidx.lifecycle.Lifecycle.State.STARTED; import static java.util.Collections.emptyList; import static java.util.logging.Level.INFO; import static java.util.logging.Logger.getLogger; -import static org.briarproject.briar.BuildConfig.FLAVOR; import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOTS; import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard; @@ -194,7 +193,9 @@ public abstract class BaseActivity extends AppCompatActivity } private boolean showScreenFilterWarning() { - if (FLAVOR == "screenshot") return false; + if (((BriarApplication) getApplication()).isInstrumentationTest()) { + return false; + } // If the dialog is already visible, filter the tap ScreenFilterDialogFragment f = findDialogFragment(); if (f != null && f.isVisible()) return false; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java index baf49a711..eae436236 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/BriarActivity.java @@ -10,6 +10,7 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.system.AndroidWakeLockManager; import org.briarproject.bramble.api.system.Wakeful; import org.briarproject.briar.R; +import org.briarproject.briar.android.BriarApplication; import org.briarproject.briar.android.account.UnlockActivity; import org.briarproject.briar.android.controller.BriarController; import org.briarproject.briar.android.controller.DbController; @@ -236,7 +237,8 @@ public abstract class BriarActivity extends BaseActivity { if (SDK_INT >= 21) finishAndRemoveTask(); else supportFinishAfterTransition(); LOG.info("Exiting"); - System.exit(0); + BriarApplication app = (BriarApplication) getApplication(); + if (!app.isInstrumentationTest()) System.exit(0); } @Deprecated diff --git a/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java index 65c3efec4..e0c166bf0 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/controller/BriarControllerImpl.java @@ -12,6 +12,7 @@ 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.system.AndroidWakeLockManager; +import org.briarproject.briar.android.BriarApplication; import org.briarproject.briar.android.BriarService; import org.briarproject.briar.android.BriarService.BriarServiceConnection; import org.briarproject.briar.android.controller.handler.ResultHandler; @@ -104,7 +105,8 @@ public class BriarControllerImpl implements BriarController { @Override public void hasDozed(ResultHandler handler) { - if (!dozeWatchdog.getAndResetDozeFlag() + BriarApplication app = (BriarApplication) activity.getApplication(); + if (app.isInstrumentationTest() || !dozeWatchdog.getAndResetDozeFlag() || !needsDozeWhitelisting(activity)) { handler.onResult(false); return; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java index aa27b6234..d9865871d 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java @@ -27,6 +27,7 @@ import org.briarproject.bramble.api.plugin.Plugin.State; import org.briarproject.bramble.api.plugin.TorConstants; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.briar.R; +import org.briarproject.briar.android.BriarApplication; import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.blog.FeedFragment; @@ -71,7 +72,6 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleS import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING; import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; -import static org.briarproject.briar.BuildConfig.FLAVOR; import static org.briarproject.briar.android.BriarService.EXTRA_STARTUP_FAILED; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD; @@ -135,7 +135,8 @@ public class NavDrawerActivity extends BriarActivity implements navDrawerViewModel = provider.get(NavDrawerViewModel.class); pluginViewModel = provider.get(PluginViewModel.class); - if (IS_DEBUG_BUILD && (FLAVOR != "screenshot")) { + BriarApplication app = (BriarApplication) getApplication(); + if (IS_DEBUG_BUILD && !app.isInstrumentationTest()) { navDrawerViewModel.showExpiryWarning() .observe(this, this::showExpiryWarning); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerViewModel.java index 132cc0b9f..783922c60 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerViewModel.java @@ -10,6 +10,7 @@ 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.system.AndroidExecutor; +import org.briarproject.briar.android.BriarApplication; import org.briarproject.briar.android.viewmodel.DbViewModel; import java.util.concurrent.Executor; @@ -120,7 +121,9 @@ public class NavDrawerViewModel extends DbViewModel { @UiThread void checkDozeWhitelisting() { // check this first, to hit the DbThread only when really necessary - if (!needsDozeWhitelisting(getApplication())) { + BriarApplication app = (BriarApplication) getApplication(); + if (app.isInstrumentationTest() || + !needsDozeWhitelisting(getApplication())) { shouldAskForDozeWhitelisting.setValue(false); return; } From 8a3dd5472b33e12aa85efa9e205a2266a9df72fa Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 25 Mar 2021 13:53:45 -0300 Subject: [PATCH 08/11] Make a screenshot when test fails to help with debugging --- .gitlab-ci.yml | 3 + .../android/ScreenshotOnFailureRule.java | 65 +++++++++++++++++++ .../briarproject/briar/android/UiTest.java | 5 ++ 3 files changed, 73 insertions(+) create mode 100644 briar-android/src/androidTest/java/org/briarproject/briar/android/ScreenshotOnFailureRule.java diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c979798b8..881e9d54e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -35,12 +35,15 @@ android test: - start-emulator.sh # run normal and screenshot tests together (exclude Large tests) - ./gradlew -Djava.security.egd=file:/dev/urandom connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.notAnnotation=androidx.test.filters.LargeTest + after_script: + - adb pull /sdcard/Pictures/screenshots artifacts: name: "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}" paths: - kernel.log - logcat.txt - briar-android/build/reports/androidTests/connected/flavors/* + - screenshots expire_in: 3 days when: on_failure when: manual diff --git a/briar-android/src/androidTest/java/org/briarproject/briar/android/ScreenshotOnFailureRule.java b/briar-android/src/androidTest/java/org/briarproject/briar/android/ScreenshotOnFailureRule.java new file mode 100644 index 000000000..87b8b7833 --- /dev/null +++ b/briar-android/src/androidTest/java/org/briarproject/briar/android/ScreenshotOnFailureRule.java @@ -0,0 +1,65 @@ +package org.briarproject.briar.android; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.io.IOException; +import java.util.HashSet; +import java.util.concurrent.atomic.AtomicBoolean; + +import androidx.test.espresso.Espresso; +import androidx.test.espresso.FailureHandler; +import androidx.test.espresso.base.DefaultFailureHandler; +import androidx.test.runner.screenshot.BasicScreenCaptureProcessor; +import androidx.test.runner.screenshot.ScreenCapture; +import androidx.test.runner.screenshot.ScreenCaptureProcessor; +import androidx.test.runner.screenshot.Screenshot; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; + +@NotNullByDefault +public class ScreenshotOnFailureRule implements TestRule { + + FailureHandler defaultFailureHandler = + new DefaultFailureHandler(getApplicationContext()); + + @Override + public Statement apply(Statement base, Description description) { + HashSet processors = new HashSet<>(1); + processors.add(new BasicScreenCaptureProcessor()); + Screenshot.addScreenCaptureProcessors(processors); + return new Statement() { + @Override + public void evaluate() throws Throwable { + AtomicBoolean errorHandled = new AtomicBoolean(false); + Espresso.setFailureHandler((throwable, matcher) -> { + takeScreenshot(description); + errorHandled.set(true); + defaultFailureHandler.handle(throwable, matcher); + }); + try { + base.evaluate(); + } catch (Throwable t) { + if (!errorHandled.get()) { + takeScreenshot(description); + } + throw t; + } + } + }; + } + + private void takeScreenshot(Description description) { + String name = description.getTestClass().getSimpleName(); + ScreenCapture capture = Screenshot.capture(); + capture.setName(name); + try { + capture.process(); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} 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 030b68dc9..63691223d 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 @@ -10,6 +10,7 @@ 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; import javax.inject.Inject; @@ -23,6 +24,10 @@ import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_ @SuppressWarnings("WeakerAccess") public abstract class UiTest { + @ClassRule + public static final ScreenshotOnFailureRule screenshotOnFailureRule = + new ScreenshotOnFailureRule(); + protected final String USERNAME = getApplicationContext().getString(R.string.screenshot_alice); protected static final String PASSWORD = "123456"; From 8a768cf933cda6fec6fd657270a06f27273638bd Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 25 Mar 2021 13:55:46 -0300 Subject: [PATCH 09/11] 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 From db610cfb4c526836a47780986d4153fba57d8d59 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 1 Apr 2021 17:12:04 -0300 Subject: [PATCH 10/11] Run only tests from android package on emulator Otherwise, it re-runs bramble tests for some reason --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 881e9d54e..9fd196c5a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -34,7 +34,7 @@ android test: # start emulator first, so it can fail early - start-emulator.sh # run normal and screenshot tests together (exclude Large tests) - - ./gradlew -Djava.security.egd=file:/dev/urandom connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.notAnnotation=androidx.test.filters.LargeTest + - ./gradlew -Djava.security.egd=file:/dev/urandom connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.package=org.briarproject.briar.android -Pandroid.testInstrumentationRunnerArguments.notAnnotation=androidx.test.filters.LargeTest after_script: - adb pull /sdcard/Pictures/screenshots artifacts: From 8f628f2d4593e66b6baddca402e84314ac87ce11 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 6 Apr 2021 14:43:42 -0300 Subject: [PATCH 11/11] Fix PromoVideoTest for CI --- .../briar/android/ViewActions.java | 25 ++++++++++-- .../briar/android/OverlayView.java | 3 ++ .../briar/android/PromoVideoTest.java | 40 ++++++++++++------- 3 files changed, 50 insertions(+), 18 deletions(-) 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 1154272ca..50c2dd8be 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 @@ -68,19 +68,38 @@ public class ViewActions { public static ViewAction waitForActivity(Class clazz, Stage stage, long timeout) { - return new CustomViewAction() { + return new CustomViewAction(timeout) { @Override protected boolean exitConditionTrue(View view) { boolean found = false; ActivityLifecycleMonitor lifecycleMonitor = ActivityLifecycleMonitorRegistry.getInstance(); + log(lifecycleMonitor); + for (Activity a : lifecycleMonitor + .getActivitiesInStage(stage)) { + if (a.getClass().equals(clazz)) found = true; + } + return found; + } + + private void log(ActivityLifecycleMonitor lifecycleMonitor) { + log(lifecycleMonitor, Stage.PRE_ON_CREATE); + log(lifecycleMonitor, Stage.CREATED); + log(lifecycleMonitor, Stage.STARTED); + log(lifecycleMonitor, Stage.RESUMED); + log(lifecycleMonitor, Stage.PAUSED); + log(lifecycleMonitor, Stage.STOPPED); + log(lifecycleMonitor, Stage.RESTARTED); + log(lifecycleMonitor, Stage.DESTROYED); + } + + private void log(ActivityLifecycleMonitor lifecycleMonitor, + Stage 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 diff --git a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/OverlayView.java b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/OverlayView.java index 5ba5417e2..776cdd6b0 100644 --- a/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/OverlayView.java +++ b/briar-android/src/androidTestScreenshot/java/org/briarproject/briar/android/OverlayView.java @@ -20,6 +20,9 @@ import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread; import static org.junit.Assert.assertTrue; +/** + * A full-screen overlay used to make taps visible in instrumentation tests. + */ public class OverlayView extends View { public static OverlayView attach(Context ctx) throws Throwable { 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 0c8034a7f..7e9d6f430 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 @@ -30,6 +30,7 @@ import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard; import static androidx.test.espresso.action.ViewActions.replaceText; +import static androidx.test.espresso.action.ViewActions.scrollTo; import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; @@ -49,13 +50,13 @@ import static org.junit.Assert.assertTrue; @RunWith(AndroidJUnit4.class) public class PromoVideoTest extends ScreenshotTest { - // we can leave debug to true (to speed up CI) - // and only set it to false when doing recordings - private static final boolean debug = true; + // we can leave isFilming to false (to speed up CI) + // and only set it to true when doing recordings + private static final boolean isFilming = false; - private static final int DELAY_SMALL = debug ? 0 : 4_000; - private static final int DELAY_MEDIUM = debug ? 0 : 7_500; - private static final int DELAY_LONG = debug ? 0 : 10_000; + private static final int DELAY_SMALL = isFilming ? 4_000 : 0; + private static final int DELAY_MEDIUM = isFilming ? 7_500 : 0; + private static final int DELAY_LONG = isFilming ? 10_000 : 0; @Rule public ActivityScenarioRule testRule = @@ -74,7 +75,7 @@ public class PromoVideoTest extends ScreenshotTest { @Test public void createAccountAddContact() throws Throwable { - if (!debug) { + if (isFilming) { // Using this breaks emulator CI tests for some reason. // Only use it for filming for now until we have time to debug this. overlayView = OverlayView.attach(getApplicationContext()); @@ -84,7 +85,14 @@ public class PromoVideoTest extends ScreenshotTest { onView(withId(R.id.logoView)) .perform(waitUntilMatches(isDisplayed())); - if (debug) waitFor(SetupActivity.class, 25_000); + // It takes a long time for SetupActivity to start after the splash, + // (because it is shown longer for videos), so increase timeout. + if (!isFilming) waitFor(SetupActivity.class, 20_000); + + // Note: We use waiting code only when not filming, + // to make the test reliable for CI. Otherwise, we used fixed + // delays to deterministically align with subtitles. + sleep(DELAY_LONG); // Enter username @@ -136,7 +144,7 @@ public class PromoVideoTest extends ScreenshotTest { sleep(DELAY_SMALL); // wait for contact list to be shown - if (debug) waitFor(NavDrawerActivity.class); + if (!isFilming) waitFor(NavDrawerActivity.class); // clicking the FAB doesn't work, so we click its inner FAB as well onView(withId(R.id.speedDial)) @@ -173,11 +181,12 @@ public class PromoVideoTest extends ScreenshotTest { sleep(DELAY_SMALL); // add pending contact + onView(withId(R.id.addButton)).perform(scrollTo()); doClick(withId(R.id.addButton)); sleep(DELAY_LONG); // wait for pending contact list activity to be shown - if (debug) { + if (!isFilming) { waitFor(PendingContactListActivity.class); waitFor(allOf(withText(R.string.pending_contact_requests), isDisplayed())); @@ -197,10 +206,11 @@ public class PromoVideoTest extends ScreenshotTest { sleep(DELAY_LONG); // wait for contact list to be shown - if (debug) { + if (!isFilming) { waitFor(NavDrawerActivity.class); waitFor(allOf(withText(R.string.contact_list_button), isDisplayed())); + waitFor(allOf(withId(R.id.recyclerView), isDisplayed())); } // click on new contact @@ -239,12 +249,12 @@ public class PromoVideoTest extends ScreenshotTest { private void doClick(final Matcher viewMatcher, long sleepMs) throws InterruptedException { doClick(viewMatcher); - if (!debug) sleep(sleepMs); + if (isFilming) sleep(sleepMs); } private void doClick(final Matcher viewMatcher) throws InterruptedException { - if (!debug) { + if (isFilming) { onView(viewMatcher) .perform(waitUntilMatches(isDisplayed())) .perform(visualClick(overlayView)); @@ -257,7 +267,7 @@ public class PromoVideoTest extends ScreenshotTest { private void doItemClick(final Matcher viewMatcher, int pos) throws InterruptedException { - if (!debug) { + if (isFilming) { onView(viewMatcher).perform( actionOnItemAtPosition(pos, visualClick(overlayView))); sleep(500); @@ -268,7 +278,7 @@ public class PromoVideoTest extends ScreenshotTest { private void closeKeyboard(final Matcher viewMatcher) throws InterruptedException { - if (!debug) sleep(750); + if (isFilming) sleep(750); onView(viewMatcher).perform(closeSoftKeyboard()); }