commit b53ca0d0f00bd8b1bfa0369dcc5a557f487c5dfc parent bde7267e4b02879693a0018661ff6e7673186731 Author: Jackie Johnson <107960801+jjSDET@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:40:00 +0000 Bug 1972025 - Add Settings Tests r=isabel_rios Differential Revision: https://phabricator.services.mozilla.com/D270408 Diffstat:
10 files changed, 301 insertions(+), 68 deletions(-)
diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/efficiency/docs/README.md b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/efficiency/docs/README.md @@ -1,6 +1,7 @@ # Android Test Automation Framework (POM-Based) ## 🚀 Introduction + This framework introduces a modern, scalable approach to Android UI testing using the Page Object Model (POM). It combines Espresso, UIAutomator2, and Jetpack Compose testing under a single unified abstraction, making test code clean, reusable, and easy to maintain. Tests written with this system read like user journeys, not code. Teams can define pages once and reuse them throughout tests—encouraging ownership, modularity, and clarity across features. @@ -8,12 +9,14 @@ Tests written with this system read like user journeys, not code. Teams can defi ## 🛠️ Getting Started ### Prerequisites + - Android Studio Arctic Fox or newer - Kotlin 1.9+ - Compose UI Test library - Enabled Test Orchestrator (optional for retries) ### Writing Your First Test + ```kotlin @Test fun verifyHomeLoads() { @@ -23,6 +26,7 @@ fun verifyHomeLoads() { ``` ### Structure Overview + - `BasePage.kt`: Common actions (clicks, verification, navigation) - `Selector.kt`: Describes UI elements in a flexible, tool-agnostic way - `PageContext.kt`: Entry point for test pages via `on.<Page>` @@ -31,6 +35,7 @@ fun verifyHomeLoads() { ## 🧪 Test Development Workflow 1. **Define Selectors** + ```kotlin val TITLE = Selector( strategy = SelectorStrategy.ESPRESSO_BY_TEXT, @@ -41,6 +46,7 @@ val TITLE = Selector( ``` 2. **Create a Page Object** + ```kotlin object HomePage : BasePage() { override val pageName = "HomePage" @@ -52,31 +58,38 @@ object HomePage : BasePage() { ``` 3. **Add to Context** + ```kotlin val homePage = HomePage ``` 4. **Write a Test** + ```kotlin on.homePage.navigateToPage() .mozVerifyElementsByGroup("requiredForPage") ``` ## 📚 Additional Resources + - 📖 [Test Automation Strategy](./docs/TestAutomationStrategy.md): Roadmap for phases and long-term goals - 💡 Example tests: See `ui/efficiency/tests/` - 📎 Diagrams: (Coming soon) ## 👥 Contributing + When adding new pages or selectors: + - Follow the fluent interface pattern - Group selectors meaningfully (e.g., `"requiredForPage"`, `"toolbar"`) - Register navigation paths explicitly in each `Page`'s `init` block ## ✅ Best Practices + - Use clear, readable selector descriptions - Avoid direct interaction with Espresso/UIAutomator in tests - Build tests in Given/When/Then structure --- -This framework enables powerful, flexible testing—but starts simple. With it, we empower teams to own their features *and* their tests. + +This framework enables powerful, flexible testing—but starts simple. With it, we empower teams to own their features _and_ their tests. diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/efficiency/docs/TestAutomationStrategy.md b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/efficiency/docs/TestAutomationStrategy.md @@ -1,9 +1,11 @@ # Test Automation as a Service: Multi-Phase Strategy ## Overview + This document outlines a multi-phase strategy for building a scalable, maintainable, and dynamic Android test automation system. The goal is to support testing at scale—ranging from dozens to tens of thousands of tests—while enabling individual development teams to own their test logic confidently. The system is designed not just to enable automated UI tests, but to act as a test orchestration framework that can: + - Generate tests dynamically - Support efficient test suite creation and execution - Optimize coverage with minimal redundancy @@ -12,11 +14,13 @@ The system is designed not just to enable automated UI tests, but to act as a te ## Summary of Phases ### **Phase 0: Ad-Hoc Testing** + - Common pattern seen in early automation efforts or tutorials - Tests are brittle, hard to maintain - Difficult to scale beyond ~100-200 tests ### **Phase 1: Standardization with Page Object Model (POM)** + - Introduce BasePage + Selector abstractions for cross-device/platform consistency - Define navigation flows using a navigation graph (nodes = pages, edges = steps) - Fluent interface pattern for clean and readable test flows @@ -24,11 +28,13 @@ The system is designed not just to enable automated UI tests, but to act as a te - Targets ~1,000 to ~2,000 tests **Key Outcomes:** + - Reduced maintenance overhead - Stable and composable test structure - Foundation for dynamic navigation and grouped element validation ### **Phase 2: Data-Driven Tests & Dynamic Test Factories** + - Use metadata and reflection to define what each test requires - Centralized test data service that dynamically prepares: - Test data @@ -39,12 +45,14 @@ The system is designed not just to enable automated UI tests, but to act as a te - Coverage optimization using Orthogonal Arrays (e.g. pairwise testing) **Key Outcomes:** + - Tests generated from config or runtime context - Faster feedback cycles by running most relevant tests first - Easier root cause analysis in CI - Teams can define and manage their own test scope ### **Phase 3: Test Factory of Test Factories** + - Fully leverage standardized infrastructure from Phases 1–2 - Enable AI and/or rule-based helpers to: - Propose tests based on heuristics or code changes @@ -54,6 +62,7 @@ The system is designed not just to enable automated UI tests, but to act as a te - Fluent interfaces make automation predictable and parseable **Key Outcomes:** + - Automation becomes intelligent and context-aware - Reduced cost of test maintenance and authoring - Human involvement focused on novel or edge-case testing @@ -61,6 +70,7 @@ The system is designed not just to enable automated UI tests, but to act as a te ## Current State (Phase 1 MVP) ### Helper Modules Implemented: + - `BasePage.kt`: Common logic for all screens (navigation, verification, interaction) - `Selector.kt`: Declarative wrapper around UI selectors with grouping and description - `NavigationRegistry.kt`: Graph structure for defining navigation steps between pages @@ -76,39 +86,45 @@ on.settingsPage.navigateToPage() ``` ## How This Enables the Future -- Pages describe their *structure* via selectors + +- Pages describe their _structure_ via selectors - Navigation is described declaratively -- Test code focuses only on *what to test*, not *how to get there* -- Data-driven factories can then define *what to test* programmatically +- Test code focuses only on _what to test_, not _how to get there_ +- Data-driven factories can then define _what to test_ programmatically ## Low-Level Developer Guidelines ### Writing a New Test + - Extend `BaseTest` - Use `on.<Page>.navigateToPage()` to transition - Use `.mozVerifyElementsByGroup("...group...")` to check structure - Use `.mozClick(selector)` or define `NavigationStep.Click()` to interact ### Adding a New Page + - Create new object that extends `BasePage` - Implement `mozGetSelectorsByGroup()` - Register all navigation edges in `init {}` block ### Creating a Dynamic Test + - Define input data and selectors declaratively - Feed test data into a factory that builds navigation + verification steps --- ## Final Thoughts + This framework is not just about making UI tests easier. It’s about enabling: + - Reusable automation primitives - Team autonomy - Data- and AI-assisted test generation - Long-term maintainability -It positions test automation as a *strategic capability*, not just a tactical tool. +It positions test automation as a _strategic capability_, not just a tactical tool. --- -*For more implementation details, see internal documentation and examples in the `helpers/`, `navigation/`, and `pageObjects/` packages.* +_For more implementation details, see internal documentation and examples in the `helpers/`, `navigation/`, and `pageObjects/` packages._ diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/efficiency/helpers/BasePage.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/efficiency/helpers/BasePage.kt @@ -7,6 +7,7 @@ package org.mozilla.fenix.ui.efficiency.helpers import android.util.Log import androidx.compose.ui.test.SemanticsNodeInteraction import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.hasAnyDescendant import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithTag @@ -20,13 +21,18 @@ import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.pressImeActionButton import androidx.test.espresso.action.ViewActions.typeText import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.matcher.ViewMatchers.hasDescendant import androidx.test.espresso.matcher.ViewMatchers.isDisplayed 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.UiObject import androidx.test.uiautomator.UiSelector +import org.hamcrest.CoreMatchers.containsString +import org.mozilla.fenix.R import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.packageName @@ -80,12 +86,11 @@ abstract class BasePage( val deadline = System.currentTimeMillis() + timeout while (System.currentTimeMillis() < deadline) { - if (requiredSelectors.all { mozVerifyElement(it) }) { + if (requiredSelectors.all { mozVerifyElement(it, applyPreconditions = false) }) { return true } android.os.SystemClock.sleep(interval) } - return false } @@ -93,12 +98,8 @@ abstract class BasePage( fun mozVerifyElementsByGroup(group: String = "requiredForPage"): BasePage { val selectors = mozGetSelectorsByGroup(group) - val allPresent = selectors.all { mozVerifyElement(it) } - - if (!allPresent) { - throw AssertionError("Not all elements in group '$group' are present") - } - + val allPresent = selectors.all { mozVerifyElement(it, applyPreconditions = true) } + if (!allPresent) throw AssertionError("Not all elements in group '$group' are present") return this } @@ -148,10 +149,11 @@ abstract class BasePage( fun mozSwipeTo( selector: Selector, direction: SwipeDirection = SwipeDirection.DOWN, - maxSwipes: Int = 5, + maxSwipes: Int = 10, // TODO (Jackie J. 10/30/2025): replace hard-coded value with self-selecting x,y boundaries + applyPreconditions: Boolean = false, // default false to avoid recursive preconditions ): BasePage { repeat(maxSwipes) { attempt -> - val element = mozGetElement(selector) + val element = mozGetElement(selector, applyPreconditions = applyPreconditions) val isVisible = when (element) { is ViewInteraction -> try { @@ -204,7 +206,7 @@ abstract class BasePage( SwipeDirection.RIGHT -> listOf(width / 4, height / 2, width * 3 / 4, height / 2) } - mDevice.swipe(startX, startY, endX, endY, 10) + mDevice.swipe(startX, startY, endX, endY, 20) } fun mozEnterText(text: String, selector: Selector): BasePage { @@ -284,12 +286,16 @@ abstract class BasePage( return this } - private fun mozGetElement(selector: Selector): Any? { + private fun mozGetElement(selector: Selector, applyPreconditions: Boolean = true): Any? { if (selector.value.isBlank()) { Log.i("mozGetElement", "Empty or blank selector value: ${selector.description}") return null } + if (applyPreconditions && requiresScroll(selector.groups)) { + ensureReachable(selector) // may call mozSwipeTo with applyPreconditions = false + } + return when (selector.strategy) { SelectorStrategy.COMPOSE_BY_TAG -> { try { @@ -330,7 +336,6 @@ abstract class BasePage( SelectorStrategy.ESPRESSO_BY_TEXT -> onView(withText(selector.value)) SelectorStrategy.ESPRESSO_BY_CONTENT_DESC -> onView(withContentDescription(selector.value)) - SelectorStrategy.UIAUTOMATOR2_BY_CLASS -> { val obj = mDevice.findObject(UiSelector().className(selector.value)) if (!obj.exists()) null else obj @@ -371,14 +376,13 @@ abstract class BasePage( } } - private fun mozVerifyElement(selector: Selector): Boolean { - val element = mozGetElement(selector) + private fun mozVerifyElement(selector: Selector, applyPreconditions: Boolean = true): Boolean { + val element = mozGetElement(selector, applyPreconditions = applyPreconditions) return when (element) { is ViewInteraction -> { try { - element.check(matches(isDisplayed())) - true + element.check(matches(isDisplayed())); true } catch (e: Exception) { false } @@ -386,9 +390,7 @@ abstract class BasePage( is UiObject -> element.exists() is SemanticsNodeInteraction -> { try { - element.assertExists() - element.assertIsDisplayed() - true + element.assertExists(); element.assertIsDisplayed(); true } catch (e: AssertionError) { false } @@ -396,4 +398,30 @@ abstract class BasePage( else -> false } } + + private fun requiresScroll(groups: List<String>): Boolean { + return groups.any { it.equals("requiresScroll", ignoreCase = true) || it.equals("needsSwipeNavStep", ignoreCase = true) } + } + + private fun desiredSwipeDirection(groups: List<String>): SwipeDirection { + return when { + groups.any { it.equals("swipeDown", true) } -> SwipeDirection.DOWN + groups.any { it.equals("swipeLeft", true) } -> SwipeDirection.LEFT + groups.any { it.equals("swipeRight", true) } -> SwipeDirection.RIGHT + else -> SwipeDirection.UP + } + } + + private fun ensureReachable(selector: Selector) { + // If it's already visible, skip swiping. + val visibleNow = mozVerifyElement(selector, applyPreconditions = false) + if (visibleNow) return + + if (requiresScroll(selector.groups)) { + val dir = desiredSwipeDirection(selector.groups) + Log.i("Preconditions", "🧠'${selector.description}' requires scroll. Swiping $dir to bring into view.") + // IMPORTANT: do not allow nested preconditions during swipe-to lookup + mozSwipeTo(selector, direction = dir, maxSwipes = 10, applyPreconditions = false) + } + } } diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/efficiency/pageObjects/SettingsAboutPage.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/efficiency/pageObjects/SettingsAboutPage.kt @@ -15,16 +15,16 @@ class SettingsAboutPage(composeRule: AndroidComposeTestRule<HomeActivityIntentTe override val pageName = "SettingsAboutPage" init { - NavigationRegistry.register( - from = "HomePage", - to = pageName, - steps = listOf( - NavigationStep.Click(HomeSelectors.MAIN_MENU_BUTTON), - NavigationStep.Click(MainMenuSelectors.SETTINGS_BUTTON), - NavigationStep.Swipe(SettingsSelectors.DATA_COLLECTION_BUTTON), - NavigationStep.Click(SettingsSelectors.DATA_COLLECTION_BUTTON), - ), - ) +// NavigationRegistry.register( +// from = "HomePage", +// to = pageName, +// steps = listOf( +// NavigationStep.Click(HomeSelectors.MAIN_MENU_BUTTON), +// NavigationStep.Click(MainMenuSelectors.SETTINGS_BUTTON), +// NavigationStep.Swipe(SettingsSelectors.DATA_COLLECTION_BUTTON), +// NavigationStep.Click(SettingsSelectors.DATA_COLLECTION_BUTTON), +// ), +// ) } override fun mozGetSelectorsByGroup(group: String): List<Selector> { diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/efficiency/pageObjects/SettingsPage.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/efficiency/pageObjects/SettingsPage.kt @@ -61,6 +61,22 @@ class SettingsPage(composeRule: AndroidComposeTestRule<HomeActivityIntentTestRul to = "SettingsTabsPage", steps = listOf(NavigationStep.Click(SettingsSelectors.TABS_BUTTON)), ) + NavigationRegistry.register( + from = pageName, + to = "GooglePlayPage", + steps = listOf( + NavigationStep.Swipe(SettingsSelectors.RATE_ON_GOOGLE_PLAY_BUTTON), + NavigationStep.Click(SettingsSelectors.RATE_ON_GOOGLE_PLAY_BUTTON), + ), + ) + NavigationRegistry.register( + from = pageName, + to = "SettingsAboutPage", + steps = listOf( + NavigationStep.Swipe(SettingsSelectors.ABOUT_FIREFOX_BUTTON), + NavigationStep.Click(SettingsSelectors.ABOUT_FIREFOX_BUTTON), + ), + ) } override fun mozGetSelectorsByGroup(group: String): List<Selector> { diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/efficiency/selectors/SettingsAboutSelectors.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/efficiency/selectors/SettingsAboutSelectors.kt @@ -1,5 +1,7 @@ package org.mozilla.fenix.ui.efficiency.selectors +import org.mozilla.fenix.R +import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource import org.mozilla.fenix.ui.efficiency.helpers.Selector import org.mozilla.fenix.ui.efficiency.helpers.SelectorStrategy @@ -12,7 +14,71 @@ object SettingsAboutSelectors { groups = listOf("requiredForPage"), ) + val WHATS_NEW_BUTTON = Selector( + strategy = SelectorStrategy.UIAUTOMATOR_WITH_TEXT_CONTAINS, + value = "new in", + description = "The Whats New Title", + groups = listOf("aboutSection", "aboutFirefox", "whatsNew"), + ) + + val SUPPORT_BUTTON = Selector( + strategy = SelectorStrategy.UIAUTOMATOR_WITH_TEXT_CONTAINS, + value = "Support", + description = "The Support Link", + groups = listOf("aboutSection", "aboutFirefox", "supportItem"), + ) + + val CRASHES_BUTTON = Selector( + strategy = SelectorStrategy.UIAUTOMATOR_WITH_TEXT_CONTAINS, + value = "Crashes", + description = "The Crashes Button", + groups = listOf("aboutSection", "aboutFirefox", "crashes"), + ) + + val PRIVACY_NOTICE_BUTTON = Selector( + strategy = SelectorStrategy.UIAUTOMATOR_WITH_TEXT_CONTAINS, + value = "Privacy Notice", + description = "The Privacy Notice Button", + groups = listOf("aboutSection", "aboutFirefox", "privacyNotice"), + ) + + val KNOW_YOUR_RIGHTS_BUTTON = Selector( + strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT, + value = "Know your rights", + description = "The Know your rights Button", + groups = listOf("Know your rights", "aboutFirefox", "knowYourRights"), + ) + + val LICENSING_INFORMATION_BUTTON = Selector( + strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT, + value = "Licensing information", + description = "The Licensing Information Button", + groups = listOf("requiresScroll", "aboutFirefox", "licensingInformation"), + ) + + val LIBRARIES_THAT_WE_USE_BUTTON = Selector( + strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT, + value = "Libraries that we use", + description = "The Libraries that we use Button", + groups = listOf("requiresScroll", "aboutFirefox", "librariesThatWeUse"), + ) + + val ABOUT_INFO_TEXTBOX = Selector( + strategy = SelectorStrategy.ESPRESSO_BY_ID, + value = "about_text", + description = "The About Info Textbox", + groups = listOf("aboutFirefox", "aboutInfo"), + ) + val all = listOf( NAVIGATE_BACK_TOOLBAR_BUTTON, + WHATS_NEW_BUTTON, + SUPPORT_BUTTON, + CRASHES_BUTTON, + PRIVACY_NOTICE_BUTTON, + KNOW_YOUR_RIGHTS_BUTTON, + LICENSING_INFORMATION_BUTTON, + LIBRARIES_THAT_WE_USE_BUTTON, + ABOUT_INFO_TEXTBOX, ) } diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/efficiency/selectors/SettingsAddonsManagerSelectors.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/efficiency/selectors/SettingsAddonsManagerSelectors.kt @@ -1,5 +1,7 @@ package org.mozilla.fenix.ui.efficiency.selectors +import org.mozilla.fenix.R +import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource import org.mozilla.fenix.ui.efficiency.helpers.Selector import org.mozilla.fenix.ui.efficiency.helpers.SelectorStrategy @@ -19,8 +21,16 @@ object SettingsAddonsManagerSelectors { groups = listOf("extensionDetails"), ) + val ADD_ONS_LIST = Selector( + strategy = SelectorStrategy.UIAUTOMATOR_WITH_RES_ID, + value = "add_ons_list", + description = "Add-ons List", + groups = listOf("addOns"), + ) + val all = listOf( NAVIGATE_BACK_TOOLBAR_BUTTON, ENABLE_OR_DISABLE_EXTENSION_TOGGLE, + ADD_ONS_LIST, ) } diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/efficiency/selectors/SettingsSelectors.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/efficiency/selectors/SettingsSelectors.kt @@ -18,56 +18,56 @@ object SettingsSelectors { strategy = SelectorStrategy.ESPRESSO_BY_TEXT, value = "General", description = "the General heading", - groups = listOf("requiredForPage", "generalSettingsSection"), + groups = listOf("generalSettingsSection"), ) val SEARCH_BUTTON = Selector( strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT, value = "Search", description = "the Search button", - groups = listOf("requiredForPage", "generalSettingsSection"), + groups = listOf("generalSettingsSection"), ) val TABS_BUTTON = Selector( strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT, value = "Tabs", description = "the Tabs button", - groups = listOf("requiredForPage", "generalSettingsSection"), + groups = listOf("generalSettingsSection"), ) val ACCESSIBILITY_BUTTON = Selector( strategy = SelectorStrategy.ESPRESSO_BY_TEXT, value = "Accessibility", description = "the Accessibility button", - groups = listOf("requiredForPage", "generalSettingsSection"), + groups = listOf("generalSettingsSection"), ) val AUTOFILL_BUTTON = Selector( strategy = SelectorStrategy.ESPRESSO_BY_TEXT, value = "Autofill", description = "the Autofill button", - groups = listOf("requiredForPage", "generalSettingsSection"), + groups = listOf("generalSettingsSection"), ) val CUSTOMIZE_BUTTON = Selector( strategy = SelectorStrategy.ESPRESSO_BY_TEXT, value = "Customize", description = "the Customize button", - groups = listOf("requiredForPage", "generalSettingsSection"), + groups = listOf("generalSettingsSection"), ) val HOMEPAGE_BUTTON = Selector( strategy = SelectorStrategy.ESPRESSO_BY_TEXT, value = "Homepage", description = "the Homepage button", - groups = listOf("requiredForPage", "generalSettingsSection"), + groups = listOf("generalSettingsSection"), ) val PASSWORDS_BUTTON = Selector( strategy = SelectorStrategy.ESPRESSO_BY_TEXT, value = "Passwords", description = "the Passwords button", - groups = listOf("requiredForPage", "generalSettingsSection"), + groups = listOf("generalSettingsSection"), ) val ABOUT_BUTTON = Selector( @@ -78,35 +78,35 @@ object SettingsSelectors { ) val DATA_COLLECTION_BUTTON = Selector( - strategy = SelectorStrategy.UIAUTOMATOR_WITH_TEXT_CONTAINS, + strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT, value = "Data collection", description = "the Data Collection button", groups = listOf("privacyAndSecuritySettingsSection"), ) val DELETE_BROWSING_DATA_ON_QUIT_BUTTON = Selector( - strategy = SelectorStrategy.UIAUTOMATOR_WITH_TEXT_CONTAINS, + strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT, value = "Delete browsing data on quit", description = "the Delete browsing data on quit button", groups = listOf("privacyAndSecuritySettingsSection"), ) val DELETE_BROWSING_DATA_BUTTON = Selector( - strategy = SelectorStrategy.UIAUTOMATOR_WITH_TEXT_CONTAINS, + strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT, value = "Delete browsing data", description = "the Delete browsing data button", groups = listOf("privacyAndSecuritySettingsSection"), ) val ENHANCED_TRACKING_PROTECTION_BUTTON = Selector( - strategy = SelectorStrategy.UIAUTOMATOR_WITH_TEXT_CONTAINS, + strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT, value = "Enhanced Tracking Protection", description = "the Enhanced tracking protection button", groups = listOf("privacyAndSecuritySettingsSection"), ) val HTTPS_ONLY_MODE_BUTTON = Selector( - strategy = SelectorStrategy.UIAUTOMATOR_WITH_TEXT_CONTAINS, + strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT, value = getStringResource(R.string.preferences_https_only_title), description = "the HTTPS only mode button", groups = listOf("privacyAndSecuritySettingsSection"), @@ -116,7 +116,7 @@ object SettingsSelectors { strategy = SelectorStrategy.ESPRESSO_BY_TEXT, value = getStringResource(R.string.preferences_language), description = "the Language button", - groups = listOf("requiredForPage", "generalSettingsSection"), + groups = listOf("generalSettingsSection"), ) val OPEN_LINKS_IN_APPS_BUTTON = Selector( @@ -127,14 +127,14 @@ object SettingsSelectors { ) val PRIVATE_BROWSING_BUTTON = Selector( - strategy = SelectorStrategy.UIAUTOMATOR_WITH_TEXT_CONTAINS, + strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT, value = "Private browsing", description = "the Private browsing button", groups = listOf("privacyAndSecuritySettingsSection"), ) val TRANSLATIONS_BUTTON = Selector( - strategy = SelectorStrategy.UIAUTOMATOR_WITH_TEXT_CONTAINS, + strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT, value = "Translations", description = "the Private browsing button", groups = listOf("generalSettingsSection"), @@ -144,11 +144,11 @@ object SettingsSelectors { strategy = SelectorStrategy.ESPRESSO_BY_TEXT, value = "Sign in", description = "the Sign in button", - groups = listOf("requiredForPage"), + groups = listOf("sync"), ) val NOTIFICATIONS_BUTTON = Selector( - strategy = SelectorStrategy.UIAUTOMATOR_WITH_TEXT_CONTAINS, + strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT, value = "Notifications", description = "the Notifications button", groups = listOf("privacyAndSecuritySettingsSection"), @@ -162,12 +162,33 @@ object SettingsSelectors { ) val SITE_SETTINGS_BUTTON = Selector( - strategy = SelectorStrategy.UIAUTOMATOR_WITH_TEXT_CONTAINS, + strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT, value = "Site settings", description = "the Site settings button", groups = listOf("privacyAndSecuritySettingsSection"), ) + val ABOUT_SECTION_TITLE = Selector( + strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT, + value = "About", + description = "The About Section Title", + groups = listOf("aboutSection", "requiresScroll"), + ) + + val RATE_ON_GOOGLE_PLAY_BUTTON = Selector( + strategy = SelectorStrategy.UIAUTOMATOR_WITH_TEXT_CONTAINS, + value = "Google Play", + description = "The Rate on Google Play Button", + groups = listOf("aboutSection", "requiresScroll", "googlePlay"), + ) + + val ABOUT_FIREFOX_BUTTON = Selector( + strategy = SelectorStrategy.UIAUTOMATOR_WITH_TEXT_CONTAINS, + value = "About Firefox", + description = "The About Firefox Title", + groups = listOf("aboutSection", "aboutFirefox", "requiresScroll"), + ) + val all = listOf( GO_BACK_BUTTON, GENERAL_HEADING, @@ -192,5 +213,8 @@ object SettingsSelectors { NOTIFICATIONS_BUTTON, EXPERIMENTS_BUTTON, SITE_SETTINGS_BUTTON, + ABOUT_FIREFOX_BUTTON, + ABOUT_SECTION_TITLE, + RATE_ON_GOOGLE_PLAY_BUTTON, ) } diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/efficiency/tests/SettingsAboutTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/efficiency/tests/SettingsAboutTest.kt @@ -5,8 +5,80 @@ import org.mozilla.fenix.ui.efficiency.helpers.BaseTest class SettingsAboutTest : BaseTest() { + // TestRail: https://mozilla.testrail.io/index.php?/cases/view/2092700 + @Test + fun verifyAboutSettingsItemsTest() { + on.settings.navigateToPage() + .mozVerifyElementsByGroup("aboutSection") + } + + // TestRail: https://mozilla.testrail.io/index.php?/cases/view/246966 + @Test + fun verifyRateOnGooglePlayButtonTest() { + on.settings.navigateToPage() + .mozVerifyElementsByGroup("googlePlay") + } + + @Test + fun verifyLibrariesListInReleaseBuildsTest() { + on.settingsAbout.navigateToPage() + .mozVerifyElementsByGroup("librariesThatWeUse") + .mozVerifyElementsByGroup("aboutInfo") + } + + // TestRail: https://mozilla.testrail.io/index.php?/cases/view/3132639 + @Test + fun verifyAboutFirefoxMenuAppDetailsItemTest() { + on.settingsAbout.navigateToPage() + .mozVerifyElementsByGroup("aboutInfo") + } + + // TestRail: https://mozilla.testrail.io/index.php?/cases/view/3132640 + @Test + fun verifyAboutFirefoxMenuWhatsNewInFirefoxItemTest() { + on.settingsAbout.navigateToPage() + .mozVerifyElementsByGroup("whatsNew") + } + + // TestRail: https://mozilla.testrail.io/index.php?/cases/view/3132641 + @Test + fun verifyAboutFirefoxMenuSupportItemTest() { + on.settingsAbout.navigateToPage() + .mozVerifyElementsByGroup("supportItem") + } + + // TestRail: https://mozilla.testrail.io/index.php?/cases/view/3132642 + @Test + fun verifyAboutFirefoxMenuCrashesItemTest() { + on.settingsAbout.navigateToPage() + .mozVerifyElementsByGroup("crashes") + } + + // TestRail: https://mozilla.testrail.io/index.php?/cases/view/3132643 + @Test + fun verifyAboutFirefoxMenuPrivacyNoticeItemTest() { + on.settingsAbout.navigateToPage() + .mozVerifyElementsByGroup("privacyNotice") + } + + // TestRail: https://mozilla.testrail.io/index.php?/cases/view/3132644 + @Test + fun verifyAboutFirefoxMenuKnowYourRightsItemTest() { + on.settingsAbout.navigateToPage() + .mozVerifyElementsByGroup("knowYourRights") + } + + // TestRail: https://mozilla.testrail.io/index.php?/cases/view/3132645 + @Test + fun verifyAboutFirefoxMenuLicensingInformationItemTest() { + on.settingsAbout.navigateToPage() + .mozVerifyElementsByGroup("licensingInformation") + } + + // TestRail: https://mozilla.testrail.io/index.php?/cases/view/3132646 @Test - fun verifyAboutSettingsSectionTest() { + fun verifyAboutFirefoxMenuLibrariesThatWeUseItemTest() { on.settingsAbout.navigateToPage() + .mozVerifyElementsByGroup("librariesThatWeUse") } } diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/efficiency/tests/SettingsAddonsTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/efficiency/tests/SettingsAddonsTest.kt @@ -1,12 +0,0 @@ -package org.mozilla.fenix.ui.efficiency.tests - -import org.junit.Test -import org.mozilla.fenix.ui.efficiency.helpers.BaseTest - -class SettingsAddonsTest : BaseTest() { - - @Test - fun verifyTheAddonsSectionTest() { - on.settingsAddonsManager.navigateToPage() - } -}