LeOS-Ice-browser/SettingsSubMenuSearchRobot.kt

474 lines
18 KiB
Kotlin
Raw Permalink Normal View History

2024-02-21 18:22:05 +01:00
/* 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/. */
@file:Suppress("TooManyFunctions")
package org.mozilla.fenix.ui.robots
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasAnySibling
import androidx.compose.ui.test.hasContentDescription
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.closeSoftKeyboard
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.action.ViewActions.clearText
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withChild
import androidx.test.espresso.matcher.ViewMatchers.withClassName
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiSelector
import org.hamcrest.CoreMatchers
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.endsWith
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.DataGenerationHelper.getAvailableSearchEngines
import org.mozilla.fenix.helpers.DataGenerationHelper.getRegionSearchEnginesList
import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdAndText
import org.mozilla.fenix.helpers.MatcherHelper.itemWithText
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
import org.mozilla.fenix.helpers.TestHelper.hasCousin
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.isChecked
import org.mozilla.fenix.helpers.isEnabled
/**
* Implementation of Robot Pattern for the settings search sub menu.
*/
class SettingsSubMenuSearchRobot {
fun verifyToolbarText(title: String) {
onView(
allOf(
withId(R.id.navigationToolbar),
hasDescendant(withContentDescription(R.string.action_bar_up_description)),
hasDescendant(withText(title)),
),
).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
fun verifySearchEnginesSectionHeader() {
onView(withText("Search engines")).check(matches(isDisplayed()))
}
fun verifyDefaultSearchEngineHeader() {
defaultSearchEngineHeader
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
fun verifyDefaultSearchEngineSummary(engineName: String) {
defaultSearchEngineHeader.check(matches(hasSibling(withText(engineName))))
}
fun verifyManageSearchShortcutsHeader() {
manageSearchShortcutsHeader.check(matches(isDisplayed()))
}
fun verifyManageShortcutsSummary() {
manageSearchShortcutsHeader
.check(matches(hasSibling(withText("Edit engines visible in the search menu"))))
}
fun verifyEnginesShortcutsListHeader() =
assertUIObjectExists(itemWithText("Engines visible on the search menu"))
fun verifyAddressBarSectionHeader() {
onView(withText("Address bar - Firefox Suggest")).check(matches(isDisplayed()))
}
fun verifyDefaultSearchEngineList() {
defaultSearchEngineOption("LeOSearch")
.check(matches(hasSibling(withId(R.id.engine_icon))))
.check(matches(isDisplayed()))
defaultSearchEngineOption("DuckDuckGo")
.check(matches(hasSibling(withId(R.id.engine_icon))))
.check(matches(isDisplayed()))
assertUIObjectExists(addSearchEngineButton)
}
fun verifyManageShortcutsList(testRule: ComposeTestRule) {
val availableShortcutsEngines = getRegionSearchEnginesList() + getAvailableSearchEngines()
availableShortcutsEngines.forEach {
testRule.onNodeWithText(it.name)
.assert(hasAnySibling(hasContentDescription("${it.name} search engine")))
.assertIsDisplayed()
}
assertUIObjectExists(addSearchEngineButton)
}
/**
* Method that verifies the selected engines inside the Manage search shortcuts list.
*/
fun verifySearchShortcutChecked(vararg engineShortcut: EngineShortcut) {
engineShortcut.forEach {
val shortcutIsChecked = mDevice.findObject(UiSelector().text(it.name))
.getFromParent(
UiSelector().index(it.checkboxIndex),
).isChecked
if (it.isChecked) {
assertTrue(shortcutIsChecked)
} else {
assertFalse(shortcutIsChecked)
}
}
}
fun verifyAutocompleteURlsIsEnabled(enabled: Boolean) {
autocompleteSwitchButton()
.check(matches(hasCousin(allOf(withClassName(endsWith("Switch")), isChecked(enabled)))))
}
fun verifyShowSearchSuggestionsEnabled(enabled: Boolean) {
showSearchSuggestionSwitchButton()
.check(matches(hasCousin(allOf(withClassName(endsWith("Switch")), isChecked(enabled)))))
}
fun verifyShowSearchSuggestionsInPrivateEnabled(enabled: Boolean) {
showSuggestionsInPrivateModeSwitch()
.check(
matches(
hasSibling(
withChild(
allOf(
withClassName(endsWith("CheckBox")),
isChecked(enabled),
),
),
),
),
)
}
fun verifyShowClipboardSuggestionsEnabled(enabled: Boolean) {
showClipboardSuggestionSwitch()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
.check(matches(hasCousin(allOf(withClassName(endsWith("Switch")), isChecked(enabled)))))
}
fun verifySearchBrowsingHistoryEnabled(enabled: Boolean) {
searchHistorySwitchButton()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
.check(matches(hasCousin(allOf(withClassName(endsWith("Switch")), isChecked(enabled)))))
}
fun verifySearchBookmarksEnabled(enabled: Boolean) {
searchBookmarksSwitchButton()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
.check(matches(hasCousin(allOf(withClassName(endsWith("Switch")), isChecked(enabled)))))
}
fun verifySearchSyncedTabsEnabled(enabled: Boolean) {
searchSyncedTabsSwitchButton()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
.check(matches(hasCousin(allOf(withClassName(endsWith("Switch")), isChecked(enabled)))))
}
fun verifyVoiceSearchEnabled(enabled: Boolean) {
voiceSearchSwitchButton()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
.check(matches(hasCousin(allOf(withClassName(endsWith("Switch")), isChecked(enabled)))))
}
fun openDefaultSearchEngineMenu() {
defaultSearchEngineHeader.click()
}
fun openManageShortcutsMenu() {
manageSearchShortcutsHeader.click()
}
fun changeDefaultSearchEngine(searchEngineName: String) {
onView(withText(searchEngineName))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
.perform(click())
}
fun selectSearchShortcut(shortcut: EngineShortcut) {
mDevice.findObject(UiSelector().text(shortcut.name))
.getFromParent(UiSelector().index(shortcut.checkboxIndex))
.click()
}
fun toggleAutocomplete() = autocompleteSwitchButton().click()
fun toggleShowSearchSuggestions() = showSearchSuggestionSwitchButton().click()
fun toggleVoiceSearch() {
voiceSearchSwitchButton().perform(click())
}
fun toggleClipboardSuggestion() {
showClipboardSuggestionSwitch().click()
}
fun switchSearchHistoryToggle() = searchHistorySwitchButton().click()
fun switchSearchBookmarksToggle() = searchBookmarksSwitchButton().click()
fun switchShowSuggestionsInPrivateSessionsToggle() =
showSuggestionsInPrivateModeSwitch().click()
fun openAddSearchEngineMenu() = addSearchEngineButton.click()
fun verifyEngineListContains(searchEngineName: String, shouldExist: Boolean) =
assertUIObjectExists(itemWithText(searchEngineName), exists = shouldExist)
fun verifyDefaultSearchEngineSelected(searchEngineName: String) {
defaultSearchEngineOption(searchEngineName).check(matches(isChecked(true)))
}
fun verifySaveSearchEngineButtonEnabled(enabled: Boolean) {
addSearchEngineSaveButton().check(matches(isEnabled(enabled)))
}
fun saveNewSearchEngine() {
closeSoftKeyboard()
addSearchEngineSaveButton().click()
}
fun typeCustomEngineDetails(engineName: String, engineURL: String) {
try {
mDevice.findObject(By.res("$packageName:id/edit_engine_name")).clear()
mDevice.findObject(By.res("$packageName:id/edit_engine_name")).text = engineName
assertUIObjectExists(
itemWithResIdAndText("$packageName:id/edit_engine_name", engineName),
)
mDevice.findObject(By.res("$packageName:id/edit_search_string")).clear()
mDevice.findObject(By.res("$packageName:id/edit_search_string")).text = engineURL
assertUIObjectExists(
itemWithResIdAndText("$packageName:id/edit_search_string", engineURL),
)
} catch (e: AssertionError) {
println("The name or the search string were not set properly")
mDevice.findObject(By.res("$packageName:id/edit_engine_name")).clear()
mDevice.findObject(By.res("$packageName:id/edit_engine_name")).setText(engineName)
assertUIObjectExists(
itemWithResIdAndText("$packageName:id/edit_engine_name", engineName),
)
mDevice.findObject(By.res("$packageName:id/edit_search_string")).clear()
mDevice.findObject(By.res("$packageName:id/edit_search_string")).setText(engineURL)
assertUIObjectExists(
itemWithResIdAndText("$packageName:id/edit_search_string", engineURL),
)
}
}
fun typeSearchEngineSuggestionString(searchSuggestionString: String) {
onView(withId(R.id.edit_suggest_string))
.click()
.perform(clearText())
.perform(typeText(searchSuggestionString))
}
// Used in the non-Compose Default search engines menu
fun openEngineOverflowMenu(searchEngineName: String) {
threeDotMenu(searchEngineName).waitForExists(waitingTimeShort)
threeDotMenu(searchEngineName).click()
}
// Used in the composable Manage shortcuts menu, otherwise the overflow menu is not visible
fun openCustomShortcutOverflowMenu(testRule: ComposeTestRule, searchEngineName: String) {
testRule.onNode(overflowMenuWithSiblingText(searchEngineName)).performClick()
}
fun clickEdit() = onView(withText("Edit")).click()
// Used in the Default search engine menu
fun clickDeleteSearchEngine() =
mDevice.findObject(
UiSelector().textContains(getStringResource(R.string.search_engine_delete)),
).click()
// Used in the composable Manage search shortcuts menu, otherwise the overflow menu is not visible
fun clickDeleteSearchEngine(testRule: ComposeTestRule) =
testRule.onNodeWithText("Delete").performClick()
fun clickUndoSnackBarButton() =
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/snackbar_btn"),
).click()
fun saveEditSearchEngine() {
onView(withId(R.id.save_button)).click()
assertUIObjectExists(itemContainingText("Saved"))
}
fun verifyInvalidTemplateSearchStringFormatError() {
closeSoftKeyboard()
onView(withText(getStringResource(R.string.search_add_custom_engine_error_missing_template)))
.check(matches(isDisplayed()))
}
fun verifyErrorConnectingToSearchString(searchEngineName: String) {
closeSoftKeyboard()
onView(withText(getStringResource(R.string.search_add_custom_engine_error_cannot_reach, searchEngineName)))
.check(matches(isDisplayed()))
}
class Transition {
fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition {
mDevice.waitForIdle()
goBackButton().perform(click())
SettingsRobot().interact()
return SettingsRobot.Transition()
}
fun clickCustomSearchStringLearnMoreLink(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
onView(withId(R.id.custom_search_engines_learn_more)).click()
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun clickCustomSearchSuggestionsLearnMoreLink(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
onView(withId(R.id.custom_search_suggestions_learn_more)).click()
BrowserRobot().interact()
return BrowserRobot.Transition()
}
}
}
/**
* Matches search shortcut items inside the 'Manage search shortcuts' menu
* @param name, of type String, should be the name of the search engine.
* @param checkboxIndex, of type Int, is the checkbox' index afferent to the search engine.
* @param isChecked, of type Boolean, should show if the checkbox is expected to be checked.
*/
class EngineShortcut(
val name: String,
val checkboxIndex: Int,
val isChecked: Boolean = true,
)
private val defaultSearchEngineHeader = onView(withText("Default search engine"))
private val manageSearchShortcutsHeader = onView(withText("Manage alternative search engines"))
private fun searchHistorySwitchButton(): ViewInteraction {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Search browsing history")),
),
)
return onView(withText("Search browsing history"))
}
private fun searchBookmarksSwitchButton(): ViewInteraction {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Search bookmarks")),
),
)
return onView(withText("Search bookmarks"))
}
private fun searchSyncedTabsSwitchButton(): ViewInteraction {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Search synced tabs")),
),
)
return onView(withText("Search synced tabs"))
}
private fun voiceSearchSwitchButton(): ViewInteraction {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Show voice search")),
),
)
return onView(withText("Show voice search"))
}
private fun autocompleteSwitchButton(): ViewInteraction {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText(getStringResource(R.string.preferences_enable_autocomplete_urls))),
),
)
return onView(withText(getStringResource(R.string.preferences_enable_autocomplete_urls)))
}
private fun showSearchSuggestionSwitchButton(): ViewInteraction {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Show search suggestions")),
),
)
return onView(withText("Show search suggestions"))
}
private fun showClipboardSuggestionSwitch(): ViewInteraction {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText(getStringResource(R.string.preferences_show_clipboard_suggestions))),
),
)
return onView(withText(getStringResource(R.string.preferences_show_clipboard_suggestions)))
}
private fun showSuggestionsInPrivateModeSwitch(): ViewInteraction {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText(getStringResource(R.string.preferences_show_search_suggestions_in_private))),
),
)
return onView(withText(getStringResource(R.string.preferences_show_search_suggestions_in_private)))
}
private fun goBackButton() =
onView(CoreMatchers.allOf(withContentDescription("Navigate up")))
private val addSearchEngineButton = mDevice.findObject(UiSelector().text("Add search engine"))
private fun addSearchEngineSaveButton() = onView(withId(R.id.save_button))
private fun threeDotMenu(searchEngineName: String) =
mDevice.findObject(UiSelector().text(searchEngineName))
.getFromParent(UiSelector().description("More options"))
private fun defaultSearchEngineOption(searchEngineName: String) =
onView(
allOf(
withId(R.id.radio_button),
hasSibling(withText(searchEngineName)),
),
)
private fun overflowMenuWithSiblingText(text: String): SemanticsMatcher =
hasAnySibling(hasText(text)) and hasContentDescription("More options")