diff --git a/README.md b/README.md index 0ecd760d2..ad0626360 100644 --- a/README.md +++ b/README.md @@ -174,12 +174,26 @@ environment. Either select a specific class or select "All in Module" / "OK" / Select your configuration in the toolbar / Click on the green "run" button in the toolbar to run the tests +## Get the benchmark results + When the benchmark is done, you will get a result like `MEASURED RESULTS (Benchmark) - Going thorough all 10 chats: 11635,11207,11363,11352,11279,11183,11137,11145,11032,11057`. You can paste `11635,11207,11363,11352,11279,11183,11137,11145,11032,11057` into a cell in a LibreOffice spreadsheet, do "Data" / "Text to columns", choose `,` as a separator, hit "OK", and create a diagram. +## Run online tests + +For some tests, you need to provide the credentials to an actual email account. +You have 2 ways to do this: + +1. (Recommended): Put them into the file ~/.gradle/gradle.properties (create it if it doesn't exist): + ``` + TEST_ADDR=youraccount@yourdomain.org + TEST_MAIL_PW=youpassword + ``` + +2. Or set them via environment variables. # Credits diff --git a/androidTest/com/b44t/messenger/EnterChatsBenchmark.java b/androidTest/com/b44t/messenger/EnterChatsBenchmark.java index 2b9ef833e..7662dd473 100644 --- a/androidTest/com/b44t/messenger/EnterChatsBenchmark.java +++ b/androidTest/com/b44t/messenger/EnterChatsBenchmark.java @@ -1,8 +1,5 @@ package com.b44t.messenger; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; import android.util.Log; import androidx.test.espresso.contrib.RecyclerViewActions; @@ -16,20 +13,15 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.thoughtcrime.securesms.ConversationListActivity; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.connect.AccountManager; -import org.thoughtcrime.securesms.connect.DcHelper; -import org.thoughtcrime.securesms.util.Prefs; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.Espresso.pressBack; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.action.ViewActions.replaceText; -import static androidx.test.espresso.action.ViewActions.typeText; import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription; import static androidx.test.espresso.matcher.ViewMatchers.withHint; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; -import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; @RunWith(AndroidJUnit4.class) @@ -50,27 +42,10 @@ public class EnterChatsBenchmark { private final static String TAG = EnterChatsBenchmark.class.getSimpleName(); @Rule - public ActivityScenarioRule activityRule = new ActivityScenarioRule<>(getConversationsListIntent()); - - private Intent getConversationsListIntent() { - Intent intent = - Intent.makeMainActivity( - new ComponentName(getInstrumentation().getTargetContext(), ConversationListActivity.class)); - if (!USE_EXISTING_CHATS) { - Context context = getInstrumentation().getTargetContext(); - AccountManager.getInstance().beginAccountCreation(context); - DcContext c = DcHelper.getContext(context); - c.setConfig("configured_addr", "alice@example.org"); - c.setConfig("configured_mail_pw", "abcd"); - c.setConfig("configured", "1"); - } - return intent; - } + public ActivityScenarioRule activityRule = TestUtils.getOfflineActivityRule(); @Test public void createAndEnterNChats() { - Prefs.setEnterSendsEnabled(getInstrumentation().getTargetContext(), true); - if (!USE_EXISTING_CHATS) { createChatAndGoBack("Group #1", "Hello!", "Some links: https://testrun.org", "And a command: /help"); createChatAndGoBack("Group #2", "example.org, alice@example.org", "aaaaaaa", "bbbbbb"); @@ -85,7 +60,7 @@ public class EnterChatsBenchmark { } String[] times = new String[GO_THROUGH_ALL_CHATS_N_TIMES]; - for (int i = 0; i activityRule = new ActivityScenarioRule<>(getIntent()); + + private Intent getIntent() { + Context context = getInstrumentation().getTargetContext(); + AccountManager.getInstance().beginAccountCreation(context); + return new Intent(getInstrumentation().getTargetContext(), WelcomeActivity.class); + } + + @Test + public void testAccountCreation() { + if (TextUtils.isEmpty(BuildConfig.TEST_ADDR) || TextUtils.isEmpty(BuildConfig.TEST_MAIL_PW)) { + throw new RuntimeException("You need to set TEST_ADDR and TEST_MAIL_PW; " + + "either in gradle.properties or via an environment variable. " + + "See README.md for more details."); + } + onView(withText(R.string.qrscan_title)).check(matches(isClickable())); + onView(withText(R.string.import_backup_title)).check(matches(isClickable())); + onView(withText(R.string.login_header)).perform(click()); + onView(withHint(R.string.email_address)).perform(replaceText(BuildConfig.TEST_ADDR)); + onView(withHint(R.string.existing_password)).perform(replaceText(BuildConfig.TEST_MAIL_PW)); + onView(withContentDescription(R.string.ok)).perform(click()); + TestUtils.waitForView(withText(R.string.app_name), 10000, 100); + + // TODO: Try to also perform other steps of the release checklist at + // https://github.com/deltachat/deltachat-android/blob/master/docs/release-checklist.md#testing-checklist + } + + @After + public void cleanup() { + TestUtils.cleanup(); + } +} diff --git a/androidTest/com/b44t/messenger/SharingTest.java b/androidTest/com/b44t/messenger/SharingTest.java new file mode 100644 index 000000000..606ff8b26 --- /dev/null +++ b/androidTest/com/b44t/messenger/SharingTest.java @@ -0,0 +1,64 @@ +package com.b44t.messenger; + +import android.content.ComponentName; +import android.content.Intent; + +import androidx.test.espresso.contrib.RecyclerViewActions; +import androidx.test.ext.junit.rules.ActivityScenarioRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.LargeTest; + +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.thoughtcrime.securesms.ConversationListActivity; +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.ShareActivity; +import org.thoughtcrime.securesms.connect.DcHelper; + +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.matcher.ViewMatchers.withHint; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class SharingTest { + // ============================================================================================== + // PLEASE BACKUP YOUR ACCOUNT BEFORE RUNNING THIS! + // ============================================================================================== + + private final static String TAG = SharingTest.class.getSimpleName(); + + @Rule + public ActivityScenarioRule activityRule = TestUtils.getOfflineActivityRule(); + + @Test + public void testNormalSharing() { + activityRule.getScenario().onActivity(a -> { + DcHelper.getContext(a).createGroupChat(false, "group"); + }); + + Intent i = new Intent(Intent.ACTION_SEND); + i.putExtra(Intent.EXTRA_TEXT, "Hello!"); + i.setComponent(new ComponentName(getInstrumentation().getTargetContext().getApplicationContext(), ShareActivity.class)); + activityRule.getScenario().onActivity(a -> a.startActivity(i)); + + onView(withId(R.id.list)).perform(RecyclerViewActions.actionOnItemAtPosition(0, click())); + + onView(withHint(R.string.chat_input_placeholder)).check(matches(withText("Hello!"))); + TestUtils.pressSend(); + } + + // TODO test other things from https://github.com/deltachat/interface/blob/master/user-testing/mailto-links.md + + @After + public void cleanup() { + TestUtils.cleanup(); + } +} diff --git a/androidTest/com/b44t/messenger/TestUtils.java b/androidTest/com/b44t/messenger/TestUtils.java new file mode 100644 index 000000000..6733737f1 --- /dev/null +++ b/androidTest/com/b44t/messenger/TestUtils.java @@ -0,0 +1,161 @@ +package com.b44t.messenger; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.test.espresso.NoMatchingViewException; +import androidx.test.espresso.UiController; +import androidx.test.espresso.ViewAction; +import androidx.test.espresso.ViewInteraction; +import androidx.test.espresso.util.TreeIterables; +import androidx.test.ext.junit.rules.ActivityScenarioRule; + +import org.hamcrest.Matcher; +import org.thoughtcrime.securesms.ConversationListActivity; +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.connect.AccountManager; +import org.thoughtcrime.securesms.connect.DcHelper; +import org.thoughtcrime.securesms.util.Prefs; +import org.thoughtcrime.securesms.util.Util; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.typeText; +import static androidx.test.espresso.matcher.ViewMatchers.isRoot; +import static androidx.test.espresso.matcher.ViewMatchers.withHint; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +class TestUtils { + private static int createdAccountId = 0; + private static boolean resetEnterSends = false; + + public static void cleanupCreatedAccount(Context context) { + DcAccounts accounts = DcHelper.getAccounts(context); + if (createdAccountId != 0) { + accounts.removeAccount(createdAccountId); + createdAccountId = 0; + } + } + + public static void cleanup() { + Context context = getInstrumentation().getTargetContext(); + cleanupCreatedAccount(context); + if (resetEnterSends) { + Prefs.setEnterSendsEnabled(getInstrumentation().getTargetContext(), false); + } + } + + public static void createOfflineAccount() { + Context context = getInstrumentation().getTargetContext(); + cleanupCreatedAccount(context); + createdAccountId = AccountManager.getInstance().beginAccountCreation(context); + DcContext c = DcHelper.getContext(context); + c.setConfig("configured_addr", "alice@example.org"); + c.setConfig("configured_mail_pw", "abcd"); + c.setConfig("configured", "1"); + } + + @NonNull + static ActivityScenarioRule getOfflineActivityRule() { + Intent intent = + Intent.makeMainActivity( + new ComponentName(getInstrumentation().getTargetContext(), ConversationListActivity.class)); + TestUtils.createOfflineAccount(); + return new ActivityScenarioRule<>(intent); + } + + /** + * Perform action of waiting for a certain view within a single root view + * + * @param matcher Generic Matcher used to find our view + */ + static ViewAction searchFor(Matcher matcher) { + return new ViewAction() { + + public Matcher getConstraints() { + return isRoot(); + } + + public String getDescription() { + return "searching for view $matcher in the root view"; + } + + public void perform(UiController uiController, View view) { + + Iterable childViews = TreeIterables.breadthFirstViewTraversal(view); + + // Look for the match in the tree of childviews + for (View it : childViews) { + if (matcher.matches(it)) { + // found the view + return; + } + } + + throw new NoMatchingViewException.Builder() + .withRootView(view) + .withViewMatcher(matcher) + .build(); + } + }; + } + + /** + * Perform action of implicitly waiting for a certain view. + * This differs from EspressoExtensions.searchFor in that, + * upon failure to locate an element, it will fetch a new root view + * in which to traverse searching for our @param match + * + * @param viewMatcher ViewMatcher used to find our view + */ + public static ViewInteraction waitForView( + Matcher viewMatcher, + int waitMillis, + int waitMillisPerTry + ) { + + // Derive the max tries + int maxTries = (int) (waitMillis / waitMillisPerTry); + + int tries = 0; + + for (int i = 0; i < maxTries; i++) + try { + // Track the amount of times we've tried + tries++; + + // Search the root for the view + onView(isRoot()).perform(searchFor(viewMatcher)); + + // If we're here, we found our view. Now return it + return onView(viewMatcher); + + } catch (Exception e) { + if (tries == maxTries) { + throw e; + } + Util.sleep(waitMillisPerTry); + } + + throw new RuntimeException("Error finding a view matching $viewMatcher"); + } + + /** + * Normally, you would do + * onView(withId(R.id.send_button)).perform(click()); + * to send the draft message. However, in order to change the send button to the attach button + * while there is no draft, the send button is made invisible and the attach button is made + * visible instead. This confuses the test framework.

