From 5838facf86b071a4d442d2046cb1c17de7debbe1 Mon Sep 17 00:00:00 2001 From: pollymce Date: Wed, 23 Sep 2026 15:50:43 +0100 Subject: [PATCH] Bug 2073095 - make the home screen tests safe to be run with different configurations of pinned and sponsored shortcuts. Such as one might find across different locales and build types (: Also locate sponsored shortcuts by their sponsored label. Sponsored shortcuts were located by their absolute position in the top sites list, which assumes a fixed number of pinned shortcuts precedes them. getSponsoredShortcutTitle and verifySponsoredShortcutDetails now take an index among the sponsored shortcuts only, resolved through the sponsored label that only sponsored shortcuts carry. TopSitesTest's setUp asserted the entire default top sites list where, as its comment says, it only needs to wait for the list to be displayed. --- .../fenix/helpers/AppAndSystemHelper.kt | 17 +++++++ .../org/mozilla/fenix/helpers/Constants.kt | 21 ++++----- .../fenix/helpers/DataGenerationHelper.kt | 45 ++++++++++++------ .../fenix/ui/SponsoredShortcutsTest.kt | 47 ++++++++++--------- .../java/org/mozilla/fenix/ui/TopSitesTest.kt | 15 +++--- .../fenix/ui/robots/HomeScreenRobot.kt | 28 ++++++----- 6 files changed, 107 insertions(+), 66 deletions(-) diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/helpers/AppAndSystemHelper.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/helpers/AppAndSystemHelper.kt index ea2ef99b87740..2baf9ff5c3eb3 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/helpers/AppAndSystemHelper.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/helpers/AppAndSystemHelper.kt @@ -46,6 +46,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import mozilla.appservices.places.BookmarkRoot +import mozilla.components.browser.state.search.RegionState import mozilla.components.browser.storage.sync.PlacesBookmarksStorage import mozilla.components.browser.storage.sync.PlacesHistoryStorage import mozilla.components.feature.top.sites.TopSite @@ -53,6 +54,7 @@ import mozilla.components.support.locale.LocaleManager.resetToSystemDefault import mozilla.components.support.locale.LocaleManager.setNewLocale import org.junit.Assert import org.junit.Assert.assertEquals +import org.mozilla.fenix.Config import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.components.PermissionStorage import org.mozilla.fenix.customtabs.ExternalAppBrowserActivity @@ -956,4 +958,19 @@ object AppAndSystemHelper { Log.i(TAG, "setScreenOrientation: Waited for the compose test rule to be idle.") Log.i(TAG, "setScreenOrientation: Orientation set to $orientation.") } + + /** + * Whether the default pinned shortcuts are expected on the homepage. + * + * Mirrors the condition in `DefaultPinnedSitesBinding`. + */ + fun isDefaultPinnedShortcutsOnHomepage(): Boolean { + val region = appContext.components.core.store.state.search.region + val expected = region != null && (!Config.channel.isReleased || region != RegionState.Default) + Log.i( + TAG, + "isDefaultPinnedShortcutsOnHomepage: region=$region channel=${Config.channel} expected=$expected", + ) + return expected + } } diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/helpers/Constants.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/helpers/Constants.kt index 3d94625f39585..e9f2c1fc7dc85 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/helpers/Constants.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/helpers/Constants.kt @@ -41,18 +41,17 @@ object Constants { "DuckDuckGo" to "t=fpas", ) - val firstSponsoredShortcutTitle by lazy { getSponsoredShortcutTitle(2) } - val secondSponsoredShortcutTitle by lazy { getSponsoredShortcutTitle(3) } + private val firstSponsoredShortcutTitle by lazy { getSponsoredShortcutTitle(1) } + private val secondSponsoredShortcutTitle by lazy { getSponsoredShortcutTitle(2) } - // Expected for en-us defaults - val defaultTopSitesList by lazy { - mapOf( - "Google" to "Google", - "First sponsored shortcut" to firstSponsoredShortcutTitle, - "Second sponsored shortcut" to secondSponsoredShortcutTitle, - "Wikipedia" to "Wikipedia", - ) - } + /** + * The shortcut titles, which are only present when [AppAndSystemHelper.isDefaultPinnedShortcutsOnHomepage] holds. + * Expected for en-us defaults. + */ + val defaultPinnedShortcutTitles by lazy { listOf("Google", "Wikipedia") } + + /** The sponsored shortcuts, which are present on every build type. */ + val sponsoredShortcutTitles by lazy { listOf(firstSponsoredShortcutTitle, secondSponsoredShortcutTitle) } // Notes: // "Ghostery" - not included in the list because of the name discrepancy between the recommended list and the diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/helpers/DataGenerationHelper.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/helpers/DataGenerationHelper.kt index 5a69e5093e075..ec5b319a67739 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/helpers/DataGenerationHelper.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/helpers/DataGenerationHelper.kt @@ -20,17 +20,20 @@ import androidx.compose.ui.test.onNodeWithContentDescription import androidx.core.graphics.createBitmap import androidx.core.net.toUri import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiSelector +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiObject2 import java.time.LocalDate import java.time.LocalTime import mozilla.components.browser.state.search.SearchEngine import mozilla.components.browser.state.state.availableSearchEngines import org.junit.Assert +import org.mozilla.fenix.R import org.mozilla.fenix.ext.components import org.mozilla.fenix.helpers.Constants.TAG import org.mozilla.fenix.helpers.Constants.recommendedAddons import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.waitForAppWindowToBeUpdated +import org.mozilla.fenix.home.topsites.TopSitesTestTag import org.mozilla.fenix.utils.IntentUtils object DataGenerationHelper { @@ -126,24 +129,36 @@ object DataGenerationHelper { return currentYear + currentMonth + currentDay + currentHour } - /** Returns sponsored shortcut title based on the index. */ - fun getSponsoredShortcutTitle(position: Int): String { - Log.i( - TAG, - "getSponsoredShortcutTitle: Trying to get the title of the sponsored shortcut at position: ${position - 1}", - ) + /** + * Returns the title of the [ordinal]th sponsored shortcut, counting only sponsored shortcuts. + * + * The shortcuts are located by their sponsored label rather than by their absolute position in the top sites list, + * because the number of default pinned shortcuts preceding them varies by release channel and region. See + * [sponsoredShortcuts]. + * + * @param ordinal One-based index among the sponsored shortcuts only. + */ + fun getSponsoredShortcutTitle(ordinal: Int): String { + Log.i(TAG, "getSponsoredShortcutTitle: Trying to get the title of sponsored shortcut #$ordinal") val sponsoredShortcut = - mDevice - .findObject(UiSelector().resourceId("top_sites_list.top_site_item").index(position - 1)) - .getChild(UiSelector().resourceId("top_sites_list.top_site_item.top_site_title")) - .text - Log.i( - TAG, - "getSponsoredShortcutTitle: The sponsored shortcut at position: ${position - 1} has title: $sponsoredShortcut", - ) + sponsoredShortcuts()[ordinal - 1].findObject(By.res(TopSitesTestTag.TOP_SITE_TITLE)).text + Log.i(TAG, "getSponsoredShortcutTitle: Sponsored shortcut #$ordinal has title: $sponsoredShortcut") return sponsoredShortcut } + /** + * Returns the top sites that are labelled as sponsored, in the order they appear on the homepage. + * + * Every shortcut lays out a sponsored label, but it only carries text when the shortcut is sponsored, so matching + * on that text distinguishes them. + */ + fun sponsoredShortcuts(): List { + val sponsoredLabel = getStringResource(R.string.top_sites_sponsored_label) + return mDevice.findObjects(By.res(TopSitesTestTag.TOP_SITE_ITEM_ROOT)).filter { + it.findObject(By.text(sponsoredLabel)) != null + } + } + /** Returns the title of the first matching extension. */ fun getRecommendedExtensionTitle(composeTestRule: ComposeTestRule): String { var verifiedCount = 0 diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SponsoredShortcutsTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SponsoredShortcutsTest.kt index 4a30412213197..373ec85c575aa 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SponsoredShortcutsTest.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/SponsoredShortcutsTest.kt @@ -10,9 +10,11 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.fenix.customannotations.SmokeTest +import org.mozilla.fenix.helpers.AppAndSystemHelper.isDefaultPinnedShortcutsOnHomepage import org.mozilla.fenix.helpers.Constants.RETRY_COUNT import org.mozilla.fenix.helpers.Constants.TAG -import org.mozilla.fenix.helpers.Constants.defaultTopSitesList +import org.mozilla.fenix.helpers.Constants.defaultPinnedShortcutTitles +import org.mozilla.fenix.helpers.Constants.sponsoredShortcutTitles import org.mozilla.fenix.helpers.DataGenerationHelper.getSponsoredShortcutTitle import org.mozilla.fenix.helpers.FenixTestRule import org.mozilla.fenix.helpers.HomeActivityIntentTestRule @@ -71,8 +73,9 @@ class SponsoredShortcutsTest { fun verifySponsoredShortcutsListTest() { homeScreen(composeTestRule) { verifyExistingTopSitesList() - defaultTopSitesList.values.forEach { value -> - verifyExistingTopSitesTabs(value) + sponsoredShortcutTitles.forEach { title -> verifyExistingTopSitesTabs(title) } + if (isDefaultPinnedShortcutsOnHomepage()) { + defaultPinnedShortcutTitles.forEach { title -> verifyExistingTopSitesTabs(title) } } verifyAddShortcutButtonExists() } @@ -86,7 +89,9 @@ class SponsoredShortcutsTest { .goBack {} .goBack(composeTestRule) { verifyNotExistingSponsoredTopSitesList() - verifyAddShortcutButtonExists() + if (isDefaultPinnedShortcutsOnHomepage()) { + verifyAddShortcutButtonExists() + } } } @@ -95,7 +100,7 @@ class SponsoredShortcutsTest { fun openSponsoredShortcutTest() { homeScreen(composeTestRule) { verifyExistingTopSitesList() - sponsoredShortcutTitle = getSponsoredShortcutTitle(2) + sponsoredShortcutTitle = getSponsoredShortcutTitle(1) } .openTopSiteTabWithTitle(sponsoredShortcutTitle) { verifyUrl(sponsoredShortcutTitle) @@ -107,7 +112,7 @@ class SponsoredShortcutsTest { fun openSponsoredShortcutInPrivateTabTest() { homeScreen(composeTestRule) { verifyExistingTopSitesList() - sponsoredShortcutTitle = getSponsoredShortcutTitle(2) + sponsoredShortcutTitle = getSponsoredShortcutTitle(1) } .openContextMenuOnTopSitesWithTitle(sponsoredShortcutTitle) {} .openTopSiteInPrivateTab { @@ -120,7 +125,7 @@ class SponsoredShortcutsTest { fun openSponsorsAndYourPrivacyOptionTest() { homeScreen(composeTestRule) { verifyExistingTopSitesList() - sponsoredShortcutTitle = getSponsoredShortcutTitle(2) + sponsoredShortcutTitle = getSponsoredShortcutTitle(1) } .openContextMenuOnTopSitesWithTitle(sponsoredShortcutTitle) {} .clickSponsorsAndPrivacyButton { @@ -133,7 +138,7 @@ class SponsoredShortcutsTest { fun openSponsoredShortcutsSettingsOptionTest() { homeScreen(composeTestRule) { verifyExistingTopSitesList() - sponsoredShortcutTitle = getSponsoredShortcutTitle(2) + sponsoredShortcutTitle = getSponsoredShortcutTitle(1) } .openContextMenuOnTopSitesWithTitle(sponsoredShortcutTitle) {} .clickSponsoredShortcutsSettingsButton { @@ -146,11 +151,11 @@ class SponsoredShortcutsTest { fun verifySponsoredShortcutsDetailsTest() { homeScreen(composeTestRule) { verifyExistingTopSitesList() - sponsoredShortcutTitle = getSponsoredShortcutTitle(2) - sponsoredShortcutTitle2 = getSponsoredShortcutTitle(3) + sponsoredShortcutTitle = getSponsoredShortcutTitle(1) + sponsoredShortcutTitle2 = getSponsoredShortcutTitle(2) - verifySponsoredShortcutDetails(sponsoredShortcutTitle, 2) - verifySponsoredShortcutDetails(sponsoredShortcutTitle2, 3) + verifySponsoredShortcutDetails(sponsoredShortcutTitle, 1) + verifySponsoredShortcutDetails(sponsoredShortcutTitle2, 2) } } @@ -165,11 +170,11 @@ class SponsoredShortcutsTest { homeScreen(composeTestRule) { verifyExistingTopSitesList() - sponsoredShortcutTitle = getSponsoredShortcutTitle(2) - sponsoredShortcutTitle2 = getSponsoredShortcutTitle(3) + sponsoredShortcutTitle = getSponsoredShortcutTitle(1) + sponsoredShortcutTitle2 = getSponsoredShortcutTitle(2) - verifySponsoredShortcutDetails(sponsoredShortcutTitle, 2) - verifySponsoredShortcutDetails(sponsoredShortcutTitle2, 3) + verifySponsoredShortcutDetails(sponsoredShortcutTitle, 1) + verifySponsoredShortcutDetails(sponsoredShortcutTitle2, 2) } navigationToolbar(composeTestRule) {} .enterURLAndEnterToBrowser(firstWebPage.url) { @@ -213,7 +218,7 @@ class SponsoredShortcutsTest { } .clickAddToShortcutsButton {} .goToHomescreen { - verifySponsoredShortcutDetails(sponsoredShortcutTitle, 2) + verifySponsoredShortcutDetails(sponsoredShortcutTitle, 1) verifySponsoredShortcutDoesNotExist(sponsoredShortcutTitle2, 3) } } @@ -234,11 +239,11 @@ class SponsoredShortcutsTest { homeScreen(composeTestRule) { verifyExistingTopSitesList() - sponsoredShortcutTitle = getSponsoredShortcutTitle(2) - sponsoredShortcutTitle2 = getSponsoredShortcutTitle(3) + sponsoredShortcutTitle = getSponsoredShortcutTitle(1) + sponsoredShortcutTitle2 = getSponsoredShortcutTitle(2) - verifySponsoredShortcutDetails(sponsoredShortcutTitle, 2) - verifySponsoredShortcutDetails(sponsoredShortcutTitle2, 3) + verifySponsoredShortcutDetails(sponsoredShortcutTitle, 1) + verifySponsoredShortcutDetails(sponsoredShortcutTitle2, 2) MockBrowserDataHelper.addPinnedSite( Pair(pagesList[0].title, pagesList[0].url.toString()), diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/TopSitesTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/TopSitesTest.kt index 753ef59abe903..2efd86079c437 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/TopSitesTest.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/TopSitesTest.kt @@ -12,9 +12,11 @@ import org.junit.Test import org.mozilla.fenix.R import org.mozilla.fenix.customannotations.Converted import org.mozilla.fenix.customannotations.SmokeTest +import org.mozilla.fenix.helpers.AppAndSystemHelper.isDefaultPinnedShortcutsOnHomepage import org.mozilla.fenix.helpers.Constants.RETRY_COUNT import org.mozilla.fenix.helpers.Constants.TAG -import org.mozilla.fenix.helpers.Constants.defaultTopSitesList +import org.mozilla.fenix.helpers.Constants.defaultPinnedShortcutTitles +import org.mozilla.fenix.helpers.Constants.sponsoredShortcutTitles import org.mozilla.fenix.helpers.DataGenerationHelper.generateRandomString import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource import org.mozilla.fenix.helpers.FenixTestRule @@ -59,11 +61,7 @@ class TopSitesTest { homeScreen(composeTestRule) {} .openThreeDotMenu {} .clickSettingsButton {} - .goBack(composeTestRule) { - defaultTopSitesList.values.forEach { value -> - verifyExistingTopSitesTabs(value) - } - } + .goBack(composeTestRule) { verifyExistingTopSitesList() } break } catch (e: Throwable) { @@ -261,8 +259,9 @@ class TopSitesTest { fun verifyENLocalesDefaultTopSitesListTest() { homeScreen(composeTestRule) { verifyExistingTopSitesList() - defaultTopSitesList.values.forEach { value -> - verifyExistingTopSitesTabs(value) + sponsoredShortcutTitles.forEach { title -> verifyExistingTopSitesTabs(title) } + if (isDefaultPinnedShortcutsOnHomepage()) { + defaultPinnedShortcutTitles.forEach { title -> verifyExistingTopSitesTabs(title) } } verifyAddShortcutButtonExists() } diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt index c550427ef007c..745ed23d10035 100644 --- a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt +++ b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt @@ -48,6 +48,7 @@ import androidx.test.uiautomator.By import androidx.test.uiautomator.UiObjectNotFoundException import androidx.test.uiautomator.UiScrollable import androidx.test.uiautomator.UiSelector +import kotlin.test.assertNotNull import mozilla.components.browser.menu.R as menuR import mozilla.components.compose.base.R as composeBaseR import mozilla.components.compose.browser.toolbar.concept.BrowserToolbarTestTags.ADDRESSBAR_URL_BOX @@ -58,6 +59,7 @@ import org.mozilla.fenix.R import org.mozilla.fenix.helpers.Constants.RETRY_COUNT import org.mozilla.fenix.helpers.Constants.TAG import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource +import org.mozilla.fenix.helpers.DataGenerationHelper.sponsoredShortcuts import org.mozilla.fenix.helpers.MatcherHelper.assertItemIsChecked import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText @@ -256,18 +258,22 @@ class HomeScreenRobot(private val composeTestRule: ComposeTestRule) { Log.i(TAG, "verifyAddShortcutButtonExists: Verified that the \"Add shortcut\" button exists") } - fun verifySponsoredShortcutDetails(sponsoredShortcutTitle: String, position: Int) { - assertUIObjectExists( - itemWithResIdAndIndex(resourceId = "top_sites_list.top_site_item", index = position - 1) - .getChild(UiSelector().resourceId(TOP_SITE_CARD_FAVICON)) - ) - assertUIObjectExists( - itemWithResIdAndIndex(resourceId = "top_sites_list.top_site_item", index = position - 1) - .getChild(UiSelector().textContains(sponsoredShortcutTitle)) + /** + * Asserts the [ordinal]th sponsored shortcut has a favicon and the expected title. + * + * @param sponsoredShortcutTitle The shortcut title which is expected. + * @param ordinal One-based index among the sponsored shortcuts only, matching + * [org.mozilla.fenix.helpers.DataGenerationHelper.getSponsoredShortcutTitle]. + */ + fun verifySponsoredShortcutDetails(sponsoredShortcutTitle: String, ordinal: Int) { + val sponsoredShortcut = sponsoredShortcuts()[ordinal - 1] + assertNotNull( + sponsoredShortcut.findObject(By.res(TOP_SITE_CARD_FAVICON)), + "Sponsored shortcut #$ordinal has no favicon", ) - assertUIObjectExists( - itemWithResIdAndIndex(resourceId = "top_sites_list.top_site_item", index = position - 1) - .getChild(UiSelector().textContains(getStringResource(R.string.top_sites_sponsored_label))) + assertNotNull( + sponsoredShortcut.findObject(By.textContains(sponsoredShortcutTitle)), + "Sponsored shortcut #$ordinal is not titled $sponsoredShortcutTitle", ) }