1
0
Fork 0

Created UI tests for Addon Settings (#9258)

master
Kadeem M 2020-07-27 11:00:35 -04:00 committed by GitHub
parent 9c4fba4565
commit 1353e157cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 544 additions and 9 deletions

View File

@ -0,0 +1,26 @@
package org.mozilla.fenix.helpers
import androidx.test.espresso.IdlingRegistry
import androidx.test.rule.ActivityTestRule
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.helpers.idlingresource.AddonsInstallingIdlingResource
object IdlingResourceHelper {
// Idling Resource to manage installing an addon
fun registerAddonInstallingIdlingResource(activityTestRule: ActivityTestRule<HomeActivity>) {
IdlingRegistry.getInstance().register(
AddonsInstallingIdlingResource(
activityTestRule.activity.supportFragmentManager
)
)
}
fun unregisterAddonInstallingIdlingResource(activityTestRule: ActivityTestRule<HomeActivity>) {
IdlingRegistry.getInstance().unregister(
AddonsInstallingIdlingResource(
activityTestRule.activity.supportFragmentManager
)
)
}
}

View File

@ -0,0 +1,42 @@
package org.mozilla.fenix.helpers.idlingresource
import androidx.fragment.app.FragmentManager
import androidx.navigation.fragment.NavHostFragment
import androidx.test.espresso.IdlingResource
import mozilla.components.feature.addons.ui.AddonInstallationDialogFragment
class AddonsInstallingIdlingResource(
val fragmentManager: FragmentManager
) :
IdlingResource {
private var resourceCallback: IdlingResource.ResourceCallback? = null
private var isAddonInstalled = false
override fun getName(): String {
return this::javaClass.name
}
override fun isIdleNow(): Boolean {
return isInstalledAddonDialogShown()
}
override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback?) {
if (callback != null)
resourceCallback = callback
}
private fun isInstalledAddonDialogShown(): Boolean {
val activityChildFragments =
(fragmentManager.fragments.first() as NavHostFragment)
.childFragmentManager.fragments
for (childFragment in activityChildFragments.indices) {
if (activityChildFragments[childFragment] is AddonInstallationDialogFragment) {
resourceCallback?.onTransitionToIdle()
isAddonInstalled = true
return isAddonInstalled
}
}
return isAddonInstalled
}
}

View File

@ -0,0 +1,41 @@
package org.mozilla.fenix.helpers.idlingresource
import android.view.View
import android.view.View.VISIBLE
import androidx.fragment.app.FragmentManager
import androidx.test.espresso.IdlingResource
import org.mozilla.fenix.R
import org.mozilla.fenix.addons.AddonsManagementFragment
class AddonsLoadingIdlingResource(val fragmentManager: FragmentManager) : IdlingResource {
private var resourceCallback: IdlingResource.ResourceCallback? = null
override fun getName(): String {
return this::javaClass.name
}
override fun isIdleNow(): Boolean {
val idle = addonsFinishedLoading()
if (idle)
resourceCallback?.onTransitionToIdle()
return idle
}
override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback?) {
if (callback != null)
resourceCallback = callback
}
private fun addonsFinishedLoading(): Boolean {
val progressbar = fragmentManager.findFragmentById(R.id.container)?.let {
val addonsManagementFragment =
it.childFragmentManager.fragments.first { it is AddonsManagementFragment }
addonsManagementFragment.view?.findViewById<View>(R.id.add_ons_progress_bar)
} ?: return true
if (progressbar.visibility == VISIBLE)
return false
return true
}
}

View File

