From 086e12975462de7d7f2af40b754cc8f3eff8ac7c Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Sun, 30 Sep 2018 19:34:37 -0500 Subject: [PATCH] Isolate UI tests and ignore in CI, test CardBrowser --- .travis.yml | 2 +- .../java/com.ichi2.anki.tests/Shared.java | 13 +- .../ichi2/anki/uitests}/ActivityTestUI.java | 15 +- .../anki/uitests/CardBrowserPreviewUI.java | 320 ++++++++++++++++++ 4 files changed, 336 insertions(+), 14 deletions(-) rename AnkiDroid/src/androidTest/java/{com.ichi2.anki.tests => com/ichi2/anki/uitests}/ActivityTestUI.java (96%) create mode 100644 AnkiDroid/src/androidTest/java/com/ichi2/anki/uitests/CardBrowserPreviewUI.java diff --git a/.travis.yml b/.travis.yml index 4009ca2ce6..2afd90dbb9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -70,7 +70,7 @@ install: - adb shell input keyevent 82 & script: - - ./gradlew jacocoTestReport -Pandroid.testInstrumentationRunnerArguments.notAnnotation=androidx.test.filters.LargeTest + - ./gradlew jacocoTestReport -Pandroid.testInstrumentationRunnerArguments.notPackage=com.ichi2.anki.uitests # Codacy integration, partials for all APIs, then an optional finalize after_success: diff --git a/AnkiDroid/src/androidTest/java/com.ichi2.anki.tests/Shared.java b/AnkiDroid/src/androidTest/java/com.ichi2.anki.tests/Shared.java index a5130705b9..7d8ce4d5e5 100644 --- a/AnkiDroid/src/androidTest/java/com.ichi2.anki.tests/Shared.java +++ b/AnkiDroid/src/androidTest/java/com.ichi2.anki.tests/Shared.java @@ -74,8 +74,12 @@ public class Shared { /** Get the current activity so, to inspect for View ids */ private Activity getCurrentActivity() { final Activity[] activity = new Activity[1]; - onView(isRoot()).check((view, noViewFoundException) -> - activity[0] = scanForActivity(view.findViewById(android.R.id.content).getContext())); + onView(isRoot()).check((view, noViewFoundException) -> { + View content = view.findViewById(android.R.id.content); + if (content != null) { + activity[0] = scanForActivity(content.getContext()); + } + }); return activity[0]; } @@ -115,10 +119,11 @@ public class Shared { * @return See getTestDir. */ private static File getTestDir(Context context, String name) { + String suffix = ""; if (!TextUtils.isEmpty(name)) { - name = "-" + name; + suffix = "-" + name; } - File dir = new File(context.getCacheDir(), "testfiles" + name); + File dir = new File(context.getCacheDir(), "testfiles" + suffix); if (!dir.exists()) { assertTrue(dir.mkdir()); } diff --git a/AnkiDroid/src/androidTest/java/com.ichi2.anki.tests/ActivityTestUI.java b/AnkiDroid/src/androidTest/java/com/ichi2/anki/uitests/ActivityTestUI.java similarity index 96% rename from AnkiDroid/src/androidTest/java/com.ichi2.anki.tests/ActivityTestUI.java rename to AnkiDroid/src/androidTest/java/com/ichi2/anki/uitests/ActivityTestUI.java index f5e94c0fab..2737ae1df4 100644 --- a/AnkiDroid/src/androidTest/java/com.ichi2.anki.tests/ActivityTestUI.java +++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/uitests/ActivityTestUI.java @@ -1,4 +1,4 @@ -package com.ichi2.anki.tests; +package com.ichi2.anki.uitests; import android.Manifest; @@ -15,6 +15,7 @@ import android.view.ViewParent; import com.azimolabs.conditionwatcher.ConditionWatcher; import com.ichi2.anki.IntentHandler; import com.ichi2.anki.R; +import com.ichi2.anki.tests.Shared; import org.hamcrest.Description; import org.hamcrest.Matcher; @@ -35,12 +36,14 @@ import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.withClassName; import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription; import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withSubstring; import static androidx.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.anything; import static org.hamcrest.Matchers.is; @LargeTest +@SuppressWarnings("PMD.ExcessiveMethodLength") @RunWith(AndroidJUnit4.class) public class ActivityTestUI { @@ -290,14 +293,8 @@ public class ActivityTestUI { isDisplayed())); floatingActionButton.perform(click()); - ViewInteraction appCompatTextView = onView( - allOf(withId(R.id.CardEditorCardsText), withText("Cards: Card 1"), - childAtPosition( - allOf(withId(R.id.CardEditorCardsButton), - childAtPosition( - withClassName(is("android.widget.LinearLayout")), - 0)), - 0))); + ConditionWatcher.waitForCondition(new Shared.ViewItemWaitingInstruction(R.id.CardEditorCardsText)); + ViewInteraction appCompatTextView = onView(allOf(withId(R.id.CardEditorCardsText), withSubstring("Cards: Card 1"))); appCompatTextView.perform(scrollTo(), click()); ViewInteraction actionMenuItemView = onView( diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/uitests/CardBrowserPreviewUI.java b/AnkiDroid/src/androidTest/java/com/ichi2/anki/uitests/CardBrowserPreviewUI.java new file mode 100644 index 0000000000..8f07d164c3 --- /dev/null +++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/uitests/CardBrowserPreviewUI.java @@ -0,0 +1,320 @@ +package com.ichi2.anki.uitests; + + +import android.Manifest; +import androidx.test.espresso.ViewInteraction; +import androidx.test.espresso.matcher.ViewMatchers; +import androidx.test.rule.ActivityTestRule; +import androidx.test.rule.GrantPermissionRule; +import androidx.test.runner.AndroidJUnit4; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; + +import com.azimolabs.conditionwatcher.ConditionWatcher; +import com.ichi2.anki.IntentHandler; +import com.ichi2.anki.R; +import com.ichi2.anki.tests.Shared; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; +import org.hamcrest.TypeSafeMatcher; +import org.hamcrest.core.IsInstanceOf; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static androidx.test.InstrumentationRegistry.getInstrumentation; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu; +import static androidx.test.espresso.Espresso.pressBack; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard; +import static androidx.test.espresso.action.ViewActions.pressImeActionButton; +import static androidx.test.espresso.action.ViewActions.replaceText; +import static androidx.test.espresso.action.ViewActions.scrollTo; +import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription; +import static androidx.test.espresso.matcher.ViewMatchers.withText; + +@androidx.test.filters.LargeTest +@SuppressWarnings("PMD.ExcessiveMethodLength") +@RunWith(AndroidJUnit4.class) +public class CardBrowserPreviewUI { + + @Rule + public GrantPermissionRule mRuntimePermissionRule = + GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE); + + @Rule + public ActivityTestRule mActivityTestRule = new ActivityTestRule<>(IntentHandler.class); + + + @Test + public void cardBrowserPreview3UI() throws Exception { + ViewInteraction appCompatImageButton = onView( + Matchers.allOf(withContentDescription("Navigate up"), + childAtPosition( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.toolbar), + childAtPosition( + ViewMatchers.withId(com.ichi2.anki.R.id.deckpicker_view), + 0)), + 0), + isDisplayed())); + appCompatImageButton.perform(click()); + + ViewInteraction navigationMenuItemView = onView( + Matchers.allOf(childAtPosition( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.design_navigation_view), + childAtPosition( + ViewMatchers.withId(com.ichi2.anki.R.id.navdrawer_items_container), + 0)), + 2), + isDisplayed())); + navigationMenuItemView.perform(click()); + + ConditionWatcher.waitForCondition(new Shared.ViewItemWaitingInstruction(com.ichi2.anki.R.id.action_add_card_from_card_browser)); + ViewInteraction actionMenuItemView = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.action_add_card_from_card_browser), withContentDescription("Add note"), + childAtPosition( + childAtPosition( + ViewMatchers.withId(com.ichi2.anki.R.id.toolbar), + 3), + 0), + isDisplayed())); + actionMenuItemView.perform(click()); + + ConditionWatcher.waitForCondition(new Shared.ViewItemWaitingInstruction(com.ichi2.anki.R.id.id_note_editText)); + ViewInteraction fieldEditText = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.id_note_editText), withContentDescription("Front"), + childAtPosition( + childAtPosition( + ViewMatchers.withId(com.ichi2.anki.R.id.CardEditorEditFieldsLayout), + 1), + 0))); + String cardId = "test" + Long.toString(System.currentTimeMillis()); + fieldEditText.perform(scrollTo(), replaceText(cardId), closeSoftKeyboard()); + + ViewInteraction fieldEditText2 = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.id_note_editText), withContentDescription("Back"), + childAtPosition( + childAtPosition( + ViewMatchers.withId(com.ichi2.anki.R.id.CardEditorEditFieldsLayout), + 3), + 0))); + fieldEditText2.perform(scrollTo(), replaceText("back"), closeSoftKeyboard()); + + ViewInteraction actionMenuItemView2 = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.action_save), withContentDescription("Save"), + childAtPosition( + childAtPosition( + ViewMatchers.withId(com.ichi2.anki.R.id.toolbar), + 4), + 0), + isDisplayed())); + actionMenuItemView2.perform(click()); + + pressBack(); + + ConditionWatcher.waitForCondition(new Shared.ViewItemWaitingInstruction(com.ichi2.anki.R.id.action_search)); + ViewInteraction actionMenuItemView3 = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.action_search), withContentDescription("Search"), + childAtPosition( + childAtPosition( + ViewMatchers.withId(com.ichi2.anki.R.id.toolbar), + 3), + 1), + isDisplayed())); + actionMenuItemView3.perform(click()); + + ConditionWatcher.waitForCondition(new Shared.ViewItemWaitingInstruction(com.ichi2.anki.R.id.search_src_text)); + ViewInteraction searchAutoComplete = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.search_src_text), + childAtPosition( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.search_plate), + childAtPosition( + ViewMatchers.withId(com.ichi2.anki.R.id.search_edit_frame), + 1)), + 0), + isDisplayed())); + searchAutoComplete.perform(replaceText(cardId + "bad"), closeSoftKeyboard()); + + ViewInteraction searchAutoComplete2 = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.search_src_text), withText(cardId + "bad"), + childAtPosition( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.search_plate), + childAtPosition( + ViewMatchers.withId(com.ichi2.anki.R.id.search_edit_frame), + 1)), + 0), + isDisplayed())); + searchAutoComplete2.perform(pressImeActionButton()); + + openActionBarOverflowOrOptionsMenu(getInstrumentation().getTargetContext()); + ViewInteraction linearLayout42 = onView( + Matchers.allOf(withText("Preview"), + childAtPosition( + IsInstanceOf.instanceOf(android.widget.FrameLayout.class), + 0), + isDisplayed())); + linearLayout42.check(doesNotExist()); + pressBack(); + + ConditionWatcher.waitForCondition(new Shared.ViewItemWaitingInstruction(com.ichi2.anki.R.id.search_close_btn)); + ViewInteraction appCompatImageView = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.search_close_btn), withContentDescription("Clear query"), + childAtPosition( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.search_plate), + childAtPosition( + ViewMatchers.withId(com.ichi2.anki.R.id.search_edit_frame), + 1)), + 1), + isDisplayed())); + appCompatImageView.perform(click()); + + ViewInteraction searchAutoComplete3 = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.search_src_text), + childAtPosition( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.search_plate), + childAtPosition( + ViewMatchers.withId(com.ichi2.anki.R.id.search_edit_frame), + 1)), + 0), + isDisplayed())); + searchAutoComplete3.perform(replaceText(cardId), closeSoftKeyboard()); + + ViewInteraction searchAutoComplete4 = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.search_src_text), withText(cardId), + childAtPosition( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.search_plate), + childAtPosition( + ViewMatchers.withId(com.ichi2.anki.R.id.search_edit_frame), + 1)), + 0), + isDisplayed())); + searchAutoComplete4.perform(pressImeActionButton()); + Thread.sleep(200); + + openActionBarOverflowOrOptionsMenu(getInstrumentation().getTargetContext()); + Thread.sleep(200); + + ViewInteraction appCompatTextView = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.title), withText("Preview"), + isDisplayed())); + Thread.sleep(200); + + appCompatTextView.perform(click()); + Thread.sleep(200); + + ConditionWatcher.waitForCondition(new Shared.ViewItemWaitingInstruction(com.ichi2.anki.R.id.flashcard_layout_ease1)); + ViewInteraction textView = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.nextTime1), withText("-"), + isDisplayed())); + textView.check(matches(withText("-"))); + + ViewInteraction textView2 = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.nextTime2), withText(">"), + isDisplayed())); + textView2.check(matches(withText(">"))); + + ViewInteraction linearLayout = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.flashcard_layout_ease2), + childAtPosition( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.answer_options_layout), + childAtPosition( + ViewMatchers.withId(com.ichi2.anki.R.id.bottom_area_layout), + 1)), + 2), + isDisplayed())); + linearLayout.perform(click()); + + ViewInteraction textView3 = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.nextTime1), withText("<"), + isDisplayed())); + textView3.check(matches(withText("<"))); + + ViewInteraction textView4 = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.nextTime2), withText(">"), + isDisplayed())); + textView4.check(matches(withText(">"))); + + ViewInteraction linearLayout2 = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.flashcard_layout_ease2), + isDisplayed())); + linearLayout2.perform(click()); + + ViewInteraction linearLayout3 = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.flashcard_layout_ease2), + isDisplayed())); + linearLayout3.perform(click()); + + ViewInteraction textView5 = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.nextTime1), withText("<"), + isDisplayed())); + textView5.check(matches(withText("<"))); + + ViewInteraction textView6 = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.nextTime2), withText("-"), + isDisplayed())); + textView6.check(matches(withText("-"))); + + ViewInteraction linearLayout4 = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.flashcard_layout_ease1), + isDisplayed())); + linearLayout4.perform(click()); + + ViewInteraction linearLayout5 = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.flashcard_layout_ease1), + isDisplayed())); + linearLayout5.perform(click()); + + ViewInteraction linearLayout6 = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.flashcard_layout_ease1), + isDisplayed())); + linearLayout6.perform(click()); + + ViewInteraction textView8 = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.nextTime1), withText("-"), + isDisplayed())); + textView8.check(matches(withText("-"))); + + ViewInteraction textView9 = onView( + Matchers.allOf(ViewMatchers.withId(com.ichi2.anki.R.id.nextTime2), withText(">"), + isDisplayed())); + textView9.check(matches(withText(">"))); + pressBack(); + + ConditionWatcher.waitForCondition(new Shared.ViewItemWaitingInstruction(com.ichi2.anki.R.id.search_src_text)); + ViewInteraction searchAutoComplete42 = onView( + Matchers.allOf(ViewMatchers.withId(R.id.search_src_text), + isDisplayed())); + searchAutoComplete42.check(matches(withText(cardId))); + Assert.assertNotNull("Did not make it to the end of the UI run", searchAutoComplete42); + + } + + + private static Matcher childAtPosition( + final Matcher parentMatcher, final int position) { + + return new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("Child at position " + position + " in parent "); + parentMatcher.describeTo(description); + } + + + @Override + public boolean matchesSafely(View view) { + ViewParent parent = view.getParent(); + return parent instanceof ViewGroup && parentMatcher.matches(parent) + && view.equals(((ViewGroup) parent).getChildAt(position)); + } + }; + } +}