From d097d12d38c4f2c40d036e1002227825abdc92bd Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 13 May 2020 08:48:25 +0530 Subject: [PATCH 1/3] Add Espresso dependencies and instrumented test configuration. --- android/BOINC/app/build.gradle | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/android/BOINC/app/build.gradle b/android/BOINC/app/build.gradle index ee0cabe49cf..77ba92b8a41 100644 --- a/android/BOINC/app/build.gradle +++ b/android/BOINC/app/build.gradle @@ -79,6 +79,8 @@ android { // Required when setting minSdkVersion to 20 or lower multiDexEnabled true vectorDrawables.useSupportLibrary = true + + testInstrumentationRunner = 'androidx.test.runner.AndroidJUnitRunner' } buildTypes { @@ -167,6 +169,10 @@ dependencies { testImplementation 'org.robolectric:robolectric:4.3.1' testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit5_version" testRuntimeOnly "org.junit.vintage:junit-vintage-engine:$junit5_version" + + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test:rules:1.3.0' } repositories { mavenCentral() From 57d71cceb72ee477161c8d5fc14a9e2d5ba5682a Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 13 May 2020 09:01:37 +0530 Subject: [PATCH 2/3] Add test for navigating to EventLogActivity from SplashActivity. --- .../boinc/SplashActivityToEventLogTest.kt | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 android/BOINC/app/src/androidTest/java/edu/berkeley/boinc/SplashActivityToEventLogTest.kt diff --git a/android/BOINC/app/src/androidTest/java/edu/berkeley/boinc/SplashActivityToEventLogTest.kt b/android/BOINC/app/src/androidTest/java/edu/berkeley/boinc/SplashActivityToEventLogTest.kt new file mode 100644 index 00000000000..aab615aea97 --- /dev/null +++ b/android/BOINC/app/src/androidTest/java/edu/berkeley/boinc/SplashActivityToEventLogTest.kt @@ -0,0 +1,105 @@ +package edu.berkeley.boinc + +import android.view.View +import android.view.ViewGroup +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.longClick +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.* +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import org.hamcrest.Description +import org.hamcrest.Matcher +import org.hamcrest.Matchers.allOf +import org.hamcrest.TypeSafeMatcher +import org.hamcrest.core.IsInstanceOf +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@LargeTest +@RunWith(AndroidJUnit4::class) +class SplashActivityToEventLogTest { + @Rule + @JvmField + var mActivityScenarioRule = ActivityScenarioRule(SplashActivity::class.java) + + @Test + fun splashActivityToEventLogTest() { + val imageView = onView( + allOf(withId(R.id.logo), withContentDescription("Starting…"), + childAtPosition( + childAtPosition( + withId(android.R.id.content), + 0), + 0), + isDisplayed())) + imageView.perform(longClick()) + + val textView = onView( + allOf(withId(R.id.refresh), withContentDescription("Refresh"), + childAtPosition( + childAtPosition( + withId(R.id.action_bar), + 2), + 0), + isDisplayed())) + textView.check(matches(isDisplayed())) + + val textView2 = onView( + allOf(withId(R.id.email_to), withContentDescription("Send as Email"), + childAtPosition( + childAtPosition( + withId(R.id.action_bar), + 2), + 1), + isDisplayed())) + textView2.check(matches(isDisplayed())) + + val textView3 = onView( + allOf(withId(R.id.copy), withContentDescription("Copy to Clipboard"), + childAtPosition( + childAtPosition( + withId(R.id.action_bar), + 2), + 2), + isDisplayed())) + textView3.check(matches(isDisplayed())) + + val textView4 = onView( + allOf(withText("CLIENT MESSAGES"), + childAtPosition( + childAtPosition( + IsInstanceOf.instanceOf(androidx.appcompat.widget.LinearLayoutCompat::class.java), + 0), + 0), + isDisplayed())) + textView4.check(matches(withText("CLIENT MESSAGES"))) + + val textView5 = onView( + allOf(withText("GUI MESSAGES"), + childAtPosition( + childAtPosition( + IsInstanceOf.instanceOf(androidx.appcompat.widget.LinearLayoutCompat::class.java), + 1), + 0), + isDisplayed())) + textView5.check(matches(withText("GUI MESSAGES"))) + } + + private fun childAtPosition(parentMatcher: Matcher, position: Int): Matcher { + return object : TypeSafeMatcher() { + override fun describeTo(description: Description) { + description.appendText("Child at position $position in parent ") + parentMatcher.describeTo(description) + } + + public override fun matchesSafely(view: View): Boolean { + val parent = view.parent + return parent is ViewGroup && parentMatcher.matches(parent) + && view == parent.getChildAt(position) + } + } + } +} From cc6e86a3c0f9d92e494082cce0563fae298d78f4 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 2 Sep 2020 06:43:36 +0530 Subject: [PATCH 3/3] Add test for BOINCActivity and its fragments. --- .../edu/berkeley/boinc/BOINCActivityTest.kt | 144 ++++++++++++++++++ .../boinc/SplashActivityToEventLogTest.kt | 20 --- .../java/edu/berkeley/boinc/TestUtils.kt | 22 +++ 3 files changed, 166 insertions(+), 20 deletions(-) create mode 100644 android/BOINC/app/src/androidTest/java/edu/berkeley/boinc/BOINCActivityTest.kt create mode 100644 android/BOINC/app/src/androidTest/java/edu/berkeley/boinc/TestUtils.kt diff --git a/android/BOINC/app/src/androidTest/java/edu/berkeley/boinc/BOINCActivityTest.kt b/android/BOINC/app/src/androidTest/java/edu/berkeley/boinc/BOINCActivityTest.kt new file mode 100644 index 00000000000..369549b193d --- /dev/null +++ b/android/BOINC/app/src/androidTest/java/edu/berkeley/boinc/BOINCActivityTest.kt @@ -0,0 +1,144 @@ +package edu.berkeley.boinc + +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.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import org.hamcrest.Matchers.allOf +import org.hamcrest.core.IsInstanceOf +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +// Disable BOINC notifications (or just the main channel on Oreo and higher) before running this test, as +// the client notification can cover the hamburger button during test execution and cause the test to fail. +@LargeTest +@RunWith(AndroidJUnit4::class) +class BOINCActivityTest { + @Rule + @JvmField + var mActivityScenarioRule = ActivityScenarioRule(BOINCActivity::class.java) + + @Test + fun boincActivityTest() { + val textView = onView( + allOf(withText(R.string.tab_tasks), + withParent(allOf(withId(R.id.action_bar), + withParent(withId(R.id.action_bar_container)))), + isDisplayed())) + textView.check(matches(withText(R.string.tab_tasks))) + + val appCompatImageButton = onView( + allOf(withContentDescription("BOINC"), + childAtPosition( + allOf(withId(R.id.action_bar), + childAtPosition( + withId(R.id.action_bar_container), + 0)), + 1), + isDisplayed())) + appCompatImageButton.perform(click()) + + val viewInteraction = onView(allOf(withText(R.string.tab_notices), isDisplayed())) + viewInteraction.perform(click()) + + val textView2 = onView( + allOf(withText(R.string.tab_notices), + withParent(allOf(withId(R.id.action_bar), + withParent(withId(R.id.action_bar_container)))), + isDisplayed())) + textView2.check(matches(withText(R.string.tab_notices))) + + appCompatImageButton.perform(click()) + + val viewInteraction2 = onView(allOf(withText(R.string.tab_projects), isDisplayed())) + viewInteraction2.perform(click()) + + val textView3 = onView( + allOf(withText(R.string.tab_projects), + withParent(allOf(withId(R.id.action_bar), + withParent(withId(R.id.action_bar_container)))), + isDisplayed())) + textView3.check(matches(withText(R.string.tab_projects))) + + appCompatImageButton.perform(click()) + + val viewInteraction3 = onView(allOf(withText(R.string.tab_preferences), isDisplayed())) + viewInteraction3.perform(click()) + + val textView4 = onView( + allOf(withText(R.string.tab_preferences), + withParent(allOf(withId(R.id.action_bar), + withParent(withId(R.id.action_bar_container)))), + isDisplayed())) + textView4.check(matches(withText(R.string.tab_preferences))) + + appCompatImageButton.perform(click()) + + val viewInteraction4 = onView(allOf(withText(R.string.menu_about), isDisplayed())) + viewInteraction4.perform(click()) + + val textView5 = onView( + allOf(withId(R.id.title), withText(R.string.menu_about), + withParent(withParent(withId(android.R.id.content))), + isDisplayed())) + textView5.check(matches(withText(R.string.menu_about))) + + val appCompatButton = onView( + allOf(withId(R.id.returnB), withText(R.string.about_button), + childAtPosition( + childAtPosition( + withId(android.R.id.content), + 0), + 6), + isDisplayed())) + appCompatButton.perform(click()) + + appCompatImageButton.perform(click()) + + val viewInteraction5 = onView(allOf(withText(R.string.menu_eventlog), isDisplayed())) + viewInteraction5.perform(click()) + + val textView6 = onView( + allOf(withText(R.string.menu_eventlog), + withParent(allOf(withId(R.id.action_bar), + withParent(withId(R.id.action_bar_container)))), + isDisplayed())) + textView6.check(matches(withText(R.string.menu_eventlog))) + + val textView7 = onView( + allOf(withText(R.string.eventlog_client_header), + withParent(allOf(withContentDescription(R.string.eventlog_client_header), + withParent(IsInstanceOf.instanceOf(android.widget.LinearLayout::class.java)))), + isDisplayed())) + textView7.check(matches(withText(R.string.eventlog_client_header))) + + val textView8 = onView( + allOf(withText(R.string.eventlog_gui_header), + withParent(allOf(withContentDescription(R.string.eventlog_gui_header), + withParent(IsInstanceOf.instanceOf(android.widget.LinearLayout::class.java)))), + isDisplayed())) + textView8.check(matches(withText(R.string.eventlog_gui_header))) + + val appCompatImageButton2 = onView( + allOf(withContentDescription("Navigate up"), + childAtPosition( + allOf(withId(R.id.action_bar), + childAtPosition( + withId(R.id.action_bar_container), + 0)), + 1), + isDisplayed())) + appCompatImageButton2.perform(click()) + + val textView9 = onView( + allOf(withText(R.string.tab_tasks), + withParent(allOf(withId(R.id.action_bar), + withParent(withId(R.id.action_bar_container)))), + isDisplayed())) + textView9.check(matches(withText(R.string.tab_tasks))) + } +} diff --git a/android/BOINC/app/src/androidTest/java/edu/berkeley/boinc/SplashActivityToEventLogTest.kt b/android/BOINC/app/src/androidTest/java/edu/berkeley/boinc/SplashActivityToEventLogTest.kt index aab615aea97..8718d3d9476 100644 --- a/android/BOINC/app/src/androidTest/java/edu/berkeley/boinc/SplashActivityToEventLogTest.kt +++ b/android/BOINC/app/src/androidTest/java/edu/berkeley/boinc/SplashActivityToEventLogTest.kt @@ -1,7 +1,5 @@ package edu.berkeley.boinc -import android.view.View -import android.view.ViewGroup import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.longClick import androidx.test.espresso.assertion.ViewAssertions.matches @@ -9,10 +7,7 @@ import androidx.test.espresso.matcher.ViewMatchers.* import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest -import org.hamcrest.Description -import org.hamcrest.Matcher import org.hamcrest.Matchers.allOf -import org.hamcrest.TypeSafeMatcher import org.hamcrest.core.IsInstanceOf import org.junit.Rule import org.junit.Test @@ -87,19 +82,4 @@ class SplashActivityToEventLogTest { isDisplayed())) textView5.check(matches(withText("GUI MESSAGES"))) } - - private fun childAtPosition(parentMatcher: Matcher, position: Int): Matcher { - return object : TypeSafeMatcher() { - override fun describeTo(description: Description) { - description.appendText("Child at position $position in parent ") - parentMatcher.describeTo(description) - } - - public override fun matchesSafely(view: View): Boolean { - val parent = view.parent - return parent is ViewGroup && parentMatcher.matches(parent) - && view == parent.getChildAt(position) - } - } - } } diff --git a/android/BOINC/app/src/androidTest/java/edu/berkeley/boinc/TestUtils.kt b/android/BOINC/app/src/androidTest/java/edu/berkeley/boinc/TestUtils.kt new file mode 100644 index 00000000000..710cb83880c --- /dev/null +++ b/android/BOINC/app/src/androidTest/java/edu/berkeley/boinc/TestUtils.kt @@ -0,0 +1,22 @@ +package edu.berkeley.boinc + +import android.view.View +import android.view.ViewGroup +import org.hamcrest.Description +import org.hamcrest.Matcher +import org.hamcrest.TypeSafeMatcher + +fun childAtPosition(parentMatcher: Matcher, position: Int): Matcher { + return object : TypeSafeMatcher() { + override fun describeTo(description: Description) { + description.appendText("Child at position $position in parent ") + parentMatcher.describeTo(description) + } + + public override fun matchesSafely(view: View): Boolean { + val parent = view.parent + return parent is ViewGroup && parentMatcher.matches(parent) + && view == parent.getChildAt(position) + } + } +}