@ -0,0 +1,96 @@
package org.mozilla.fenix.ui
import org.mozilla.fenix.helpers.TestAssetHelper
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import okhttp3.mockwebserver.MockWebServer
import org.junit.Rule
import org.junit.Before
import org.junit.After
import org.junit.Test
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
/**
* Tests for verifying the functionality of installing or removing addons
*
*/
class SettingsAddonsTest {
/* ktlint-disable no-blank-line-before-rbrace */ // This imposes unreadable grouping.
private lateinit var mockWebServer: MockWebServer
@get:Rule
val activityTestRule = HomeActivityTestRule()
@Before
fun setUp() {
mockWebServer = MockWebServer().apply {
setDispatcher(AndroidAssetDispatcher())
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
// Walks through settings add-ons menu to ensure all items are present
@Test
fun settingsAddonsItemsTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
verifyAdvancedHeading()
verifyAddons()
}.openAddonsManagerMenu {
verifyAddonsItems()
}
}
// Opens a webpage and installs an add-on from the three-dot menu
@Test
fun installAddonFromThreeDotMenu() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val addonName = "uBlock Origin"
navigationToolbar {
}.openNewTabAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
}.openAddonsManagerMenu {
clickInstallAddon(addonName)
verifyAddonPrompt(addonName)
cancelInstallAddon()
clickInstallAddon(addonName)
acceptInstallAddon()
verifyDownloadAddonPrompt(addonName, activityTestRule)
}
}
// Opens the addons settings menu, installs an addon, then uninstalls
@Test
fun verifyAddonsCanBeUninstalled() {
val addonName = "uBlock Origin"
homeScreen {
}.openThreeDotMenu {
}.openSettings {
verifyAdvancedHeading()
verifyAddons()
}.openAddonsManagerMenu {
clickInstallAddon(addonName)
acceptInstallAddon()
verifyDownloadAddonPrompt(addonName, activityTestRule)
}.openDetailedMenuForAddon(addonName) {
verifyCurrentAddonMenu()
}.removeAddon {
}
}
}

View File

