From d5d550b7e5ea4525d06806b77e3df551c3ef160e Mon Sep 17 00:00:00 2001 From: Art O Cathain Date: Sat, 23 Jan 2021 17:28:25 +1030 Subject: [PATCH] Wait for certain views to appear --- .../android/espresso/EspressoUtils.java | 61 +++++++++++++++++++ .../android/espresso/InternalLinksTest.kt | 15 +++-- .../android/espresso/NoteFragmentTest.kt | 10 +-- 3 files changed, 76 insertions(+), 10 deletions(-) diff --git a/app/src/androidTest/java/com/orgzly/android/espresso/EspressoUtils.java b/app/src/androidTest/java/com/orgzly/android/espresso/EspressoUtils.java index 7acff983d..41bac8ca7 100644 --- a/app/src/androidTest/java/com/orgzly/android/espresso/EspressoUtils.java +++ b/app/src/androidTest/java/com/orgzly/android/espresso/EspressoUtils.java @@ -13,12 +13,15 @@ import androidx.appcompat.widget.Toolbar; import androidx.recyclerview.widget.RecyclerView; import androidx.test.espresso.DataInteraction; +import androidx.test.espresso.PerformException; import androidx.test.espresso.UiController; import androidx.test.espresso.ViewAction; import androidx.test.espresso.ViewInteraction; import androidx.test.espresso.action.CloseKeyboardAction; import androidx.test.espresso.contrib.RecyclerViewActions; import androidx.test.espresso.matcher.ViewMatchers; +import androidx.test.espresso.util.HumanReadables; +import androidx.test.espresso.util.TreeIterables; import androidx.test.platform.app.InstrumentationRegistry; import com.orgzly.R; @@ -29,6 +32,8 @@ import org.hamcrest.Matchers; import org.hamcrest.TypeSafeMatcher; +import java.util.concurrent.TimeoutException; + import static androidx.test.espresso.Espresso.onData; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu; @@ -40,6 +45,7 @@ import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom; import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA; 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.withContentDescription; import static androidx.test.espresso.matcher.ViewMatchers.withHint; @@ -247,9 +253,15 @@ public boolean matchesSafely(View view) { } static ViewInteraction onSnackbar() { + onView(isRoot()).perform(waitId(com.google.android.material.R.id.snackbar_text, 5000)); return onView(withId(com.google.android.material.R.id.snackbar_text)); } + static ViewInteraction onNoteTitle() { + onView(isRoot()).perform(waitId(R.id.fragment_note_title, 5000)); + return onView(withId(R.id.fragment_note_title)); + } + /* * Regular expression matching. * https://github.com/hamcrest/JavaHamcrest/issues/65 @@ -443,4 +455,53 @@ public void perform(UiController uiController, View view) { } }; } + + /** + * from https://stackoverflow.com/a/49814995/116509 + * + * Perform action of waiting for a specific view id. + * @param viewId The id of the view to wait for. + * @param millis The timeout of until when to wait for. + */ + public static ViewAction waitId(final int viewId, final long millis) { + return new ViewAction() { + @Override + public Matcher getConstraints() { + return isRoot(); + } + + @Override + public String getDescription() { + return "wait for a specific view with id <" + viewId + "> during " + millis + " millis."; + } + + @Override + public void perform(final UiController uiController, final View view) { + uiController.loopMainThreadUntilIdle(); + final long startTime = System.currentTimeMillis(); + final long endTime = startTime + millis; + final Matcher viewMatcher = withId(viewId); + + do { + for (View child : TreeIterables.breadthFirstViewTraversal(view)) { + // found view with required ID + if (viewMatcher.matches(child)) { + return; + } + } + + uiController.loopMainThreadForAtLeast(50); + } + while (System.currentTimeMillis() < endTime); + + // timeout happens + throw new PerformException.Builder() + .withActionDescription(this.getDescription()) + .withViewDescription(HumanReadables.describe(view)) + .withCause(new TimeoutException()) + .build(); + } + }; + } + } diff --git a/app/src/androidTest/java/com/orgzly/android/espresso/InternalLinksTest.kt b/app/src/androidTest/java/com/orgzly/android/espresso/InternalLinksTest.kt index 368e69383..afc6fe4c3 100644 --- a/app/src/androidTest/java/com/orgzly/android/espresso/InternalLinksTest.kt +++ b/app/src/androidTest/java/com/orgzly/android/espresso/InternalLinksTest.kt @@ -4,10 +4,15 @@ import androidx.test.core.app.ActivityScenario import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.matcher.ViewMatchers.* +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText import com.orgzly.R import com.orgzly.android.OrgzlyTest -import com.orgzly.android.espresso.EspressoUtils.* +import com.orgzly.android.espresso.EspressoUtils.clickClickableSpan +import com.orgzly.android.espresso.EspressoUtils.onBook +import com.orgzly.android.espresso.EspressoUtils.onNoteInBook +import com.orgzly.android.espresso.EspressoUtils.onNoteTitle import com.orgzly.android.ui.main.MainActivity import org.junit.Before import org.junit.Test @@ -68,21 +73,21 @@ class InternalLinksTest : OrgzlyTest() { fun testDifferentCaseUuidInternalLink() { onNoteInBook(1, R.id.item_head_content) .perform(clickClickableSpan("id:bdce923b-C3CD-41ED-B58E-8BDF8BABA54F")) - onView(withId(R.id.fragment_note_title)).check(matches(withText("Note [b-2]"))) + onNoteTitle().check(matches(withText("Note [b-2]"))) } @Test fun testDifferentCaseCustomIdInternalLink() { onNoteInBook(2, R.id.item_head_content) .perform(clickClickableSpan("#Different case custom id")) - onView(withId(R.id.fragment_note_title)).check(matches(withText("Note [b-1]"))) + onNoteTitle().check(matches(withText("Note [b-1]"))) } @Test fun testCustomIdLink() { onNoteInBook(3, R.id.item_head_content) .perform(clickClickableSpan("#Link to note in a different book")) - onView(withId(R.id.fragment_note_title)).check(matches(withText("Note [b-3]"))) + onNoteTitle().check(matches(withText("Note [b-3]"))) } @Test diff --git a/app/src/androidTest/java/com/orgzly/android/espresso/NoteFragmentTest.kt b/app/src/androidTest/java/com/orgzly/android/espresso/NoteFragmentTest.kt index 2e6ce464c..b7232f888 100644 --- a/app/src/androidTest/java/com/orgzly/android/espresso/NoteFragmentTest.kt +++ b/app/src/androidTest/java/com/orgzly/android/espresso/NoteFragmentTest.kt @@ -92,7 +92,7 @@ class NoteFragmentTest : OrgzlyTest() { onNoteInBook(1, R.id.item_head_title).check(matches(withText("Note #1."))) onNoteInBook(1).perform(click()) - onView(withId(R.id.fragment_note_title)) + onNoteTitle() .perform(*replaceTextCloseKeyboard("Note title changed")) onView(withId(R.id.done)).perform(click()) @@ -230,7 +230,7 @@ class NoteFragmentTest : OrgzlyTest() { @Test fun testTitleCanNotBeEmptyForExistingNote() { onNoteInBook(1).perform(click()) - onView(withId(R.id.fragment_note_title)).perform(*replaceTextCloseKeyboard("")) + onNoteTitle().perform(*replaceTextCloseKeyboard("")) onView(withId(R.id.done)).perform(click()) onSnackbar().check(matches(withText(R.string.title_can_not_be_empty))) } @@ -502,7 +502,7 @@ class NoteFragmentTest : OrgzlyTest() { fun testBreadcrumbsFollowToNote() { onNoteInBook(3).perform(click()) onView(withId(R.id.fragment_note_breadcrumbs_text)).perform(clickClickableSpan("Note #2.")) - onView(withId(R.id.fragment_note_title)).check(matches(withText("Note #2."))) + onNoteTitle().check(matches(withText("Note #2."))) } @Test @@ -510,7 +510,7 @@ class NoteFragmentTest : OrgzlyTest() { onNoteInBook(1).perform(longClick()) onView(withId(R.id.bottom_action_bar_new)).perform(click()) onView(withText(R.string.new_under)).perform(click()) - onView(withId(R.id.fragment_note_title)).perform(*replaceTextCloseKeyboard("1.1")) + onNoteTitle().perform(*replaceTextCloseKeyboard("1.1")) onView(withId(R.id.fragment_note_breadcrumbs_text)).perform(clickClickableSpan("Note #1.")) // Dialog is displayed @@ -521,7 +521,7 @@ class NoteFragmentTest : OrgzlyTest() { onView(withText(R.string.cancel)).perform(click()) // Title remains the same - onView(withId(R.id.fragment_note_title)).check(matches(withText("1.1"))) + onNoteTitle().check(matches(withText("1.1"))) } // https://github.com/orgzly/orgzly-android/issues/605