diff --git a/app/src/androidTest/java/org/mozilla/fenix/helpers/TestHelper.kt b/app/src/androidTest/java/org/mozilla/fenix/helpers/TestHelper.kt index f7582b41d5..ae3b0b3401 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/helpers/TestHelper.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/helpers/TestHelper.kt @@ -2,6 +2,8 @@ * 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("DEPRECATION") + package org.mozilla.fenix.helpers import android.app.ActivityManager @@ -10,6 +12,7 @@ import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color @@ -31,6 +34,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withParent import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.rule.ActivityTestRule import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiObject @@ -38,6 +42,8 @@ import androidx.test.uiautomator.UiObjectNotFoundException import androidx.test.uiautomator.UiScrollable import androidx.test.uiautomator.UiSelector import androidx.test.uiautomator.Until +import java.util.Locale +import java.util.regex.Pattern import junit.framework.AssertionFailedError import mozilla.components.browser.state.search.SearchEngine import mozilla.components.support.ktx.android.content.appName @@ -46,6 +52,7 @@ import org.hamcrest.CoreMatchers.allOf import org.hamcrest.Matcher import org.junit.Assert import org.junit.Assert.assertTrue +import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.customtabs.ExternalAppBrowserActivity import org.mozilla.fenix.ext.components @@ -56,7 +63,7 @@ import org.mozilla.fenix.helpers.ext.waitNotNull import org.mozilla.fenix.helpers.idlingresource.NetworkConnectionIdlingResource import org.mozilla.fenix.ui.robots.BrowserRobot import org.mozilla.fenix.utils.IntentUtils -import java.util.regex.Pattern +import org.mozilla.gecko.util.ThreadUtils object TestHelper { @@ -329,4 +336,39 @@ object TestHelper { .map { kotlin.random.Random.nextInt(0, charPool.size) } .map(charPool::get) .joinToString("") + + /** + * Changes the default language of the entire device, not just the app. + * Runs the test in its testBlock. + * Cleans up and sets the default locale after it's are done. + */ + fun runWithSystemLocaleChanged(locale: Locale, testRule: ActivityTestRule, testBlock: () -> Unit) { + val defaultLocale = Locale.getDefault() + + try { + setSystemLocale(locale) + testBlock() + ThreadUtils.runOnUiThread { testRule.activity.recreate() } + } catch (e: Exception) { + e.printStackTrace() + } finally { + setSystemLocale(defaultLocale) + } + } + + /** + * Changes the default language of the entire device, not just the app. + */ + private fun setSystemLocale(locale: Locale) { + val activityManagerNative = Class.forName("android.app.ActivityManagerNative") + val am = activityManagerNative.getMethod("getDefault", *arrayOfNulls(0)) + .invoke(activityManagerNative, *arrayOfNulls(0)) + val config = InstrumentationRegistry.getInstrumentation().context.resources.configuration + config.javaClass.getDeclaredField("locale")[config] = locale + config.javaClass.getDeclaredField("userSetLocale").setBoolean(config, true) + am.javaClass.getMethod( + "updateConfiguration", + Configuration::class.java + ).invoke(am, config) + } } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt index 1873b3a776..a8513310a3 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt @@ -5,27 +5,40 @@ package org.mozilla.fenix.ui import android.content.res.Configuration +import androidx.test.espresso.IdlingRegistry +import java.time.LocalDate +import java.util.Locale import okhttp3.mockwebserver.MockWebServer import org.junit.After import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.mozilla.fenix.FenixApplication +import org.mozilla.fenix.R import org.mozilla.fenix.customannotations.SmokeTest import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.FeatureSettingsHelper import org.mozilla.fenix.helpers.HomeActivityIntentTestRule +import org.mozilla.fenix.helpers.RecyclerViewIdlingResource import org.mozilla.fenix.helpers.TestAssetHelper import org.mozilla.fenix.helpers.TestAssetHelper.getLoremIpsumAsset -import org.mozilla.fenix.ui.SettingsBasicsTest.creditCard.MOCK_CREDIT_CARD_NUMBER -import org.mozilla.fenix.ui.SettingsBasicsTest.creditCard.MOCK_EXPIRATION_MONTH -import org.mozilla.fenix.ui.SettingsBasicsTest.creditCard.MOCK_EXPIRATION_YEAR -import org.mozilla.fenix.ui.SettingsBasicsTest.creditCard.MOCK_LAST_CARD_DIGITS -import org.mozilla.fenix.ui.SettingsBasicsTest.creditCard.MOCK_NAME_ON_CARD +import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeLong +import org.mozilla.fenix.helpers.TestHelper.runWithSystemLocaleChanged +import org.mozilla.fenix.helpers.TestHelper.getStringResource +import org.mozilla.fenix.helpers.TestHelper.mDevice +import org.mozilla.fenix.ui.SettingsBasicsTest.CreditCard.MOCK_CREDIT_CARD_NUMBER +import org.mozilla.fenix.ui.SettingsBasicsTest.CreditCard.MOCK_EXPIRATION_MONTH +import org.mozilla.fenix.ui.SettingsBasicsTest.CreditCard.MOCK_EXPIRATION_YEAR +import org.mozilla.fenix.ui.SettingsBasicsTest.CreditCard.MOCK_LAST_CARD_DIGITS +import org.mozilla.fenix.ui.SettingsBasicsTest.CreditCard.MOCK_NAME_ON_CARD import org.mozilla.fenix.ui.robots.checkTextSizeOnWebsite import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.navigationToolbar -import java.time.LocalDate +import org.mozilla.fenix.ui.util.FRENCH_LANGUAGE_HEADER +import org.mozilla.fenix.ui.util.FRENCH_SYSTEM_LOCALE_OPTION +import org.mozilla.fenix.ui.util.FR_SETTINGS +import org.mozilla.fenix.ui.util.ROMANIAN_LANGUAGE_HEADER /** * Tests for verifying the General section of the Settings menu @@ -35,8 +48,9 @@ class SettingsBasicsTest { /* ktlint-disable no-blank-line-before-rbrace */ // This imposes unreadable grouping. private lateinit var mockWebServer: MockWebServer private val featureSettingsHelper = FeatureSettingsHelper() + private var localeListIdlingResource: RecyclerViewIdlingResource? = null - object creditCard { + object CreditCard { const val MOCK_CREDIT_CARD_NUMBER = "5555555555554444" const val MOCK_LAST_CARD_DIGITS = "4444" const val MOCK_NAME_ON_CARD = "Mastercard" @@ -64,6 +78,10 @@ class SettingsBasicsTest { // resetting modified features enabled setting to default featureSettingsHelper.resetAllFeatureFlags() + + if (localeListIdlingResource != null) { + IdlingRegistry.getInstance().unregister(localeListIdlingResource) + } } private fun getUiTheme(): Boolean { @@ -250,4 +268,68 @@ class SettingsBasicsTest { verifyAddCreditCardsButton() } } + + @SmokeTest + @Test + fun switchLanguageTest() { + val enLanguageHeaderText = getStringResource(R.string.preferences_language) + + homeScreen { + }.openThreeDotMenu { + }.openSettings { + }.openLanguageSubMenu { + localeListIdlingResource = + RecyclerViewIdlingResource( + activityIntentTestRule.activity.findViewById(R.id.locale_list), + 2 + ) + IdlingRegistry.getInstance().register(localeListIdlingResource) + selectLanguage("Romanian") + verifyLanguageHeaderIsTranslated(ROMANIAN_LANGUAGE_HEADER) + selectLanguage("Français") + verifyLanguageHeaderIsTranslated(FRENCH_LANGUAGE_HEADER) + selectLanguage(FRENCH_SYSTEM_LOCALE_OPTION) + verifyLanguageHeaderIsTranslated(enLanguageHeaderText) + IdlingRegistry.getInstance().unregister(localeListIdlingResource) + } + } + + @Test + fun searchInLanguagesListTest() { + val systemLocaleDefault = getStringResource(R.string.default_locale_text) + + homeScreen { + }.openThreeDotMenu { + }.openSettings { + }.openLanguageSubMenu { + verifyLanguageListIsDisplayed() + openSearchBar() + typeInSearchBar("French") + verifySearchResultsContains(systemLocaleDefault) + clearSearchBar() + typeInSearchBar("French") + selectLanguageSearchResult("Français") + verifyLanguageHeaderIsTranslated(FRENCH_LANGUAGE_HEADER) + // Add this step when https://github.com/mozilla-mobile/fenix/issues/26733 is fixed + // verifyLanguageListIsDisplayed() + } + } + + @Ignore("Failing due to app translation bug, see: https://github.com/mozilla-mobile/fenix/issues/26729") + @Test + fun frenchSystemLocaleTest() { + val frenchLocale = Locale("fr", "FR") + + runWithSystemLocaleChanged(frenchLocale, activityIntentTestRule) { + mDevice.waitForIdle(waitingTimeLong) + + homeScreen { + }.openThreeDotMenu { + }.openSettings(localizedText = FR_SETTINGS) { + }.openLanguageSubMenu(localizedText = FRENCH_LANGUAGE_HEADER) { + verifyLanguageHeaderIsTranslated(FRENCH_LANGUAGE_HEADER) + verifySelectedLanguage(FRENCH_SYSTEM_LOCALE_OPTION) + } + } + } } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt index 8e08c2adfa..30d51dc1b8 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt @@ -47,9 +47,6 @@ import org.mozilla.fenix.ui.robots.navigationToolbar import org.mozilla.fenix.ui.robots.notificationShade import org.mozilla.fenix.ui.robots.openEditURLView import org.mozilla.fenix.ui.robots.searchScreen -import org.mozilla.fenix.ui.util.FRENCH_LANGUAGE_HEADER -import org.mozilla.fenix.ui.util.FRENCH_SYSTEM_LOCALE_OPTION -import org.mozilla.fenix.ui.util.ROMANIAN_LANGUAGE_HEADER import org.mozilla.fenix.ui.util.STRING_ONBOARDING_TRACKING_PROTECTION_HEADER /** @@ -68,7 +65,6 @@ class SmokeTest { private var recentlyClosedTabsListIdlingResource: RecyclerViewIdlingResource? = null private var readerViewNotification: ViewVisibilityIdlingResource? = null private var bookmarksListIdlingResource: RecyclerViewIdlingResource? = null - private var localeListIdlingResource: RecyclerViewIdlingResource? = null private val customMenuItem = "TestMenuItem" private lateinit var browserStore: BrowserStore private val featureSettingsHelper = FeatureSettingsHelper() @@ -129,10 +125,6 @@ class SmokeTest { IdlingRegistry.getInstance().unregister(readerViewNotification) } - if (localeListIdlingResource != null) { - IdlingRegistry.getInstance().unregister(localeListIdlingResource) - } - // resetting modified features enabled setting to default featureSettingsHelper.resetAllFeatureFlags() } @@ -953,28 +945,6 @@ class SmokeTest { } } - @Test - fun switchLanguageTest() { - homeScreen { - }.openThreeDotMenu { - }.openSettings { - }.openLanguageSubMenu { - localeListIdlingResource = - RecyclerViewIdlingResource( - activityTestRule.activity.findViewById(R.id.locale_list), - 2 - ) - IdlingRegistry.getInstance().register(localeListIdlingResource) - selectLanguage("Romanian") - verifyLanguageHeaderIsTranslated(ROMANIAN_LANGUAGE_HEADER) - selectLanguage("Français") - verifyLanguageHeaderIsTranslated(FRENCH_LANGUAGE_HEADER) - selectLanguage(FRENCH_SYSTEM_LOCALE_OPTION) - verifyLanguageHeaderIsTranslated("Language") - IdlingRegistry.getInstance().unregister(localeListIdlingResource) - } - } - @Test fun goToHomeScreenBottomToolbarTest() { val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsRobot.kt index 1855cbaa9e..71e0c3d7e5 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsRobot.kt @@ -192,12 +192,15 @@ class SettingsRobot { return SettingsSubMenuAccessibilityRobot.Transition() } - fun openLanguageSubMenu(interact: SettingsSubMenuLanguageRobot.() -> Unit): SettingsSubMenuLanguageRobot.Transition { + fun openLanguageSubMenu( + localizedText: String = getStringResource(R.string.preferences_language), + interact: SettingsSubMenuLanguageRobot.() -> Unit + ): SettingsSubMenuLanguageRobot.Transition { onView(withId(R.id.recycler_view)) .perform( RecyclerViewActions.actionOnItem( hasDescendant( - withText(R.string.preferences_language) + withText(localizedText) ), ViewActions.click() ) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuLanguageRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuLanguageRobot.kt index d54bb54760..7724f98e6f 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuLanguageRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuLanguageRobot.kt @@ -7,13 +7,16 @@ package org.mozilla.fenix.ui.robots import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.uiautomator.UiScrollable import androidx.test.uiautomator.UiSelector import junit.framework.TestCase.assertTrue import org.hamcrest.CoreMatchers +import org.mozilla.fenix.R import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.packageName +import org.mozilla.fenix.helpers.click class SettingsSubMenuLanguageRobot { fun selectLanguage(language: String) { @@ -23,9 +26,45 @@ class SettingsSubMenuLanguageRobot { .click() } + fun selectLanguageSearchResult(languageName: String) { + language(languageName).waitForExists(waitingTime) + language(languageName).click() + } + fun verifyLanguageHeaderIsTranslated(translation: String) = assertTrue(mDevice.findObject(UiSelector().text(translation)).waitForExists(waitingTime)) + fun verifySelectedLanguage(language: String) { + languagesList.waitForExists(waitingTime) + assertTrue( + languagesList + .getChildByText(UiSelector().text(language), language, true) + .getFromParent(UiSelector().resourceId("$packageName:id/locale_selected_icon")) + .waitForExists(waitingTime) + ) + } + + fun openSearchBar() { + onView(withId(R.id.search)).click() + } + + fun typeInSearchBar(text: String) { + searchBar.waitForExists(waitingTime) + searchBar.text = text + } + + fun verifySearchResultsContains(languageName: String) { + assertTrue(language(languageName).waitForExists(waitingTime)) + } + + fun clearSearchBar() { + onView(withId(R.id.search_close_btn)).click() + } + + fun verifyLanguageListIsDisplayed() { + assertTrue(languagesList.waitForExists(waitingTime)) + } + class Transition { fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition { @@ -47,3 +86,8 @@ private val languagesList = .resourceId("$packageName:id/locale_list") .scrollable(true) ) + +private fun language(name: String) = mDevice.findObject(UiSelector().text(name)) + +private val searchBar = + mDevice.findObject(UiSelector().resourceId("$packageName:id/search_src_text")) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt index 4809d59820..e166e945e2 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt @@ -33,6 +33,7 @@ import org.mozilla.fenix.R import org.mozilla.fenix.helpers.Constants.RETRY_COUNT import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeLong +import org.mozilla.fenix.helpers.TestHelper.getStringResource import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.packageName import org.mozilla.fenix.helpers.click @@ -145,13 +146,16 @@ class ThreeDotMenuMainRobot { } class Transition { - fun openSettings(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition { + fun openSettings( + localizedText: String = getStringResource(R.string.browser_menu_settings), + interact: SettingsRobot.() -> Unit + ): SettingsRobot.Transition { // We require one swipe to display the full size 3-dot menu. On smaller devices // such as the Pixel 2, we require two swipes to display the "Settings" menu item // at the bottom. On larger devices, the second swipe is a no-op. threeDotMenuRecyclerView().perform(swipeUp()) threeDotMenuRecyclerView().perform(swipeUp()) - settingsButton().click() + settingsButton(localizedText).click() SettingsRobot().interact() return SettingsRobot.Transition() @@ -405,7 +409,9 @@ private fun threeDotMenuRecyclerViewExists() { threeDotMenuRecyclerView().check(matches(isDisplayed())) } -private fun settingsButton() = mDevice.findObject(UiSelector().text("Settings")) +private fun settingsButton(localizedText: String = getStringResource(R.string.browser_menu_settings)) = + mDevice.findObject(UiSelector().text(localizedText)) + private fun assertSettingsButton() = assertTrue(settingsButton().waitForExists(waitingTime)) private fun customizeHomeButton() = diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/util/Strings.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/util/Strings.kt index d7448ca562..61973d9700 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/util/Strings.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/util/Strings.kt @@ -8,5 +8,6 @@ const val STRING_ONBOARDING_ACCOUNT_SIGN_IN_HEADER = "Sync Firefox between devic const val STRING_ONBOARDING_TRACKING_PROTECTION_HEADER = "Always-on privacy" const val STRING_ONBOARDING_TOOLBAR_PLACEMENT_HEADER = "Pick your toolbar placement" const val FRENCH_LANGUAGE_HEADER = "Langues" -const val ROMANIAN_LANGUAGE_HEADER = "Limbă" +const val FR_SETTINGS = "Paramètres" const val FRENCH_SYSTEM_LOCALE_OPTION = "Utiliser la langue de l’appareil" +const val ROMANIAN_LANGUAGE_HEADER = "Limbă"