@ -19,6 +19,7 @@ import androidx.test.espresso.intent.matcher.IntentMatchers.toPackage
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.Visibility
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
@ -74,7 +75,9 @@ class SettingsRobot {
// ADVANCED SECTION
fun verifyAdvancedHeading() = assertAdvancedHeading()
fun verifyAddons() = assertAddons()
fun verifyAddons() = assertAddonsButton()
// DEVELOPER TOOLS SECTION
fun verifyRemoteDebug() = assertRemoteDebug()
fun verifyLeakCanaryButton() = assertLeakCanaryButton()
@ -211,6 +214,13 @@ class SettingsRobot {
SettingsSubMenuDataCollectionRobot().interact()
return SettingsSubMenuDataCollectionRobot.Transition()
}
fun openAddonsManagerMenu(interact: SettingsSubMenuAddonsManagerRobot.() -> Unit): SettingsSubMenuAddonsManagerRobot.Transition {
addonsManagerButton().click()
SettingsSubMenuAddonsManagerRobot().interact()
return SettingsSubMenuAddonsManagerRobot.Transition()
}
}
companion object {
@ -349,15 +359,25 @@ private fun assertDeveloperToolsHeading() {
// ADVANCED SECTION
private fun assertAdvancedHeading() {
scrollToElementByText("Advanced")
onView(withText("Advanced"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
onView(withId(R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Add-ons"))
)
)
onView(withText("Add-ons"))
.check(matches(isCompletelyDisplayed()))
}
private fun assertAddons() {
scrollToElementByText("Add-ons")
onView(withText("Add-ons"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertAddonsButton() {
onView(withId(R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Add-ons"))
)
)
addonsManagerButton()
.check(matches(isCompletelyDisplayed()))
}
private fun assertRemoteDebug() {
@ -414,5 +434,7 @@ fun isPackageInstalled(packageName: String): Boolean {
}
}
private fun addonsManagerButton() = onView(withText(R.string.preferences_addons))
private fun goBackButton() =
onView(CoreMatchers.allOf(ViewMatchers.withContentDescription("Navigate up")))

View File

@ -0,0 +1,59 @@
package org.mozilla.fenix.ui.robots
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withId
import org.hamcrest.CoreMatchers.allOf
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.click
/**
* Implementation of Robot Pattern for a single Addon inside of the Addons Management Settings.
*/
class SettingsSubMenuAddonsManagerAddonDetailedMenuRobot {
fun verifyCurrentAddonMenu() = assertAddonMenuItems()
class Transition {
fun goBack(interact: SettingsSubMenuAddonsManagerRobot.() -> Unit): SettingsSubMenuAddonsManagerRobot.Transition {
fun goBackButton() = onView(allOf(withContentDescription("Navigate up")))
goBackButton().click()
SettingsSubMenuAddonsManagerRobot().interact()
return SettingsSubMenuAddonsManagerRobot.Transition()
}
fun removeAddon(interact: SettingsSubMenuAddonsManagerRobot.() -> Unit): SettingsSubMenuAddonsManagerRobot.Transition {
removeAddonButton().click()
SettingsSubMenuAddonsManagerRobot().interact()
return SettingsSubMenuAddonsManagerRobot.Transition()
}
}
private fun assertAddonMenuItems() {
enableSwitchButton().check(matches(isCompletelyDisplayed()))
settingsButton().check(matches(isCompletelyDisplayed()))
detailsButton().check(matches(isCompletelyDisplayed()))
permissionsButton().check(matches(isCompletelyDisplayed()))
removeAddonButton().check(matches(isCompletelyDisplayed()))
}
}
private fun enableSwitchButton() =
onView(withId(R.id.enable_switch))
private fun settingsButton() =
onView(withId(R.id.settings))
private fun detailsButton() =
onView(withId(R.id.details))
private fun permissionsButton() =
onView(withId(R.id.permissions))
private fun removeAddonButton() =
onView(withId(R.id.remove_add_on))

View File

@ -0,0 +1,228 @@
package org.mozilla.fenix.ui.robots
import android.widget.RelativeLayout
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.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.Visibility
import androidx.test.espresso.matcher.ViewMatchers.withParent
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.containsString
import org.hamcrest.CoreMatchers.instanceOf
import org.hamcrest.CoreMatchers.not
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.IdlingResourceHelper.registerAddonInstallingIdlingResource
import org.mozilla.fenix.helpers.IdlingResourceHelper.unregisterAddonInstallingIdlingResource
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull
/**
* Implementation of Robot Pattern for the Addons Management Settings.
*/
class SettingsSubMenuAddonsManagerRobot {
fun verifyAddonPrompt(addonName: String) = assertAddonPrompt(addonName)
fun clickInstallAddon(addonName: String) = selectInstallAddon(addonName)
fun verifyDownloadAddonPrompt(addonName: String, activityTestRule: HomeActivityTestRule) =
assertDownloadingAddonPrompt(addonName, activityTestRule)
fun cancelInstallAddon() = cancelInstall()
fun acceptInstallAddon() = allowInstall()
fun verifyAddonsItems() = assertAddonsItems()
fun verifyAddonCanBeInstalled(addonName: String) = assertAddonCanBeInstalled(addonName)
class Transition {
fun goBack(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
fun goBackButton() = onView(allOf(withContentDescription("Navigate up")))
goBackButton().click()
HomeScreenRobot().interact()
return HomeScreenRobot.Transition()
}
fun openDetailedMenuForAddon(
addonName: String,
interact: SettingsSubMenuAddonsManagerAddonDetailedMenuRobot.() -> Unit
): SettingsSubMenuAddonsManagerAddonDetailedMenuRobot.Transition {
addonName.chars()
onView(
allOf(
withId(R.id.add_on_item),
hasDescendant(
allOf(
withId(R.id.add_on_name),
withText(addonName)
)
)
)
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
.perform(click())
SettingsSubMenuAddonsManagerAddonDetailedMenuRobot().interact()
return SettingsSubMenuAddonsManagerAddonDetailedMenuRobot.Transition()
}
}
private fun installButtonForAddon(addonName: String) =
onView(
allOf(
withContentDescription(R.string.mozac_feature_addons_install_addon_content_description),
isDescendantOfA(withId(R.id.add_on_item)),
hasSibling(hasDescendant(withText(addonName)))
)
)
private fun selectInstallAddon(addonName: String) {
mDevice.waitNotNull(
Until.findObject(By.textContains(addonName)),
waitingTime
)
installButtonForAddon(addonName)
.check(matches(isCompletelyDisplayed()))
.perform(click())
}
private fun assertAddonIsEnabled(addonName: String) {
installButtonForAddon(addonName)
.check(matches(not(isCompletelyDisplayed())))
}
private fun assertAddonPrompt(addonName: String) {
onView(allOf(withId(R.id.title), withText("Add $addonName?")))
.check(matches(isCompletelyDisplayed()))
onView(
allOf(
withId(R.id.permissions),
withText(containsString("It requires your permission to:"))
)
)
.check(matches(isCompletelyDisplayed()))
onView(allOf(withId(R.id.allow_button), withText("Add")))
.check(matches(isCompletelyDisplayed()))
onView(allOf(withId(R.id.deny_button), withText("Cancel")))
.check(matches(isCompletelyDisplayed()))
}
private fun assertDownloadingAddonPrompt(
addonName: String,
activityTestRule: HomeActivityTestRule
) {
registerAddonInstallingIdlingResource(activityTestRule)
onView(
allOf(
withText("Okay, Got it"),
withParent(instanceOf(RelativeLayout::class.java)),
hasSibling(withText("$addonName has been added to Firefox Preview")),
hasSibling(withText("Open it in the menu")),
hasSibling(withText("Allow in private browsing"))
)
)
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
.perform(click())
unregisterAddonInstallingIdlingResource(activityTestRule)
TestHelper.scrollToElementByText(addonName)
assertAddonIsInstalled(addonName)
}
private fun assertAddonIsInstalled(addonName: String) {
onView(
allOf(
withId(R.id.add_button),
isDescendantOfA(withId(R.id.add_on_item)),
hasSibling(hasDescendant(withText(addonName)))
)
).check(matches(withEffectiveVisibility(Visibility.GONE)))
}
private fun cancelInstall() {
onView(allOf(withId(R.id.deny_button), withText("Cancel")))
.check(matches(isCompletelyDisplayed()))
.perform(click())
}
private fun allowInstall() {
onView(allOf(withId(R.id.allow_button), withText("Add")))
.check(matches(isCompletelyDisplayed()))
.perform(click())
}
private fun assertAddonsItems() {
assertRecommendedTitleDisplayed()
assertAddons()
}
private fun assertRecommendedTitleDisplayed() {
onView(allOf(withId(R.id.title), withText("Recommended")))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertEnabledTitleDisplayed() {
onView(withText("Enabled"))
.check(matches(isCompletelyDisplayed()))
}
private fun assertAddons() {
assertAddonUblock()
}
private fun assertAddonUblock() {
onView(
allOf(
isAssignableFrom(RelativeLayout::class.java),
withId(R.id.add_on_item),
hasDescendant(allOf(withId(R.id.add_on_icon), isCompletelyDisplayed())),
hasDescendant(
allOf(
withId(R.id.details_container),
hasDescendant(withText("uBlock Origin")),
hasDescendant(withText("Finally, an efficient wide-spectrum content blocker. Easy on CPU and memory.")),
hasDescendant(withId(R.id.rating)),
hasDescendant(withId(R.id.users_count))
)
),
hasDescendant(withId(R.id.add_button))
)
).check(matches(isCompletelyDisplayed()))
}
private fun assertAddonCanBeInstalled(addonName: String) {
device.waitNotNull(Until.findObject(By.text(addonName)), waitingTime)
onView(
allOf(
withId(R.id.add_button),
hasSibling(
hasDescendant(
allOf(
withId(R.id.add_on_name),
withText(addonName)
)
)
)
)
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
}

View File

@ -347,6 +347,17 @@ class ThreeDotMenuMainRobot {
ThreeDotMenuMainRobot().interact()
return ThreeDotMenuMainRobot.Transition()
}
fun openAddonsManagerMenu(interact: SettingsSubMenuAddonsManagerRobot.() -> Unit): SettingsSubMenuAddonsManagerRobot.Transition {
clickAddonsManagerButton()
mDevice.waitNotNull(
Until.findObject(By.text("Recommended")),
waitingTime
)
SettingsSubMenuAddonsManagerRobot().interact()
return SettingsSubMenuAddonsManagerRobot.Transition()
}
}
}
@ -423,7 +434,9 @@ private fun addNewCollectionButton() = onView(allOf(withText("Add new collection
private fun assertaddNewCollectionButton() = addNewCollectionButton()
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun collectionNameTextField() = onView(allOf(withResourceName("name_collection_edittext")))
private fun collectionNameTextField() =
onView(allOf(withResourceName("name_collection_edittext")))
private fun assertCollectionNameTextField() = collectionNameTextField()
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
@ -473,6 +486,7 @@ private fun assertReaderViewAppearanceButton(visible: Boolean) = readerViewAppea
private fun addToFirefoxHomeButton() =
onView(allOf(withText(R.string.browser_menu_add_to_top_sites)))
private fun assertAddToFirefoxHome() {
onView(withId(R.id.mozac_browser_menu_recyclerView))
.perform(
@ -514,3 +528,10 @@ private fun assertOpenInAppButton() {
)
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun addonsManagerButton() = onView(withText("Add-ons Manager"))
private fun clickAddonsManagerButton() {
onView(withText("Add-ons")).click()
addonsManagerButton().click()
}