+ * + * So, this is a workaround for pressing the send button. + */ + public static void pressSend() { + if (!Prefs.isEnterSendsEnabled(getInstrumentation().getTargetContext())) { + resetEnterSends = true; + Prefs.setEnterSendsEnabled(getInstrumentation().getTargetContext(), true); + } + onView(withHint(R.string.chat_input_placeholder)).perform(typeText("\n")); + } +} diff --git a/build.gradle b/build.gradle index c3fc0f75f..44a89a514 100644 --- a/build.gradle +++ b/build.gradle @@ -120,6 +120,9 @@ android { } testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + buildConfigField("String", "TEST_ADDR", buildConfigProperty("TEST_ADDR")) + buildConfigField("String", "TEST_MAIL_PW", buildConfigProperty("TEST_MAIL_PW")) } compileOptions { @@ -215,3 +218,19 @@ android { abortOnError false } } + +String buildConfigProperty(String name) { + return "\"${propertyOrEmpty(name)}\"" +} + +String propertyOrEmpty(String name) { + Object p = findProperty(name) + if (p == null) return environmentVariable(name) + return (String) p +} + +String environmentVariable(String name) { + String env = System.getenv(name) + if (env == null) return "" + return env +} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/connect/AccountManager.java b/src/org/thoughtcrime/securesms/connect/AccountManager.java index 4975931f1..5e062df22 100644 --- a/src/org/thoughtcrime/securesms/connect/AccountManager.java +++ b/src/org/thoughtcrime/securesms/connect/AccountManager.java @@ -83,9 +83,10 @@ public class AccountManager { // add accounts - public void beginAccountCreation(Context context) { - DcHelper.getAccounts(context).addAccount(); + public int beginAccountCreation(Context context) { + int id = DcHelper.getAccounts(context).addAccount(); resetDcContext(context); + return id; } public boolean canRollbackAccountCreation(Context context) {