From 847fa3663b870fb0a6ba7bc3a28b362637ec659f Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 20 Feb 2024 00:03:19 +0000 Subject: [PATCH 001/238] Import translations from android-l10n --- app/src/main/res/values-es-rAR/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 134 ++++++++++++++------- app/src/main/res/values-sv-rSE/strings.xml | 2 +- app/src/main/res/values-vi/strings.xml | 2 +- 4 files changed, 96 insertions(+), 44 deletions(-) diff --git a/app/src/main/res/values-es-rAR/strings.xml b/app/src/main/res/values-es-rAR/strings.xml index 36b024d21..68be82b68 100644 --- a/app/src/main/res/values-es-rAR/strings.xml +++ b/app/src/main/res/values-es-rAR/strings.xml @@ -505,7 +505,7 @@ Sitio seguro no disponible - Lo más probable es que el sitio web simplemente no sea compatible con HTTPs. + Lo más probable es que el sitio web simplemente no sea compatible con HTTPS. Sin embargo, también es posible que un atacante esté involucrado. Si continúa al sitio web, no debe ingresar ninguna información sensible. Si continúa, el modo solo HTTPS se desactivará temporalmente para el sitio. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 05599fb39..5f00a5e88 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -247,6 +247,7 @@ Personalizar la página de inicio + Pantalla de inicio @@ -254,6 +255,9 @@ Eliminar historial de navegación + + Traducir página + Idioma seleccionado @@ -265,8 +269,6 @@ Escanear - - Buscador Ajustes del buscador @@ -322,14 +324,14 @@ - Las notificaciones te ayudan a hacer más con %s + Las notificaciones te ayudan a hacer más con %s - Sincroniza tus pestañas entre dispositivos, administra descargas, obtén consejos sobre cómo aprovechar al máximo la protección de privacidad de %s y más. + Sincroniza tus pestañas entre dispositivos, administra descargas, obtén consejos sobre cómo aprovechar al máximo la protección de privacidad de %s y más. - Continuar + Continuar - Ahora no + Ahora no @@ -448,21 +450,11 @@ Modo solo HTTPS - - Reducción de avisos de cookies Bloqueador de avisos de cookies Bloqueador de avisos de cookies en navegación privada - - Reducir los avisos de cookies - - Desactivado - - Activado - - - %1$s intenta rechazar automáticamente las solicitudes de cookies en los avisos de cookies. + Desactivada para este sitio @@ -480,35 +472,16 @@ Sitio actualmente no compatible - ¿Activar la reducción de aviso de cookies para %1$s? - ¿Activar el bloqueo de aviso de cookies para %1$s? - ¿Desactivar la reducción de aviso de cookies para %1$s? - ¿Desactivar el bloqueo de aviso de cookies para %1$s? %1$s no puede rechazar automáticamente los avisos de cookies en este sitio. Puede enviar una solicitud para admitir este sitio en el futuro. - - %1$s borrará las cookies de este sitio y recargará la página. Borrar todas las cookies puede cerrar tu sesión o vaciar los carritos de compras. Tras desactivarlo, %1$s borrará las cookies y recargará la página. Esto puede desconectarte del sitio o vaciar tu carrito de compra. - %1$s intenta rechazar automáticamente las solicitudes de cookies en sitios compatibles. - Al activarlo %1$s intentará rechazar automáticamente los avisos de cookies en este sitio. - - ¿Permitir que %1$s rechace los avisos de cookies? - - %1$s puede rechazar automáticamente muchas solicitudes de cookies. - - Ahora no - - Verás menos solicitudes de cookies - - - Permitir %1$s acaba de rechazar las cookies por ti @@ -725,6 +698,8 @@ Marcadores Inicios de sesión + + Contraseñas Pestañas abiertas @@ -751,6 +726,8 @@ Tarjetas de crédito + + Métodos de pago Direcciones @@ -1306,8 +1283,6 @@ Descartar - No se puede imprimir - No se puede imprimir esta página Imprimir @@ -1720,8 +1695,12 @@ Inicios de sesión y contraseñas + + Contraseñas Guardar inicios de sesión y contraseñas + + Guardar contraseñas Preguntar antes de guardar @@ -1740,26 +1719,45 @@ Añadir cuenta + + Añadir contraseña + Inicios de sesión sincronizados + + Sincronizar contraseñas Sincronizar inicios de sesión entre dispositivos + + Sincronizar contraseñas entre dispositivos Inicios de sesión guardados + + Contraseñas guardadas Los inicios de sesión que guardes o sincronices con %s se mostrarán aquí. + + Las contraseñas que guardes o sincronices con %s aparecerán aquí. Todas las contraseñas que guardes quedan cifradas. Saber más sobre Sync. + + Descubre más sobre Sync Excepciones Los inicios de sesión y contraseñas no guardados aparecerán aquí. + + %s no guardará contraseñas para los sitios que se listen aquí. No se guardarán los inicios de sesión y contraseñas para estos sitios. + + %s no guardará las contraseñas para estos sitios. Eliminar todas las excepciones Buscar inicios de sesión + + Buscar contraseñas Sitio @@ -1788,10 +1786,16 @@ Ocultar contraseña Desbloquear para ver tus inicios de sesión guardados + + Desbloquea para ver tus contraseñas guardadas Asegurar tus usuarios y contraseñas + + Asegura tus contraseñas guardadas Configura un patrón de bloqueo del dispositivo, un PIN o una contraseña para proteger el acceso a tus usuarios y contraseñas guardados si alguien más tiene tu dispositivo. + + Establece un patrón de bloqueo de dispositivo, PIN o contraseña para proteger tus contraseñas guardadas y evitar que sean accedidas por otras personas en caso de que alguien más tenga tu dispositivo. Más tarde @@ -1812,6 +1816,9 @@ Ordenar menú de inicio de sesión + + Menú ordenar contraseñas + Autocompletado @@ -1819,11 +1826,17 @@ Direcciones Tarjetas de crédito + + Métodos de pago Guardar y autocompletar tarjetas + + Guardar y completar métodos de pago Los datos están cifrados + + %s cifra todos los métodos de pago que guardes Sincronizar tarjetas entre dispositivos @@ -1831,17 +1844,26 @@ Añadir tarjeta de crédito + + Añadir tarjeta Administrar tarjetas guardadas + + Administrar tarjetas Añadir dirección Administrar direcciones Guardar y autocompletar direcciones + + Guardar y completar direcciones Incluir información como números, correos electrónicos y direcciones de envío + + Incluye números de teléfono y direcciones de correo electrónico + Añadir tarjeta @@ -1862,6 +1884,8 @@ Eliminar tarjeta ¿Seguro que quieres eliminar esta tarjeta de crédito? + + ¿Eliminar tarjeta? Eliminar @@ -1877,14 +1901,22 @@ Por favor, escriba un número válido de tarjeta de crédito + + Introduce un número de tarjeta válido Por favor, rellena este campo + + Añadir un nombre Desbloquear para ver tus tarjetas guardadas Asegurar tus tarjetas de crédito + + Protege tus métodos de pago guardados Configura un patrón de bloqueo, PIN o contraseña para proteger el acceso a tus tarjetas guardadas si alguien más accede a tu dispositivo. + + Establece un patrón de bloqueo de dispositivo, PIN o contraseña para proteger tus métodos de pago guardados y evitar que sean accedidos por otras personas en caso de que alguien más tenga tu dispositivo. Configurar ahora @@ -1894,6 +1926,8 @@ Desbloquear para usar la información de la tarjeta de crédito almacenada + + Desbloquea para utilizar métodos de pago guardados Añadir dirección @@ -1931,6 +1965,8 @@ ¿Seguro que quieres eliminar esta dirección? + + ¿Eliminar esta dirección? Eliminar @@ -2030,26 +2066,44 @@ Editar ¿Seguro que quieres eliminar este inicio de sesión? + + ¿Estás seguro de que quieres eliminar esta contraseña? Eliminar Cancelar Opciones de inicio de sesión + + Opciones de contraseña El campo de texto editable para la dirección web del inicio de sesión. + + El campo de texto editable para la dirección del sitio web. El campo de texto editable para el nombre de usuario del inicio de sesión. + + El campo de texto editable para el nombre de usuario. El campo de texto editable para la contraseña del inicio de sesión. + + El campo de texto editable para la contraseña. Guardar cambios para el inicio de sesión. + + Guardar cambios. Editar + + Editar contraseña Añadir nueva cuenta + + Añadir contraseña Se necesita contraseña + + Introduce una contraseña Se requiere nombre de usuario @@ -2149,7 +2203,7 @@ Buscar con %s - + Configura enlaces de sitios web, correos electrónicos y mensajes para que se abran automáticamente en Firefox. @@ -2223,8 +2277,6 @@ puntos destacados provienen de reseñas de %s de los últimos 80 días que creemos que son fiables.]]> Saber más sobre %s. - - cómo %s de Mozilla determina la calidad de las reseñas cómo determina %s la calidad de las reseñas diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index b4c270ba6..f718c9bed 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -505,7 +505,7 @@ Säker webbplats är inte tillgänglig - Troligtvis stöder webbplatsen helt enkelt inte HTTPs. + Troligtvis stöder webbplatsen helt enkelt inte HTTPS. Men det är också möjligt att en angripare är inblandad. Om du fortsätter till webbplatsen ska du inte ange någon känslig information. Om du fortsätter kommer endast HTTPS-läget att stängas av tillfälligt för webbplatsen. diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 356737044..1b7a232b0 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -498,7 +498,7 @@ Trang web an toàn không khả dụng - Rất có thể, trang web chỉ đơn giản là không hỗ trợ HTTPs. + Rất có thể, trang web chỉ đơn giản là không hỗ trợ HTTPS. Tuy nhiên, cũng có thể có kẻ tấn công tham gia. Nếu bạn tiếp tục vào trang web, bạn không nên nhập bất kỳ thông tin nhạy cảm nào. Nếu bạn tiếp tục, chế độ chỉ HTTPS sẽ tạm thời bị tắt cho trang web. From 518d89d449ca3f622a927bf96b7a261e99c57e7a Mon Sep 17 00:00:00 2001 From: AndiAJ Date: Fri, 16 Feb 2024 13:35:25 +0200 Subject: [PATCH 002/238] Bug 1880640 - Convert private variables to functions so they don't get initialised --- .../ui/robots/SettingsSubMenuAutofillRobot.kt | 316 +++++++++--------- 1 file changed, 158 insertions(+), 158 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuAutofillRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuAutofillRobot.kt index 349451b69..6f7a581cd 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuAutofillRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuAutofillRobot.kt @@ -36,7 +36,7 @@ import org.mozilla.fenix.helpers.click class SettingsSubMenuAutofillRobot { fun verifyAutofillToolbarTitle() { - assertUIObjectExists(autofillToolbarTitle) + assertUIObjectExists(autofillToolbarTitle()) Log.i(TAG, "verifyAutofillToolbarTitle: Verified \"Autofill\" toolbar title exists") } fun verifyManageAddressesToolbarTitle() { @@ -54,16 +54,16 @@ class SettingsSubMenuAutofillRobot { fun verifyAddressAutofillSection(isAddressAutofillEnabled: Boolean, userHasSavedAddress: Boolean) { assertUIObjectExists( - autofillToolbarTitle, - addressesSectionTitle, - saveAndAutofillAddressesOption, - saveAndAutofillAddressesSummary, + autofillToolbarTitle(), + addressesSectionTitle(), + saveAndAutofillAddressesOption(), + saveAndAutofillAddressesSummary(), ) if (userHasSavedAddress) { - assertUIObjectExists(manageAddressesButton) + assertUIObjectExists(manageAddressesButton()) } else { - assertUIObjectExists(addAddressButton) + assertUIObjectExists(addAddressButton()) } verifyAddressesAutofillToggle(isAddressAutofillEnabled) @@ -71,18 +71,18 @@ class SettingsSubMenuAutofillRobot { fun verifyCreditCardsAutofillSection(isAddressAutofillEnabled: Boolean, userHasSavedCreditCard: Boolean) { assertUIObjectExists( - autofillToolbarTitle, - creditCardsSectionTitle, - saveAndAutofillCreditCardsOption, - saveAndAutofillCreditCardsSummary, - syncCreditCardsAcrossDevicesButton, + autofillToolbarTitle(), + creditCardsSectionTitle(), + saveAndAutofillCreditCardsOption(), + saveAndAutofillCreditCardsSummary(), + syncCreditCardsAcrossDevicesButton(), ) if (userHasSavedCreditCard) { - assertUIObjectExists(manageSavedCreditCardsButton) + assertUIObjectExists(manageSavedCreditCardsButton()) } else { - assertUIObjectExists(addCreditCardButton) + assertUIObjectExists(addCreditCardButton()) } verifySaveAndAutofillCreditCardsToggle(isAddressAutofillEnabled) @@ -90,9 +90,9 @@ class SettingsSubMenuAutofillRobot { fun verifyManageAddressesSection(vararg savedAddressDetails: String) { assertUIObjectExists( - navigateBackButton, - manageAddressesToolbarTitle, - addAddressButton, + navigateBackButton(), + manageAddressesToolbarTitle(), + addAddressButton(), ) for (savedAddressDetail in savedAddressDetails) { assertUIObjectExists(itemContainingText(savedAddressDetail)) @@ -102,9 +102,9 @@ class SettingsSubMenuAutofillRobot { fun verifySavedCreditCardsSection(creditCardLastDigits: String, creditCardExpiryDate: String) { assertUIObjectExists( - navigateBackButton, - savedCreditCardsToolbarTitle, - addCreditCardButton, + navigateBackButton(), + savedCreditCardsToolbarTitle(), + addCreditCardButton(), itemContainingText(creditCardLastDigits), itemContainingText(creditCardExpiryDate), ) @@ -148,35 +148,35 @@ class SettingsSubMenuAutofillRobot { fun verifyAddAddressView() { assertUIObjectExists( - addAddressToolbarTitle, - navigateBackButton, - toolbarCheckmarkButton, - firstNameTextInput, - middleNameTextInput, + addAddressToolbarTitle(), + navigateBackButton(), + toolbarCheckmarkButton(), + firstNameTextInput(), + middleNameTextInput(), ) scrollToElementByText(getStringResource(R.string.addresses_street_address)) Log.i(TAG, "verifyAddAddressView: Scrolled to \"Street Address\" text input") assertUIObjectExists( - lastNameTextInput, - streetAddressTextInput, + lastNameTextInput(), + streetAddressTextInput(), ) scrollToElementByText(getStringResource(R.string.addresses_country)) Log.i(TAG, "verifyAddAddressView: Scrolled to \"Country or region\" dropdown") assertUIObjectExists( - cityTextInput, - subRegionDropDown, - zipCodeTextInput, + cityTextInput(), + subRegionDropDown(), + zipCodeTextInput(), ) scrollToElementByText(getStringResource(R.string.addresses_save_button)) Log.i(TAG, "verifyAddAddressView: Scrolled to \"Save\" button") assertUIObjectExists( - countryDropDown, - phoneTextInput, - emailTextInput, + countryDropDown(), + phoneTextInput(), + emailTextInput(), ) assertUIObjectExists( - saveButton, - cancelButton, + saveButton(), + cancelButton(), ) } @@ -193,7 +193,7 @@ class SettingsSubMenuAutofillRobot { } fun verifyCountryOptions(vararg countries: String) { - countryDropDown.click() + countryDropDown().click() Log.i(TAG, "verifyCountryOptions: Clicked \"Country or region\" dropdown") for (country in countries) { assertUIObjectExists(itemContainingText(country)) @@ -201,7 +201,7 @@ class SettingsSubMenuAutofillRobot { } fun selectCountry(country: String) { - countryDropDown.click() + countryDropDown().click() Log.i(TAG, "selectCountry: Clicked \"Country or region\" dropdown") countryOption(country).click() Log.i(TAG, "selectCountry: Selected $country dropdown option") @@ -209,50 +209,50 @@ class SettingsSubMenuAutofillRobot { fun verifyEditAddressView() { assertUIObjectExists( - editAddressToolbarTitle, - navigateBackButton, - toolbarDeleteAddressButton, - toolbarCheckmarkButton, - firstNameTextInput, - middleNameTextInput, + editAddressToolbarTitle(), + navigateBackButton(), + toolbarDeleteAddressButton(), + toolbarCheckmarkButton(), + firstNameTextInput(), + middleNameTextInput(), ) scrollToElementByText(getStringResource(R.string.addresses_street_address)) Log.i(TAG, "verifyEditAddressView: Scrolled to \"Street Address\" text input") assertUIObjectExists( - lastNameTextInput, - streetAddressTextInput, + lastNameTextInput(), + streetAddressTextInput(), ) scrollToElementByText(getStringResource(R.string.addresses_country)) Log.i(TAG, "verifyEditAddressView: Scrolled to \"Country or region\" dropdown") assertUIObjectExists( - cityTextInput, - subRegionDropDown, - zipCodeTextInput, + cityTextInput(), + subRegionDropDown(), + zipCodeTextInput(), ) scrollToElementByText(getStringResource(R.string.addresses_save_button)) Log.i(TAG, "verifyEditAddressView: Scrolled to \"Save\" button") assertUIObjectExists( - countryDropDown, - phoneTextInput, - emailTextInput, + countryDropDown(), + phoneTextInput(), + emailTextInput(), ) assertUIObjectExists( - saveButton, - cancelButton, + saveButton(), + cancelButton(), ) - assertUIObjectExists(deleteAddressButton) + assertUIObjectExists(deleteAddressButton()) } fun clickSaveAndAutofillAddressesOption() { - saveAndAutofillAddressesOption.click() + saveAndAutofillAddressesOption().click() Log.i(TAG, "clickSaveAndAutofillAddressesOption: Clicked \"Save and autofill addresses\" button") } fun clickAddAddressButton() { - addAddressButton.click() + addAddressButton().click() Log.i(TAG, "clickAddAddressButton: Clicked \"Add address\" button") } fun clickManageAddressesButton() { - manageAddressesButton.click() + manageAddressesButton().click() Log.i(TAG, "clickManageAddressesButton: Clicked \"Manage addresses\" button") } fun clickSavedAddress(firstName: String) { @@ -261,17 +261,17 @@ class SettingsSubMenuAutofillRobot { } fun clickDeleteAddressButton() { Log.i(TAG, "clickDeleteAddressButton: Looking for delete address toolbar button") - toolbarDeleteAddressButton.waitForExists(waitingTime) - toolbarDeleteAddressButton.click() + toolbarDeleteAddressButton().waitForExists(waitingTime) + toolbarDeleteAddressButton().click() Log.i(TAG, "clickDeleteAddressButton: Clicked delete address toolbar button") } fun clickCancelDeleteAddressButton() { - cancelDeleteAddressButton.click() + cancelDeleteAddressButton().click() Log.i(TAG, "clickCancelDeleteAddressButton: Clicked \"CANCEL\" button from delete address dialog") } fun clickConfirmDeleteAddressButton() { - confirmDeleteAddressButton.click() + confirmDeleteAddressButton().click() Log.i(TAG, "clickConfirmDeleteAddressButton: Clicked \"DELETE\" button from delete address dialog") } @@ -292,7 +292,7 @@ class SettingsSubMenuAutofillRobot { Log.i(TAG, "clickCountryOption: Clicked \"Country or region\" $country dropdown option") } fun verifyAddAddressButton() { - assertUIObjectExists(addAddressButton) + assertUIObjectExists(addAddressButton()) Log.i(TAG, "verifyAddAddressButton: Verified \"Add address\" button exists") } @@ -321,58 +321,58 @@ class SettingsSubMenuAutofillRobot { } } Log.i(TAG, "fillAndSaveAddress: Looking for \"First Name\" text input") - firstNameTextInput.waitForExists(waitingTime) + firstNameTextInput().waitForExists(waitingTime) mDevice.pressBack() Log.i(TAG, "fillAndSaveAddress: Dismissed keyboard using device back button") - firstNameTextInput.setText(firstName) + firstNameTextInput().setText(firstName) Log.i(TAG, "fillAndSaveAddress: \"First Name\" set to $firstName") - middleNameTextInput.setText(middleName) + middleNameTextInput().setText(middleName) Log.i(TAG, "fillAndSaveAddress: \"Middle Name\" set to $middleName") - lastNameTextInput.setText(lastName) + lastNameTextInput().setText(lastName) Log.i(TAG, "fillAndSaveAddress: \"Last Name\" set to $lastName") - streetAddressTextInput.setText(streetAddress) + streetAddressTextInput().setText(streetAddress) Log.i(TAG, "fillAndSaveAddress: \"Street Address\" set to $streetAddress") - cityTextInput.setText(city) + cityTextInput().setText(city) Log.i(TAG, "fillAndSaveAddress: \"City\" set to $city") - subRegionDropDown.click() + subRegionDropDown().click() Log.i(TAG, "fillAndSaveAddress: Clicked \"State\" dropdown button") clickSubRegionOption(state) Log.i(TAG, "fillAndSaveAddress: Selected $state as \"State\"") - zipCodeTextInput.setText(zipCode) + zipCodeTextInput().setText(zipCode) Log.i(TAG, "fillAndSaveAddress: \"Zip\" set to $zipCode") - countryDropDown.click() + countryDropDown().click() Log.i(TAG, "fillAndSaveAddress: Clicked \"Country or region\" dropdown button") clickCountryOption(country) Log.i(TAG, "fillAndSaveAddress: Selected $country as \"Country or region\"") scrollToElementByText(getStringResource(R.string.addresses_save_button)) Log.i(TAG, "fillAndSaveAddress: Scrolled to \"Save\" button") - phoneTextInput.setText(phoneNumber) + phoneTextInput().setText(phoneNumber) Log.i(TAG, "fillAndSaveAddress: \"Phone\" set to $phoneNumber") - emailTextInput.setText(emailAddress) + emailTextInput().setText(emailAddress) Log.i(TAG, "fillAndSaveAddress: \"Email\" set to $emailAddress") - saveButton.click() + saveButton().click() Log.i(TAG, "fillAndSaveAddress: Clicked \"Save\" button") Log.i(TAG, "fillAndSaveAddress: Looking for \"Manage addressese\" button") - manageAddressesButton.waitForExists(waitingTime) + manageAddressesButton().waitForExists(waitingTime) } - fun clickAddCreditCardButton() = addCreditCardButton.click() - fun clickManageSavedCreditCardsButton() = manageSavedCreditCardsButton.click() - fun clickSecuredCreditCardsLaterButton() = securedCreditCardsLaterButton.click() - fun clickSavedCreditCard() = savedCreditCardNumber.clickAndWaitForNewWindow(waitingTime) + fun clickAddCreditCardButton() = addCreditCardButton().click() + fun clickManageSavedCreditCardsButton() = manageSavedCreditCardsButton().click() + fun clickSecuredCreditCardsLaterButton() = securedCreditCardsLaterButton().click() + fun clickSavedCreditCard() = savedCreditCardNumber().clickAndWaitForNewWindow(waitingTime) fun clickDeleteCreditCardToolbarButton() { - deleteCreditCardToolbarButton.waitForExists(waitingTime) - deleteCreditCardToolbarButton.click() + deleteCreditCardToolbarButton().waitForExists(waitingTime) + deleteCreditCardToolbarButton().click() } fun clickDeleteCreditCardMenuButton() { - deleteCreditCardMenuButton.waitForExists(waitingTime) - deleteCreditCardMenuButton.click() + deleteCreditCardMenuButton().waitForExists(waitingTime) + deleteCreditCardMenuButton().click() } - fun clickSaveAndAutofillCreditCardsOption() = saveAndAutofillCreditCardsOption.click() + fun clickSaveAndAutofillCreditCardsOption() = saveAndAutofillCreditCardsOption().click() - fun clickConfirmDeleteCreditCardButton() = confirmDeleteCreditCardButton.click() + fun clickConfirmDeleteCreditCardButton() = confirmDeleteCreditCardButton().click() - fun clickCancelDeleteCreditCardButton() = cancelDeleteCreditCardButton.click() + fun clickCancelDeleteCreditCardButton() = cancelDeleteCreditCardButton().click() fun clickExpiryMonthOption(expiryMonth: String) { expiryMonthOption(expiryMonth).waitForExists(waitingTime) @@ -384,34 +384,34 @@ class SettingsSubMenuAutofillRobot { expiryYearOption(expiryYear).click() } - fun verifyAddCreditCardsButton() = assertUIObjectExists(addCreditCardButton) + fun verifyAddCreditCardsButton() = assertUIObjectExists(addCreditCardButton()) fun fillAndSaveCreditCard(cardNumber: String, cardName: String, expiryMonth: String, expiryYear: String) { - creditCardNumberTextInput.waitForExists(waitingTime) - creditCardNumberTextInput.setText(cardNumber) - nameOnCreditCardTextInput.setText(cardName) - expiryMonthDropDown.click() + creditCardNumberTextInput().waitForExists(waitingTime) + creditCardNumberTextInput().setText(cardNumber) + nameOnCreditCardTextInput().setText(cardName) + expiryMonthDropDown().click() clickExpiryMonthOption(expiryMonth) - expiryYearDropDown.click() + expiryYearDropDown().click() clickExpiryYearOption(expiryYear) - saveButton.click() - manageSavedCreditCardsButton.waitForExists(waitingTime) + saveButton().click() + manageSavedCreditCardsButton().waitForExists(waitingTime) } fun clearCreditCardNumber() = - creditCardNumberTextInput.also { + creditCardNumberTextInput().also { it.waitForExists(waitingTime) it.clearTextField() } fun clearNameOnCreditCard() = - nameOnCreditCardTextInput.also { + nameOnCreditCardTextInput().also { it.waitForExists(waitingTime) it.clearTextField() } - fun clickSaveCreditCardToolbarButton() = saveCreditCardToolbarButton.click() + fun clickSaveCreditCardToolbarButton() = saveCreditCardToolbarButton().click() fun verifyEditCreditCardView( cardNumber: String, @@ -420,19 +420,19 @@ class SettingsSubMenuAutofillRobot { expiryYear: String, ) { assertUIObjectExists( - editCreditCardToolbarTitle, - navigateBackButton, - deleteCreditCardToolbarButton, - saveCreditCardToolbarButton, + editCreditCardToolbarTitle(), + navigateBackButton(), + deleteCreditCardToolbarButton(), + saveCreditCardToolbarButton(), ) - assertEquals(cardNumber, creditCardNumberTextInput.text) - assertEquals(cardName, nameOnCreditCardTextInput.text) + assertEquals(cardNumber, creditCardNumberTextInput().text) + assertEquals(cardName, nameOnCreditCardTextInput().text) // Can't get the text from the drop-down items, need to verify them individually assertUIObjectExists( - expiryYearDropDown, - expiryMonthDropDown, + expiryYearDropDown(), + expiryMonthDropDown(), ) assertUIObjectExists( @@ -441,14 +441,14 @@ class SettingsSubMenuAutofillRobot { ) assertUIObjectExists( - saveButton, - cancelButton, + saveButton(), + cancelButton(), ) - assertUIObjectExists(deleteCreditCardMenuButton) + assertUIObjectExists(deleteCreditCardMenuButton()) } - fun verifyEditCreditCardToolbarTitle() = assertUIObjectExists(editCreditCardToolbarTitle) + fun verifyEditCreditCardToolbarTitle() = assertUIObjectExists(editCreditCardToolbarTitle()) fun verifyCreditCardNumberErrorMessage() = assertUIObjectExists(itemContainingText(getStringResource(R.string.credit_cards_number_validation_error_message))) @@ -465,7 +465,7 @@ class SettingsSubMenuAutofillRobot { } fun goBackToAutofillSettings(interact: SettingsSubMenuAutofillRobot.() -> Unit): SettingsSubMenuAutofillRobot.Transition { - navigateBackButton.click() + navigateBackButton().click() Log.i(TAG, "goBackToAutofillSettings: Clicked \"Navigate back\" toolbar button") SettingsSubMenuAutofillRobot().interact() @@ -473,7 +473,7 @@ class SettingsSubMenuAutofillRobot { } fun goBackToSavedCreditCards(interact: SettingsSubMenuAutofillRobot.() -> Unit): SettingsSubMenuAutofillRobot.Transition { - navigateBackButton.click() + navigateBackButton().click() SettingsSubMenuAutofillRobot().interact() return SettingsSubMenuAutofillRobot.Transition() @@ -494,64 +494,64 @@ fun autofillScreen(interact: SettingsSubMenuAutofillRobot.() -> Unit): SettingsS return SettingsSubMenuAutofillRobot.Transition() } -private val autofillToolbarTitle = itemContainingText(getStringResource(R.string.preferences_autofill)) -private val addressesSectionTitle = itemContainingText(getStringResource(R.string.preferences_addresses)) -private val manageAddressesToolbarTitle = +private fun autofillToolbarTitle() = itemContainingText(getStringResource(R.string.preferences_autofill)) +private fun addressesSectionTitle() = itemContainingText(getStringResource(R.string.preferences_addresses)) +private fun manageAddressesToolbarTitle() = mDevice.findObject( UiSelector() .resourceId("$packageName:id/navigationToolbar") .childSelector(UiSelector().text(getStringResource(R.string.addresses_manage_addresses))), ) -private val saveAndAutofillAddressesOption = itemContainingText(getStringResource(R.string.preferences_addresses_save_and_autofill_addresses)) -private val saveAndAutofillAddressesSummary = itemContainingText(getStringResource(R.string.preferences_addresses_save_and_autofill_addresses_summary)) -private val addAddressButton = itemContainingText(getStringResource(R.string.preferences_addresses_add_address)) -private val manageAddressesButton = +private fun saveAndAutofillAddressesOption() = itemContainingText(getStringResource(R.string.preferences_addresses_save_and_autofill_addresses)) +private fun saveAndAutofillAddressesSummary() = itemContainingText(getStringResource(R.string.preferences_addresses_save_and_autofill_addresses_summary)) +private fun addAddressButton() = itemContainingText(getStringResource(R.string.preferences_addresses_add_address)) +private fun manageAddressesButton() = mDevice.findObject( UiSelector() .resourceId("android:id/title") .text(getStringResource(R.string.preferences_addresses_manage_addresses)), ) -private val addAddressToolbarTitle = itemContainingText(getStringResource(R.string.addresses_add_address)) -private val editAddressToolbarTitle = itemContainingText(getStringResource(R.string.addresses_edit_address)) -private val toolbarCheckmarkButton = itemWithResId("$packageName:id/save_address_button") -private val navigateBackButton = itemWithDescription(getStringResource(R.string.action_bar_up_description)) -private val firstNameTextInput = itemWithResId("$packageName:id/first_name_input") -private val middleNameTextInput = itemWithResId("$packageName:id/middle_name_input") -private val lastNameTextInput = itemWithResId("$packageName:id/last_name_input") -private val streetAddressTextInput = itemWithResId("$packageName:id/street_address_input") -private val cityTextInput = itemWithResId("$packageName:id/city_input") -private val subRegionDropDown = itemWithResId("$packageName:id/subregion_drop_down") -private val zipCodeTextInput = itemWithResId("$packageName:id/zip_input") -private val countryDropDown = itemWithResId("$packageName:id/country_drop_down") -private val phoneTextInput = itemWithResId("$packageName:id/phone_input") -private val emailTextInput = itemWithResId("$packageName:id/email_input") -private val saveButton = itemWithResId("$packageName:id/save_button") -private val cancelButton = itemWithResId("$packageName:id/cancel_button") -private val deleteAddressButton = itemContainingText(getStringResource(R.string.addressess_delete_address_button)) -private val toolbarDeleteAddressButton = itemWithResId("$packageName:id/delete_address_button") -private val cancelDeleteAddressButton = onView(withId(android.R.id.button2)).inRoot(RootMatchers.isDialog()) -private val confirmDeleteAddressButton = onView(withId(android.R.id.button1)).inRoot(RootMatchers.isDialog()) - -private val creditCardsSectionTitle = itemContainingText(getStringResource(R.string.preferences_credit_cards)) -private val saveAndAutofillCreditCardsOption = itemContainingText(getStringResource(R.string.preferences_credit_cards_save_and_autofill_cards)) -private val saveAndAutofillCreditCardsSummary = itemContainingText(getStringResource(R.string.preferences_credit_cards_save_and_autofill_cards_summary)) -private val syncCreditCardsAcrossDevicesButton = itemContainingText(getStringResource(R.string.preferences_credit_cards_sync_cards_across_devices)) -private val addCreditCardButton = mDevice.findObject(UiSelector().textContains(getStringResource(R.string.preferences_credit_cards_add_credit_card))) -private val savedCreditCardsToolbarTitle = itemContainingText(getStringResource(R.string.credit_cards_saved_cards)) -private val editCreditCardToolbarTitle = itemContainingText(getStringResource(R.string.credit_cards_edit_card)) -private val manageSavedCreditCardsButton = mDevice.findObject(UiSelector().textContains(getStringResource(R.string.preferences_credit_cards_manage_saved_cards))) -private val creditCardNumberTextInput = mDevice.findObject(UiSelector().resourceId("$packageName:id/card_number_input")) -private val nameOnCreditCardTextInput = mDevice.findObject(UiSelector().resourceId("$packageName:id/name_on_card_input")) -private val expiryMonthDropDown = mDevice.findObject(UiSelector().resourceId("$packageName:id/expiry_month_drop_down")) -private val expiryYearDropDown = mDevice.findObject(UiSelector().resourceId("$packageName:id/expiry_year_drop_down")) -private val savedCreditCardNumber = mDevice.findObject(UiSelector().resourceId("$packageName:id/credit_card_logo")) -private val deleteCreditCardToolbarButton = mDevice.findObject(UiSelector().resourceId("$packageName:id/delete_credit_card_button")) -private val saveCreditCardToolbarButton = itemWithResId("$packageName:id/save_credit_card_button") -private val deleteCreditCardMenuButton = itemContainingText(getStringResource(R.string.credit_cards_delete_card_button)) -private val confirmDeleteCreditCardButton = onView(withId(android.R.id.button1)).inRoot(RootMatchers.isDialog()) -private val cancelDeleteCreditCardButton = onView(withId(android.R.id.button2)).inRoot(RootMatchers.isDialog()) -private val securedCreditCardsLaterButton = onView(withId(android.R.id.button2)).inRoot(RootMatchers.isDialog()) +private fun addAddressToolbarTitle() = itemContainingText(getStringResource(R.string.addresses_add_address)) +private fun editAddressToolbarTitle() = itemContainingText(getStringResource(R.string.addresses_edit_address)) +private fun toolbarCheckmarkButton() = itemWithResId("$packageName:id/save_address_button") +private fun navigateBackButton() = itemWithDescription(getStringResource(R.string.action_bar_up_description)) +private fun firstNameTextInput() = itemWithResId("$packageName:id/first_name_input") +private fun middleNameTextInput() = itemWithResId("$packageName:id/middle_name_input") +private fun lastNameTextInput() = itemWithResId("$packageName:id/last_name_input") +private fun streetAddressTextInput() = itemWithResId("$packageName:id/street_address_input") +private fun cityTextInput() = itemWithResId("$packageName:id/city_input") +private fun subRegionDropDown() = itemWithResId("$packageName:id/subregion_drop_down") +private fun zipCodeTextInput() = itemWithResId("$packageName:id/zip_input") +private fun countryDropDown() = itemWithResId("$packageName:id/country_drop_down") +private fun phoneTextInput() = itemWithResId("$packageName:id/phone_input") +private fun emailTextInput() = itemWithResId("$packageName:id/email_input") +private fun saveButton() = itemWithResId("$packageName:id/save_button") +private fun cancelButton() = itemWithResId("$packageName:id/cancel_button") +private fun deleteAddressButton() = itemContainingText(getStringResource(R.string.addressess_delete_address_button)) +private fun toolbarDeleteAddressButton() = itemWithResId("$packageName:id/delete_address_button") +private fun cancelDeleteAddressButton() = onView(withId(android.R.id.button2)).inRoot(RootMatchers.isDialog()) +private fun confirmDeleteAddressButton() = onView(withId(android.R.id.button1)).inRoot(RootMatchers.isDialog()) + +private fun creditCardsSectionTitle() = itemContainingText(getStringResource(R.string.preferences_credit_cards)) +private fun saveAndAutofillCreditCardsOption() = itemContainingText(getStringResource(R.string.preferences_credit_cards_save_and_autofill_cards)) +private fun saveAndAutofillCreditCardsSummary() = itemContainingText(getStringResource(R.string.preferences_credit_cards_save_and_autofill_cards_summary)) +private fun syncCreditCardsAcrossDevicesButton() = itemContainingText(getStringResource(R.string.preferences_credit_cards_sync_cards_across_devices)) +private fun addCreditCardButton() = mDevice.findObject(UiSelector().textContains(getStringResource(R.string.preferences_credit_cards_add_credit_card))) +private fun savedCreditCardsToolbarTitle() = itemContainingText(getStringResource(R.string.credit_cards_saved_cards)) +private fun editCreditCardToolbarTitle() = itemContainingText(getStringResource(R.string.credit_cards_edit_card)) +private fun manageSavedCreditCardsButton() = mDevice.findObject(UiSelector().textContains(getStringResource(R.string.preferences_credit_cards_manage_saved_cards))) +private fun creditCardNumberTextInput() = mDevice.findObject(UiSelector().resourceId("$packageName:id/card_number_input")) +private fun nameOnCreditCardTextInput() = mDevice.findObject(UiSelector().resourceId("$packageName:id/name_on_card_input")) +private fun expiryMonthDropDown() = mDevice.findObject(UiSelector().resourceId("$packageName:id/expiry_month_drop_down")) +private fun expiryYearDropDown() = mDevice.findObject(UiSelector().resourceId("$packageName:id/expiry_year_drop_down")) +private fun savedCreditCardNumber() = mDevice.findObject(UiSelector().resourceId("$packageName:id/credit_card_logo")) +private fun deleteCreditCardToolbarButton() = mDevice.findObject(UiSelector().resourceId("$packageName:id/delete_credit_card_button")) +private fun saveCreditCardToolbarButton() = itemWithResId("$packageName:id/save_credit_card_button") +private fun deleteCreditCardMenuButton() = itemContainingText(getStringResource(R.string.credit_cards_delete_card_button)) +private fun confirmDeleteCreditCardButton() = onView(withId(android.R.id.button1)).inRoot(RootMatchers.isDialog()) +private fun cancelDeleteCreditCardButton() = onView(withId(android.R.id.button2)).inRoot(RootMatchers.isDialog()) +private fun securedCreditCardsLaterButton() = onView(withId(android.R.id.button2)).inRoot(RootMatchers.isDialog()) private fun savedAddress(firstName: String) = mDevice.findObject(UiSelector().textContains(firstName)) private fun subRegionOption(subRegion: String) = mDevice.findObject(UiSelector().textContains(subRegion)) From e711687c54df074bc46d9b27b405f4e7f2476f14 Mon Sep 17 00:00:00 2001 From: AndiAJ Date: Fri, 16 Feb 2024 14:27:29 +0200 Subject: [PATCH 003/238] Bug 1880640 - Add pairs of logs to SettingsSubMenuAutofillRobot --- .../ui/robots/SettingsSubMenuAutofillRobot.kt | 222 +++++++++++++----- 1 file changed, 163 insertions(+), 59 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuAutofillRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuAutofillRobot.kt index 6f7a581cd..c9af2fdc6 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuAutofillRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuAutofillRobot.kt @@ -37,10 +37,9 @@ class SettingsSubMenuAutofillRobot { fun verifyAutofillToolbarTitle() { assertUIObjectExists(autofillToolbarTitle()) - Log.i(TAG, "verifyAutofillToolbarTitle: Verified \"Autofill\" toolbar title exists") } fun verifyManageAddressesToolbarTitle() { - Log.i(TAG, "verifyManageAddressesToolbarTitle: Trying to verify the \"Manage addresses\" toolbar title is displayed") + Log.i(TAG, "verifyManageAddressesToolbarTitle: Trying to verify that the \"Manage addresses\" toolbar title is displayed") onView( allOf( withId(R.id.navigationToolbar), @@ -49,7 +48,7 @@ class SettingsSubMenuAutofillRobot { ), ), ).check(matches(isDisplayed())) - Log.i(TAG, "verifyManageAddressesToolbarTitle: Verified the \"Manage addresses\" toolbar title is displayed") + Log.i(TAG, "verifyManageAddressesToolbarTitle: Verified that the \"Manage addresses\" toolbar title is displayed") } fun verifyAddressAutofillSection(isAddressAutofillEnabled: Boolean, userHasSavedAddress: Boolean) { @@ -96,7 +95,6 @@ class SettingsSubMenuAutofillRobot { ) for (savedAddressDetail in savedAddressDetails) { assertUIObjectExists(itemContainingText(savedAddressDetail)) - Log.i(TAG, "verifyManageAddressesSection: Verified saved address detail: $savedAddressDetail exists") } } @@ -111,6 +109,7 @@ class SettingsSubMenuAutofillRobot { } fun verifyAddressesAutofillToggle(enabled: Boolean) { + Log.i(TAG, "verifyAddressesAutofillToggle: Trying to verify that the \"Save and autofill addresses\" toggle is checked: $enabled") onView(withText(R.string.preferences_addresses_save_and_autofill_addresses)) .check( matches( @@ -126,10 +125,11 @@ class SettingsSubMenuAutofillRobot { ), ), ) - Log.i(TAG, "verifyAddressesAutofillToggle: Verified if address autofill toggle is enabled: $enabled") + Log.i(TAG, "verifyAddressesAutofillToggle: Verified that the \"Save and autofill addresses\" toggle is checked: $enabled") } - fun verifySaveAndAutofillCreditCardsToggle(enabled: Boolean) = + fun verifySaveAndAutofillCreditCardsToggle(enabled: Boolean) { + Log.i(TAG, "verifySaveAndAutofillCreditCardsToggle: Trying to verify that the \"Save and autofill cards\" toggle is checked: $enabled") onView(withText(R.string.preferences_credit_cards_save_and_autofill_cards)) .check( matches( @@ -145,6 +145,8 @@ class SettingsSubMenuAutofillRobot { ), ), ) + Log.i(TAG, "verifySaveAndAutofillCreditCardsToggle: Verified that the \"Save and autofill cards\" toggle is checked: $enabled") + } fun verifyAddAddressView() { assertUIObjectExists( @@ -155,20 +157,17 @@ class SettingsSubMenuAutofillRobot { middleNameTextInput(), ) scrollToElementByText(getStringResource(R.string.addresses_street_address)) - Log.i(TAG, "verifyAddAddressView: Scrolled to \"Street Address\" text input") assertUIObjectExists( lastNameTextInput(), streetAddressTextInput(), ) scrollToElementByText(getStringResource(R.string.addresses_country)) - Log.i(TAG, "verifyAddAddressView: Scrolled to \"Country or region\" dropdown") assertUIObjectExists( cityTextInput(), subRegionDropDown(), zipCodeTextInput(), ) scrollToElementByText(getStringResource(R.string.addresses_save_button)) - Log.i(TAG, "verifyAddAddressView: Scrolled to \"Save\" button") assertUIObjectExists( countryDropDown(), phoneTextInput(), @@ -182,9 +181,9 @@ class SettingsSubMenuAutofillRobot { fun verifyCountryOption(country: String) { scrollToElementByText(getStringResource(R.string.addresses_country)) - Log.i(TAG, "verifyCountryOption: Scrolled to \"Country or region\" dropdown") + Log.i(TAG, "verifyCountryOption: Trying to click device back button") mDevice.pressBack() - Log.i(TAG, "fillAndSaveAddress: Dismissed \"Country or region\" dropdown using device back button") + Log.i(TAG, "verifyCountryOption: Clicked device back button") assertUIObjectExists(itemContainingText(country)) } @@ -193,16 +192,19 @@ class SettingsSubMenuAutofillRobot { } fun verifyCountryOptions(vararg countries: String) { + Log.i(TAG, "verifyCountryOptions: Trying to click the \"Country or region\" dropdown") countryDropDown().click() - Log.i(TAG, "verifyCountryOptions: Clicked \"Country or region\" dropdown") + Log.i(TAG, "verifyCountryOptions: Clicked the \"Country or region\" dropdown") for (country in countries) { assertUIObjectExists(itemContainingText(country)) } } fun selectCountry(country: String) { + Log.i(TAG, "selectCountry: Trying to click the \"Country or region\" dropdown") countryDropDown().click() - Log.i(TAG, "selectCountry: Clicked \"Country or region\" dropdown") + Log.i(TAG, "selectCountry: Clicked the \"Country or region\" dropdown") + Log.i(TAG, "selectCountry: Trying to select $country dropdown option") countryOption(country).click() Log.i(TAG, "selectCountry: Selected $country dropdown option") } @@ -217,20 +219,17 @@ class SettingsSubMenuAutofillRobot { middleNameTextInput(), ) scrollToElementByText(getStringResource(R.string.addresses_street_address)) - Log.i(TAG, "verifyEditAddressView: Scrolled to \"Street Address\" text input") assertUIObjectExists( lastNameTextInput(), streetAddressTextInput(), ) scrollToElementByText(getStringResource(R.string.addresses_country)) - Log.i(TAG, "verifyEditAddressView: Scrolled to \"Country or region\" dropdown") assertUIObjectExists( cityTextInput(), subRegionDropDown(), zipCodeTextInput(), ) scrollToElementByText(getStringResource(R.string.addresses_save_button)) - Log.i(TAG, "verifyEditAddressView: Scrolled to \"Save\" button") assertUIObjectExists( countryDropDown(), phoneTextInput(), @@ -244,57 +243,65 @@ class SettingsSubMenuAutofillRobot { } fun clickSaveAndAutofillAddressesOption() { + Log.i(TAG, "clickSaveAndAutofillAddressesOption: Trying to click the \"Save and autofill addresses\" button") saveAndAutofillAddressesOption().click() - Log.i(TAG, "clickSaveAndAutofillAddressesOption: Clicked \"Save and autofill addresses\" button") + Log.i(TAG, "clickSaveAndAutofillAddressesOption: Clicked the \"Save and autofill addresses\" button") } fun clickAddAddressButton() { + Log.i(TAG, "clickAddAddressButton: Trying to click the \"Add address\" button") addAddressButton().click() - Log.i(TAG, "clickAddAddressButton: Clicked \"Add address\" button") + Log.i(TAG, "clickAddAddressButton: Clicked the \"Add address\" button") } fun clickManageAddressesButton() { + Log.i(TAG, "clickManageAddressesButton: Trying to click the \"Manage addresses\" button") manageAddressesButton().click() - Log.i(TAG, "clickManageAddressesButton: Clicked \"Manage addresses\" button") + Log.i(TAG, "clickManageAddressesButton: Clicked the \"Manage addresses\" button") } fun clickSavedAddress(firstName: String) { + Log.i(TAG, "clickSavedAddress: Trying to click the $firstName saved address and and wait for $waitingTime ms for a new window") savedAddress(firstName).clickAndWaitForNewWindow(waitingTime) - Log.i(TAG, "clickSavedAddress: Clicked $firstName saved address and waiting for a new window for $waitingTime") + Log.i(TAG, "clickSavedAddress: Clicked the $firstName saved address and and waited for $waitingTime ms for a new window") } fun clickDeleteAddressButton() { - Log.i(TAG, "clickDeleteAddressButton: Looking for delete address toolbar button") + Log.i(TAG, "clickDeleteAddressButton: Waiting for $waitingTime ms for the delete address toolbar button to exist") toolbarDeleteAddressButton().waitForExists(waitingTime) + Log.i(TAG, "clickDeleteAddressButton: Waited for $waitingTime ms for the delete address toolbar button to exist") + Log.i(TAG, "clickDeleteAddressButton: Trying to click the delete address toolbar button") toolbarDeleteAddressButton().click() - Log.i(TAG, "clickDeleteAddressButton: Clicked delete address toolbar button") + Log.i(TAG, "clickDeleteAddressButton: Clicked the delete address toolbar button") } fun clickCancelDeleteAddressButton() { + Log.i(TAG, "clickCancelDeleteAddressButton: Trying to click the \"CANCEL\" button from the delete address dialog") cancelDeleteAddressButton().click() - Log.i(TAG, "clickCancelDeleteAddressButton: Clicked \"CANCEL\" button from delete address dialog") + Log.i(TAG, "clickCancelDeleteAddressButton: Clicked the \"CANCEL\" button from the delete address dialog") } fun clickConfirmDeleteAddressButton() { + Log.i(TAG, "clickConfirmDeleteAddressButton: Trying to click the \"DELETE\" button from the delete address dialog") confirmDeleteAddressButton().click() - Log.i(TAG, "clickConfirmDeleteAddressButton: Clicked \"DELETE\" button from delete address dialog") + Log.i(TAG, "clickConfirmDeleteAddressButton: Clicked \"DELETE\" button from the delete address dialog") } fun clickSubRegionOption(subRegion: String) { scrollToElementByText(subRegion) - Log.i(TAG, "clickSubRegionOption: Scrolled to \"State\" drop down") subRegionOption(subRegion).also { - Log.i(TAG, "clickSubRegionOption: Looking for \"State\" $subRegion dropdown option") + Log.i(TAG, "clickSubRegionOption: Waiting for $waitingTime ms for the \"State\" $subRegion dropdown option to exist") it.waitForExists(waitingTime) + Log.i(TAG, "clickSubRegionOption: Waited for $waitingTime ms for the \"State\" $subRegion dropdown option to exist") + Log.i(TAG, "clickSubRegionOption: Trying to click the \"State\" $subRegion dropdown option") it.click() - Log.i(TAG, "clickSubRegionOption: Clicked \"State\" $subRegion dropdown option") + Log.i(TAG, "clickSubRegionOption: Clicked the \"State\" $subRegion dropdown option") } } fun clickCountryOption(country: String) { - Log.i(TAG, "clickCountryOption: Looking for \"Country or region\" $country dropdown option") + Log.i(TAG, "clickCountryOption: Waiting for $waitingTime ms for the \"Country or region\" $country dropdown option to exist") countryOption(country).waitForExists(waitingTime) + Log.i(TAG, "clickCountryOption: Waited for $waitingTime ms for the \"Country or region\" $country dropdown option to exist") + Log.i(TAG, "clickCountryOption: Trying to click \"Country or region\" $country dropdown option") countryOption(country).click() Log.i(TAG, "clickCountryOption: Clicked \"Country or region\" $country dropdown option") } - fun verifyAddAddressButton() { - assertUIObjectExists(addAddressButton()) - Log.i(TAG, "verifyAddAddressButton: Verified \"Add address\" button exists") - } + fun verifyAddAddressButton() = assertUIObjectExists(addAddressButton()) fun fillAndSaveAddress( navigateToAutofillSettings: Boolean, @@ -320,98 +327,186 @@ class SettingsSubMenuAutofillRobot { clickAddAddressButton() } } - Log.i(TAG, "fillAndSaveAddress: Looking for \"First Name\" text input") + Log.i(TAG, "fillAndSaveAddress: Waiting for $waitingTime ms for \"First Name\" text field to exist") firstNameTextInput().waitForExists(waitingTime) + Log.i(TAG, "fillAndSaveAddress: Waited for $waitingTime ms for \"First Name\" text field to exist") + Log.i(TAG, "fillAndSaveAddress: Trying to click device back button to dismiss keyboard using device back button") mDevice.pressBack() - Log.i(TAG, "fillAndSaveAddress: Dismissed keyboard using device back button") + Log.i(TAG, "fillAndSaveAddress: Clicked device back button to dismiss keyboard using device back button") + Log.i(TAG, "fillAndSaveAddress: Trying to set \"First Name\" to $firstName") firstNameTextInput().setText(firstName) - Log.i(TAG, "fillAndSaveAddress: \"First Name\" set to $firstName") + Log.i(TAG, "fillAndSaveAddress: \"First Name\" was set to $firstName") + Log.i(TAG, "fillAndSaveAddress: Trying to set \"Middle Name\" to $middleName") middleNameTextInput().setText(middleName) - Log.i(TAG, "fillAndSaveAddress: \"Middle Name\" set to $middleName") + Log.i(TAG, "fillAndSaveAddress: \"Middle Name\" was set to $middleName") + Log.i(TAG, "fillAndSaveAddress: Trying to set \"Last Name\" to $lastName") lastNameTextInput().setText(lastName) - Log.i(TAG, "fillAndSaveAddress: \"Last Name\" set to $lastName") + Log.i(TAG, "fillAndSaveAddress: \"Last Name\" was set to $lastName") + Log.i(TAG, "fillAndSaveAddress: Trying to set \"Street Address\" to $streetAddress") streetAddressTextInput().setText(streetAddress) - Log.i(TAG, "fillAndSaveAddress: \"Street Address\" set to $streetAddress") + Log.i(TAG, "fillAndSaveAddress: \"Street Address\" was set to $streetAddress") + Log.i(TAG, "fillAndSaveAddress: Trying to set \"City\" to $city") cityTextInput().setText(city) - Log.i(TAG, "fillAndSaveAddress: \"City\" set to $city") + Log.i(TAG, "fillAndSaveAddress: \"City\" was set to $city") + Log.i(TAG, "fillAndSaveAddress: Trying to click \"State\" dropdown button") subRegionDropDown().click() Log.i(TAG, "fillAndSaveAddress: Clicked \"State\" dropdown button") + Log.i(TAG, "fillAndSaveAddress: Trying to click the $state dropdown option") clickSubRegionOption(state) - Log.i(TAG, "fillAndSaveAddress: Selected $state as \"State\"") + Log.i(TAG, "fillAndSaveAddress: Clicked $state dropdown option") + Log.i(TAG, "fillAndSaveAddress: Trying to set \"Zip\" to $zipCode") zipCodeTextInput().setText(zipCode) - Log.i(TAG, "fillAndSaveAddress: \"Zip\" set to $zipCode") + Log.i(TAG, "fillAndSaveAddress: \"Zip\" was set to $zipCode") + Log.i(TAG, "fillAndSaveAddress: Trying to click \"Country or region\" dropdown button") countryDropDown().click() Log.i(TAG, "fillAndSaveAddress: Clicked \"Country or region\" dropdown button") + Log.i(TAG, "fillAndSaveAddress: Trying to click $country dropdown option") clickCountryOption(country) - Log.i(TAG, "fillAndSaveAddress: Selected $country as \"Country or region\"") + Log.i(TAG, "fillAndSaveAddress: Clicked $country dropdown option") scrollToElementByText(getStringResource(R.string.addresses_save_button)) - Log.i(TAG, "fillAndSaveAddress: Scrolled to \"Save\" button") + Log.i(TAG, "fillAndSaveAddress: Trying to set \"Phone\" to $phoneNumber") phoneTextInput().setText(phoneNumber) - Log.i(TAG, "fillAndSaveAddress: \"Phone\" set to $phoneNumber") + Log.i(TAG, "fillAndSaveAddress: \"Phone\" was set to $phoneNumber") + Log.i(TAG, "fillAndSaveAddress: Trying to set \"Email\" to $emailAddress") emailTextInput().setText(emailAddress) - Log.i(TAG, "fillAndSaveAddress: \"Email\" set to $emailAddress") + Log.i(TAG, "fillAndSaveAddress: \"Email\" was set to $emailAddress") + Log.i(TAG, "fillAndSaveAddress: Trying to click the \"Save\" button") saveButton().click() - Log.i(TAG, "fillAndSaveAddress: Clicked \"Save\" button") - Log.i(TAG, "fillAndSaveAddress: Looking for \"Manage addressese\" button") + Log.i(TAG, "fillAndSaveAddress: Clicked the \"Save\" button") + Log.i(TAG, "fillAndSaveAddress: Waiting for $waitingTime ms for for \"Manage addresses\" button to exist") manageAddressesButton().waitForExists(waitingTime) + Log.i(TAG, "fillAndSaveAddress: Waited for $waitingTime ms for for \"Manage addresses\" button to exist") } - fun clickAddCreditCardButton() = addCreditCardButton().click() - fun clickManageSavedCreditCardsButton() = manageSavedCreditCardsButton().click() - fun clickSecuredCreditCardsLaterButton() = securedCreditCardsLaterButton().click() - fun clickSavedCreditCard() = savedCreditCardNumber().clickAndWaitForNewWindow(waitingTime) + fun clickAddCreditCardButton() { + Log.i(TAG, "clickAddCreditCardButton: Trying to click the \"Add credit card\" button") + addCreditCardButton().click() + Log.i(TAG, "clickAddCreditCardButton: Clicked the \"Add credit card\" button") + } + fun clickManageSavedCreditCardsButton() { + Log.i(TAG, "clickManageSavedCreditCardsButton: Trying to click the \"Manage saved cards\" button") + manageSavedCreditCardsButton().click() + Log.i(TAG, "clickManageSavedCreditCardsButton: Clicked the \"Manage saved cards\" button") + } + fun clickSecuredCreditCardsLaterButton() { + Log.i(TAG, "clickSecuredCreditCardsLaterButton: Trying to click the \"Later\" button") + securedCreditCardsLaterButton().click() + Log.i(TAG, "clickSecuredCreditCardsLaterButton: Clicked the \"Later\" button") + } + fun clickSavedCreditCard() { + Log.i(TAG, "clickSavedCreditCard: Trying to click the saved credit card and and wait for $waitingTime ms for a new window") + savedCreditCardNumber().clickAndWaitForNewWindow(waitingTime) + Log.i(TAG, "clickSavedCreditCard: Clicked the saved credit card and and waited for $waitingTime ms for a new window") + } fun clickDeleteCreditCardToolbarButton() { + Log.i(TAG, "clickDeleteCreditCardToolbarButton: Waiting for $waitingTime ms for the delete credit card toolbar button to exist") deleteCreditCardToolbarButton().waitForExists(waitingTime) + Log.i(TAG, "clickDeleteCreditCardToolbarButton: Waited for $waitingTime ms for the delete credit card toolbar button to exist") + Log.i(TAG, "clickDeleteCreditCardToolbarButton: Trying to click the delete credit card toolbar button") deleteCreditCardToolbarButton().click() + Log.i(TAG, "clickDeleteCreditCardToolbarButton: Clicked the delete credit card toolbar button") } fun clickDeleteCreditCardMenuButton() { + Log.i(TAG, "clickDeleteCreditCardMenuButton: Waiting for $waitingTime ms for the delete credit card menu button to exist") deleteCreditCardMenuButton().waitForExists(waitingTime) + Log.i(TAG, "clickDeleteCreditCardMenuButton: Waited for $waitingTime ms for the delete credit card menu button to exist") + Log.i(TAG, "clickDeleteCreditCardMenuButton: Trying to click the delete credit card menu button") deleteCreditCardMenuButton().click() + Log.i(TAG, "clickDeleteCreditCardMenuButton: Clicked the delete credit card menu button") + } + fun clickSaveAndAutofillCreditCardsOption() { + Log.i(TAG, "clickSaveAndAutofillCreditCardsOption: Trying to click the \"Save and autofill cards\" option") + saveAndAutofillCreditCardsOption().click() + Log.i(TAG, "clickSaveAndAutofillCreditCardsOption: Clicked the \"Save and autofill cards\" option") } - fun clickSaveAndAutofillCreditCardsOption() = saveAndAutofillCreditCardsOption().click() - fun clickConfirmDeleteCreditCardButton() = confirmDeleteCreditCardButton().click() + fun clickConfirmDeleteCreditCardButton() { + Log.i(TAG, "clickConfirmDeleteCreditCardButton: Trying to click the \"Delete\" credit card dialog button") + confirmDeleteCreditCardButton().click() + Log.i(TAG, "clickConfirmDeleteCreditCardButton: Clicked the \"Delete\" credit card dialog button") + } - fun clickCancelDeleteCreditCardButton() = cancelDeleteCreditCardButton().click() + fun clickCancelDeleteCreditCardButton() { + Log.i(TAG, "clickCancelDeleteCreditCardButton: Trying to click the \"Cancel\" credit card dialog button") + cancelDeleteCreditCardButton().click() + Log.i(TAG, "clickCancelDeleteCreditCardButton: Clicked the \"Cancel\" credit card dialog button") + } fun clickExpiryMonthOption(expiryMonth: String) { + Log.i(TAG, "clickExpiryMonthOption: Waiting for $waitingTime ms for the $expiryMonth expiry month option to exist") expiryMonthOption(expiryMonth).waitForExists(waitingTime) + Log.i(TAG, "clickExpiryMonthOption: Waited for $waitingTime ms for the $expiryMonth expiry month option to exist") + Log.i(TAG, "clickExpiryMonthOption: Trying to click $expiryMonth expiry month option") expiryMonthOption(expiryMonth).click() + Log.i(TAG, "clickExpiryMonthOption: Clicked $expiryMonth expiry month option") } fun clickExpiryYearOption(expiryYear: String) { + Log.i(TAG, "clickExpiryYearOption: Waiting for $waitingTime ms for the $expiryYear expiry year option to exist") expiryYearOption(expiryYear).waitForExists(waitingTime) + Log.i(TAG, "clickExpiryYearOption: Waited for $waitingTime ms for the $expiryYear expiry year option to exist") + Log.i(TAG, "clickExpiryYearOption: Trying to click $expiryYear expiry year option") expiryYearOption(expiryYear).click() + Log.i(TAG, "clickExpiryYearOption: Clicked $expiryYear expiry year option") } fun verifyAddCreditCardsButton() = assertUIObjectExists(addCreditCardButton()) fun fillAndSaveCreditCard(cardNumber: String, cardName: String, expiryMonth: String, expiryYear: String) { + Log.i(TAG, "fillAndSaveCreditCard: Waiting for $waitingTime ms for the credit card number text field to exist") creditCardNumberTextInput().waitForExists(waitingTime) + Log.i(TAG, "fillAndSaveCreditCard: Waited for $waitingTime ms for the credit card number text field to exist") + Log.i(TAG, "fillAndSaveCreditCard: Trying to set the credit card number to: $cardNumber") creditCardNumberTextInput().setText(cardNumber) + Log.i(TAG, "fillAndSaveCreditCard: The credit card number was set to: $cardNumber") + Log.i(TAG, "fillAndSaveCreditCard: Trying to set the name on card to: $cardName") nameOnCreditCardTextInput().setText(cardName) + Log.i(TAG, "fillAndSaveCreditCard: The credit card name was set to: $cardName") + Log.i(TAG, "fillAndSaveCreditCard: Trying to click the expiry month dropdown") expiryMonthDropDown().click() + Log.i(TAG, "fillAndSaveCreditCard: Clicked the expiry month dropdown") + Log.i(TAG, "fillAndSaveCreditCard: Trying to click $expiryMonth expiry month option") clickExpiryMonthOption(expiryMonth) + Log.i(TAG, "fillAndSaveCreditCard: Clicked $expiryMonth expiry month option") + Log.i(TAG, "fillAndSaveCreditCard: Trying to click the expiry year dropdown") expiryYearDropDown().click() + Log.i(TAG, "fillAndSaveCreditCard: Clicked the expiry year dropdown") + Log.i(TAG, "fillAndSaveCreditCard: Trying to click $expiryYear expiry year option") clickExpiryYearOption(expiryYear) - + Log.i(TAG, "fillAndSaveCreditCard: Clicked $expiryYear expiry year option") + Log.i(TAG, "fillAndSaveCreditCard: Trying to click the \"Save\" button") saveButton().click() + Log.i(TAG, "fillAndSaveCreditCard: Clicked the \"Save\" button") + Log.i(TAG, "fillAndSaveCreditCard: Waiting for $waitingTime ms for the \"Manage saved cards\" button to exist") manageSavedCreditCardsButton().waitForExists(waitingTime) + Log.i(TAG, "fillAndSaveCreditCard: Waited for $waitingTime ms for the \"Manage saved cards\" button to exist") } fun clearCreditCardNumber() = creditCardNumberTextInput().also { + Log.i(TAG, "clearCreditCardNumber: Waiting for $waitingTime ms for the credit card number text field to exist") it.waitForExists(waitingTime) + Log.i(TAG, "clearCreditCardNumber: Waited for $waitingTime ms for the credit card number text field to exist") + Log.i(TAG, "clearCreditCardNumber: Trying to clear the credit card number text field") it.clearTextField() + Log.i(TAG, "clearCreditCardNumber: Cleared the credit card number text field") } fun clearNameOnCreditCard() = nameOnCreditCardTextInput().also { + Log.i(TAG, "clearNameOnCreditCard: Waiting for $waitingTime ms for name on card text field to exist") it.waitForExists(waitingTime) + Log.i(TAG, "clearNameOnCreditCard: Waited for $waitingTime ms for name on card text field to exist") + Log.i(TAG, "clearNameOnCreditCard: Trying to clear the name on card text field") it.clearTextField() + Log.i(TAG, "clearNameOnCreditCard: Cleared the name on card text field") } - fun clickSaveCreditCardToolbarButton() = saveCreditCardToolbarButton().click() + fun clickSaveCreditCardToolbarButton() { + Log.i(TAG, "clickSaveCreditCardToolbarButton: Trying to click the save credit card toolbar button") + saveCreditCardToolbarButton().click() + Log.i(TAG, "clickSaveCreditCardToolbarButton: Clicked the save credit card toolbar button") + } fun verifyEditCreditCardView( cardNumber: String, @@ -425,9 +520,12 @@ class SettingsSubMenuAutofillRobot { deleteCreditCardToolbarButton(), saveCreditCardToolbarButton(), ) - + Log.i(TAG, "verifyEditCreditCardView: Trying to verify that the card number text field is set to: $cardNumber") assertEquals(cardNumber, creditCardNumberTextInput().text) + Log.i(TAG, "verifyEditCreditCardView: Verified that the card number text field was set to: $cardNumber") + Log.i(TAG, "verifyEditCreditCardView: Trying to verify that the card name text field is set to: $cardName") assertEquals(cardName, nameOnCreditCardTextInput().text) + Log.i(TAG, "verifyEditCreditCardView: Verified that the card card name text field was set to: $cardName") // Can't get the text from the drop-down items, need to verify them individually assertUIObjectExists( @@ -458,30 +556,36 @@ class SettingsSubMenuAutofillRobot { class Transition { fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition { + Log.i(TAG, "goBack: Trying to click the device back button") mDevice.pressBack() + Log.i(TAG, "goBack: Clicked the device back button") SettingsRobot().interact() return SettingsRobot.Transition() } fun goBackToAutofillSettings(interact: SettingsSubMenuAutofillRobot.() -> Unit): SettingsSubMenuAutofillRobot.Transition { + Log.i(TAG, "goBackToAutofillSettings: Trying to click the navigate up toolbar button") navigateBackButton().click() - Log.i(TAG, "goBackToAutofillSettings: Clicked \"Navigate back\" toolbar button") + Log.i(TAG, "goBackToAutofillSettings: Clicked the navigate up toolbar button") SettingsSubMenuAutofillRobot().interact() return SettingsSubMenuAutofillRobot.Transition() } fun goBackToSavedCreditCards(interact: SettingsSubMenuAutofillRobot.() -> Unit): SettingsSubMenuAutofillRobot.Transition { + Log.i(TAG, "goBackToSavedCreditCards: Trying to click the navigate up toolbar button") navigateBackButton().click() + Log.i(TAG, "goBackToSavedCreditCards: Clicked the navigate up toolbar button") SettingsSubMenuAutofillRobot().interact() return SettingsSubMenuAutofillRobot.Transition() } fun goBackToBrowser(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { + Log.i(TAG, "goBackToBrowser: Trying to click the device back button") mDevice.pressBack() - Log.i(TAG, "goBackToBrowser: Go back to browser view using device back button") + Log.i(TAG, "goBackToBrowser: Clicked the device back button") BrowserRobot().interact() return BrowserRobot.Transition() From 7e49ba72c69086a11c40c3a51b431a1992a7592d Mon Sep 17 00:00:00 2001 From: AndiAJ Date: Mon, 19 Feb 2024 11:17:15 +0200 Subject: [PATCH 004/238] Bug 1880808 - Convert private variables to functions so they don't get initialised --- .../mozilla/fenix/ui/robots/SearchRobot.kt | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SearchRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SearchRobot.kt index c28cc8fc1..0cf474f70 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SearchRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SearchRobot.kt @@ -67,14 +67,14 @@ class SearchRobot { ) fun verifyScanButtonVisibility(visible: Boolean = true) = - assertUIObjectExists(scanButton, exists = visible) + assertUIObjectExists(scanButton(), exists = visible) fun verifyVoiceSearchButtonVisibility(enabled: Boolean) = - assertUIObjectExists(voiceSearchButton, exists = enabled) + assertUIObjectExists(voiceSearchButton(), exists = enabled) // Device or AVD requires a Google Services Android OS installation fun startVoiceSearch() { - voiceSearchButton.click() + voiceSearchButton().click() grantSystemPermission() if (isPackageInstalled(Constants.PackageName.GOOGLE_QUICK_SEARCH)) { @@ -170,11 +170,11 @@ class SearchRobot { ).click() } - fun verifySearchSelectorButton() = assertUIObjectExists(searchSelectorButton) + fun verifySearchSelectorButton() = assertUIObjectExists(searchSelectorButton()) fun clickSearchSelectorButton() { - searchSelectorButton.waitForExists(waitingTime) - searchSelectorButton.click() + searchSelectorButton().waitForExists(waitingTime) + searchSelectorButton().click() } fun verifySearchEngineIcon(name: String) = assertUIObjectExists(itemWithDescription(name)) @@ -188,33 +188,33 @@ class SearchRobot { searchEngineName.forEach { if (shouldExist) { assertUIObjectExists( - searchShortcutList.getChild(UiSelector().text(it)), + searchShortcutList().getChild(UiSelector().text(it)), ) } else { - assertUIObjectIsGone(searchShortcutList.getChild(UiSelector().text(it))) + assertUIObjectIsGone(searchShortcutList().getChild(UiSelector().text(it))) } } } // New unified search UI search selector. fun selectTemporarySearchMethod(searchEngineName: String) { - searchShortcutList.getChild(UiSelector().text(searchEngineName)).click() + searchShortcutList().getChild(UiSelector().text(searchEngineName)).click() } fun clickScanButton() = - scanButton.also { + scanButton().also { it.waitForExists(waitingTime) it.click() } fun clickDismissPermissionRequiredDialog() { - dismissPermissionButton.waitForExists(waitingTime) - dismissPermissionButton.click() + dismissPermissionButton().waitForExists(waitingTime) + dismissPermissionButton().click() } fun clickGoToPermissionsSettings() { - goToPermissionsSettingsButton.waitForExists(waitingTime) - goToPermissionsSettingsButton.click() + goToPermissionsSettingsButton().waitForExists(waitingTime) + goToPermissionsSettingsButton().click() } fun verifyScannerOpen() { @@ -346,7 +346,7 @@ class SearchRobot { } fun clickSearchEngineSettings(interact: SettingsSubMenuSearchRobot.() -> Unit): SettingsSubMenuSearchRobot.Transition { - searchShortcutList.getChild(UiSelector().text("Search settings")).click() + searchShortcutList().getChild(UiSelector().text("Search settings")).click() SettingsSubMenuSearchRobot().interact() return SettingsSubMenuSearchRobot.Transition() @@ -372,22 +372,22 @@ fun searchScreen(interact: SearchRobot.() -> Unit): SearchRobot.Transition { private fun browserToolbarEditView() = mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_edit_url_view")) -private val dismissPermissionButton = +private fun dismissPermissionButton() = mDevice.findObject(UiSelector().text("DISMISS")) -private val goToPermissionsSettingsButton = +private fun goToPermissionsSettingsButton() = mDevice.findObject(UiSelector().text("GO TO SETTINGS")) -private val scanButton = itemWithDescription("Scan") +private fun scanButton() = itemWithDescription("Scan") private fun clearButton() = mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_clear_view")) private fun searchWrapper() = mDevice.findObject(UiSelector().resourceId("$packageName:id/search_wrapper")) -private val searchSelectorButton = itemWithResId("$packageName:id/search_selector") +private fun searchSelectorButton() = itemWithResId("$packageName:id/search_selector") -private val searchShortcutList = +private fun searchShortcutList() = mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_menu_recyclerView")) -private val voiceSearchButton = mDevice.findObject(UiSelector().description("Voice search")) +private fun voiceSearchButton() = mDevice.findObject(UiSelector().description("Voice search")) From 4c3e3e5c2a3b8e47532e0bd81b13a3410c09bfc7 Mon Sep 17 00:00:00 2001 From: AndiAJ Date: Mon, 19 Feb 2024 12:30:40 +0200 Subject: [PATCH 005/238] Bug 1880808 - Add logs to SearchRobot --- .../mozilla/fenix/ui/robots/SearchRobot.kt | 148 ++++++++++++++---- 1 file changed, 121 insertions(+), 27 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SearchRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SearchRobot.kt index 0cf474f70..04f0c420d 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SearchRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SearchRobot.kt @@ -18,7 +18,6 @@ import androidx.compose.ui.test.onAllNodesWithTag import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performScrollToNode import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.action.ViewActions.closeSoftKeyboard import androidx.test.espresso.assertion.PositionAssertions import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.intent.Intents @@ -34,10 +33,11 @@ import org.junit.Assert.assertTrue import org.mozilla.fenix.R import org.mozilla.fenix.helpers.AppAndSystemHelper.grantSystemPermission import org.mozilla.fenix.helpers.AppAndSystemHelper.isPackageInstalled -import org.mozilla.fenix.helpers.Constants import org.mozilla.fenix.helpers.Constants.LONG_CLICK_DURATION +import org.mozilla.fenix.helpers.Constants.PackageName.GOOGLE_QUICK_SEARCH import org.mozilla.fenix.helpers.Constants.RETRY_COUNT import org.mozilla.fenix.helpers.Constants.SPEECH_RECOGNITION +import org.mozilla.fenix.helpers.Constants.TAG import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource import org.mozilla.fenix.helpers.MatcherHelper.assertItemTextContains import org.mozilla.fenix.helpers.MatcherHelper.assertItemTextEquals @@ -74,11 +74,16 @@ class SearchRobot { // Device or AVD requires a Google Services Android OS installation fun startVoiceSearch() { + Log.i(TAG, "startVoiceSearch: Trying to click the voice search button button") voiceSearchButton().click() + Log.i(TAG, "startVoiceSearch: Clicked the voice search button button") grantSystemPermission() - if (isPackageInstalled(Constants.PackageName.GOOGLE_QUICK_SEARCH)) { + if (isPackageInstalled(GOOGLE_QUICK_SEARCH)) { + Log.i(TAG, "startVoiceSearch: $GOOGLE_QUICK_SEARCH is installed") + Log.i(TAG, "startVoiceSearch: Trying to verify the intent to: $GOOGLE_QUICK_SEARCH") Intents.intended(IntentMatchers.hasAction(SPEECH_RECOGNITION)) + Log.i(TAG, "startVoiceSearch: Verified the intent to: $GOOGLE_QUICK_SEARCH") } } @@ -91,15 +96,20 @@ class SearchRobot { ) { rule.waitForIdle() for (i in 1..RETRY_COUNT) { + Log.i(TAG, "verifySearchEngineSuggestionResults: Started try #$i") try { for (searchSuggestion in searchSuggestions) { mDevice.waitForObjects(mDevice.findObject(UiSelector().textContains(searchSuggestion))) - rule.onNodeWithTag("mozac.awesomebar.suggestions") - .performScrollToNode(hasText(searchSuggestion)) - .assertExists() + Log.i(TAG, "verifySearchEngineSuggestionResults: Trying to perform scroll action to $searchSuggestion search suggestion") + rule.onNodeWithTag("mozac.awesomebar.suggestions").performScrollToNode(hasText(searchSuggestion)) + Log.i(TAG, "verifySearchEngineSuggestionResults: Performed scroll action to $searchSuggestion search suggestion") + Log.i(TAG, "verifySearchEngineSuggestionResults: Trying to verify that $searchSuggestion search suggestion exists") + rule.onNodeWithTag("mozac.awesomebar.suggestions").assertExists() + Log.i(TAG, "verifySearchEngineSuggestionResults: Verified that $searchSuggestion search suggestion exists") } break } catch (e: AssertionError) { + Log.i(TAG, "verifySearchEngineSuggestionResults: AssertionError caught, executing fallback methods") if (i == RETRY_COUNT) { throw e } else { @@ -117,29 +127,41 @@ class SearchRobot { } fun verifySuggestionsAreNotDisplayed(rule: ComposeTestRule, vararg searchSuggestions: String) { + Log.i(TAG, "verifySuggestionsAreNotDisplayed: Waiting for compose test rule to be idle") rule.waitForIdle() + Log.i(TAG, "verifySuggestionsAreNotDisplayed: Waited for compose test rule to be idle") for (searchSuggestion in searchSuggestions) { + Log.i(TAG, "verifySuggestionsAreNotDisplayed: Trying to verify that there are no $searchSuggestion related search suggestions") rule.onAllNodesWithTag("mozac.awesomebar.suggestions") .assertAny( hasText(searchSuggestion) .not(), ) + Log.i(TAG, "verifySuggestionsAreNotDisplayed: Verified that there are no $searchSuggestion related search suggestions") } } @OptIn(ExperimentalTestApi::class) fun verifySearchSuggestionsCount(rule: ComposeTestRule, numberOfSuggestions: Int, searchTerm: String) { for (i in 1..RETRY_COUNT) { + Log.i(TAG, "verifySearchSuggestionsCount: Started try #$i") try { + Log.i(TAG, "verifySearchSuggestionsCount: Compose test rule is waiting for $waitingTime ms until the note count equals to: $numberOfSuggestions") rule.waitUntilNodeCount(hasTestTag("mozac.awesomebar.suggestion"), numberOfSuggestions, waitingTime) + Log.i(TAG, "verifySearchSuggestionsCount: Compose test rule waited for $waitingTime ms until the note count equals to: $numberOfSuggestions") + Log.i(TAG, "verifySearchSuggestionsCount: Trying to verify that the count of the search suggestions equals: $numberOfSuggestions") rule.onAllNodesWithTag("mozac.awesomebar.suggestion").assertCountEquals(numberOfSuggestions) + Log.i(TAG, "verifySearchSuggestionsCount: Verified that the count of the search suggestions equals: $numberOfSuggestions") break } catch (e: ComposeTimeoutException) { + Log.i(TAG, "verifySearchSuggestionsCount: ComposeTimeoutException caught, executing fallback methods") if (i == RETRY_COUNT) { throw e } else { + Log.i(TAG, "verifySearchSuggestionsCount: Trying to click device back button") mDevice.pressBack() + Log.i(TAG, "verifySearchSuggestionsCount: Clicked device back button") homeScreen { }.openSearch { typeSearch(searchTerm) @@ -159,28 +181,38 @@ class SearchRobot { ) fun denySuggestionsInPrivateMode() { + Log.i(TAG, "denySuggestionsInPrivateMode: Trying to click the \"Don’t allow\" button") mDevice.findObject( UiSelector().text(getStringResource(R.string.search_suggestions_onboarding_do_not_allow_button)), ).click() + Log.i(TAG, "denySuggestionsInPrivateMode: Clicked the \"Don’t allow\" button") } fun allowSuggestionsInPrivateMode() { + Log.i(TAG, "allowSuggestionsInPrivateMode: Trying to click the \"Allow\" button") mDevice.findObject( UiSelector().text(getStringResource(R.string.search_suggestions_onboarding_allow_button)), ).click() + Log.i(TAG, "allowSuggestionsInPrivateMode: Clicked the \"Allow\" button") } fun verifySearchSelectorButton() = assertUIObjectExists(searchSelectorButton()) fun clickSearchSelectorButton() { + Log.i(TAG, "clickSearchSelectorButton: Waiting for $waitingTime ms for search selector button to exist") searchSelectorButton().waitForExists(waitingTime) + Log.i(TAG, "clickSearchSelectorButton: Waited for $waitingTime ms for search selector button to exist") + Log.i(TAG, "clickSearchSelectorButton: Trying to click the search selector button") searchSelectorButton().click() + Log.i(TAG, "clickSearchSelectorButton: Clicked the search selector button") } fun verifySearchEngineIcon(name: String) = assertUIObjectExists(itemWithDescription(name)) fun verifySearchBarPlaceholder(text: String) { + Log.i(TAG, "verifySearchBarPlaceholder: Waiting for $waitingTime ms for the edit mode toolbar to exist") browserToolbarEditView().waitForExists(waitingTime) + Log.i(TAG, "verifySearchBarPlaceholder: Waited for $waitingTime ms for the edit mode toolbar to exist") assertItemTextEquals(browserToolbarEditView(), expectedText = text) } @@ -198,95 +230,128 @@ class SearchRobot { // New unified search UI search selector. fun selectTemporarySearchMethod(searchEngineName: String) { + Log.i(TAG, "selectTemporarySearchMethod: Trying to click the $searchEngineName search shortcut") searchShortcutList().getChild(UiSelector().text(searchEngineName)).click() + Log.i(TAG, "selectTemporarySearchMethod: Clicked the $searchEngineName search shortcut") } fun clickScanButton() = scanButton().also { + Log.i(TAG, "clickScanButton: Waiting for $waitingTime ms for the scan button to exist") it.waitForExists(waitingTime) + Log.i(TAG, "clickScanButton: Waited for $waitingTime ms for the scan button to exist") + Log.i(TAG, "clickScanButton: Trying to click the scan button") it.click() + Log.i(TAG, "clickScanButton: Clicked the scan button") } fun clickDismissPermissionRequiredDialog() { + Log.i(TAG, "clickDismissPermissionRequiredDialog: Waiting for $waitingTime ms for the \"Dismiss\" permission button to exist") dismissPermissionButton().waitForExists(waitingTime) + Log.i(TAG, "clickDismissPermissionRequiredDialog: Waited for $waitingTime ms for the \"Dismiss\" permission button to exist") + Log.i(TAG, "clickDismissPermissionRequiredDialog: Trying to click the \"Dismiss\" permission button") dismissPermissionButton().click() + Log.i(TAG, "clickDismissPermissionRequiredDialog: Clicked the \"Dismiss\" permission button") } fun clickGoToPermissionsSettings() { + Log.i(TAG, "clickGoToPermissionsSettings: Waiting for $waitingTime ms for the \"Go To Settings\" permission button to exist") goToPermissionsSettingsButton().waitForExists(waitingTime) + Log.i(TAG, "clickGoToPermissionsSettings: Waited for $waitingTime ms for the \"Go To Settings\" permission button to exist") + Log.i(TAG, "clickGoToPermissionsSettings: Trying to click the \"Go To Settings\" permission button") goToPermissionsSettingsButton().click() + Log.i(TAG, "clickGoToPermissionsSettings: Clicked the \"Go To Settings\" permission button") } fun verifyScannerOpen() { + Log.i(TAG, "verifyScannerOpen: Trying to verify that the device camera is opened or that the camera app error message exist") assertTrue( + "$TAG: Neither the device camera was opened nor the camera app error message was displayed", mDevice.findObject(UiSelector().resourceId("$packageName:id/view_finder")) .waitForExists(waitingTime) || // In case there is no camera available, an error will be shown. mDevice.findObject(UiSelector().resourceId("$packageName:id/camera_error")) .exists(), ) + Log.i(TAG, "verifyScannerOpen: Verified that the device camera is opened or that the camera app error message exist") } fun typeSearch(searchTerm: String) { - mDevice.findObject( - UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_edit_url_view"), - ).waitForExists(waitingTime) - + Log.i(TAG, "typeSearch: Waiting for $waitingTime ms for the edit mode toolbar to exist") + browserToolbarEditView().waitForExists(waitingTime) + Log.i(TAG, "typeSearch: Waited for $waitingTime ms for the edit mode toolbar to exist") + Log.i(TAG, "typeSearch: Trying to set the edit mode toolbar text to $searchTerm") browserToolbarEditView().setText(searchTerm) - + Log.i(TAG, "typeSearch: Edit mode toolbar text was set to $searchTerm") + Log.i(TAG, "typeSearch: Waiting for device to be idle") mDevice.waitForIdle() + Log.i(TAG, "typeSearch: Waited for device to be idle") } fun clickClearButton() { + Log.i(TAG, "clickClearButton: Trying to click the clear button") clearButton().click() + Log.i(TAG, "clickClearButton: Clicked the clear button") } fun tapOutsideToDismissSearchBar() { + Log.i(TAG, "tapOutsideToDismissSearchBar: Trying to click the search wrapper") itemWithResId("$packageName:id/search_wrapper").click() - itemWithResId("$packageName:id/mozac_browser_toolbar_edit_url_view") - .waitUntilGone(waitingTime) + Log.i(TAG, "tapOutsideToDismissSearchBar: Clicked the search wrapper") + Log.i(TAG, "tapOutsideToDismissSearchBar: Waiting for $waitingTime ms for the edit mode toolbar to be gone") + browserToolbarEditView().waitUntilGone(waitingTime) + Log.i(TAG, "tapOutsideToDismissSearchBar: Waited for $waitingTime ms for the edit mode toolbar to be gone") } fun longClickToolbar() { + Log.i(TAG, "longClickToolbar: Waiting for $waitingTime ms for $packageName window to be updated") mDevice.waitForWindowUpdate(packageName, waitingTime) + Log.i(TAG, "longClickToolbar: Waited for $waitingTime ms for $packageName window to be updated") + Log.i(TAG, "longClickToolbar: Waiting for $waitingTime ms for the awesome bar to exist") mDevice.findObject(UiSelector().resourceId("$packageName:id/awesomeBar")) .waitForExists(waitingTime) + Log.i(TAG, "longClickToolbar: Waited for $waitingTime ms for the awesome bar to exist") + Log.i(TAG, "longClickToolbar: Waiting for $waitingTime ms for the toolbar to exist") mDevice.findObject(UiSelector().resourceId("$packageName:id/toolbar")) .waitForExists(waitingTime) - val toolbar = mDevice.findObject(By.res("$packageName:id/toolbar")) - toolbar.click(LONG_CLICK_DURATION) + Log.i(TAG, "longClickToolbar: Waited for $waitingTime ms for the toolbar to exist") + Log.i(TAG, "longClickToolbar: Trying to perform long click on the toolbar") + mDevice.findObject(By.res("$packageName:id/toolbar")).click(LONG_CLICK_DURATION) + Log.i(TAG, "longClickToolbar: Performed long click on the toolbar") } fun clickPasteText() { + Log.i(TAG, "clickPasteText: Waiting for $waitingTime ms for the \"Paste\" option to exist") mDevice.findObject(UiSelector().textContains("Paste")).waitForExists(waitingTime) - val pasteText = mDevice.findObject(By.textContains("Paste")) - pasteText.click() - } - - fun expandSearchSuggestionsList() { - onView(allOf(withId(R.id.search_wrapper))).perform( - closeSoftKeyboard(), - ) - browserToolbarEditView().swipeUp(2) + Log.i(TAG, "clickPasteText: Waited for $waitingTime ms for the \"Paste\" option to exist") + Log.i(TAG, "clickPasteText: Trying to click the \"Paste\" button") + mDevice.findObject(By.textContains("Paste")).click() + Log.i(TAG, "clickPasteText: Clicked the \"Paste\" button") } fun verifyTranslatedFocusedNavigationToolbar(toolbarHintString: String) = assertItemTextContains(browserToolbarEditView(), itemText = toolbarHintString) fun verifyTypedToolbarText(expectedText: String) { + Log.i(TAG, "verifyTypedToolbarText: Waiting for $waitingTime ms for the toolbar to exist") mDevice.findObject(UiSelector().resourceId("$packageName:id/toolbar")) .waitForExists(waitingTime) - mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_url_view")) - .waitForExists(waitingTime) + Log.i(TAG, "verifyTypedToolbarText: Waited for $waitingTime ms for the toolbar to exist") + Log.i(TAG, "verifyTypedToolbarText: Waiting for $waitingTime ms for the edit mode toolbar to exist") + browserToolbarEditView().waitForExists(waitingTime) + Log.i(TAG, "verifyTypedToolbarText: Waited for $waitingTime ms for the edit mode toolbar to exist") + Log.i(TAG, "verifyTypedToolbarText: Trying to verify that $expectedText is visible in the toolbar") onView( allOf( withText(expectedText), withId(R.id.mozac_browser_toolbar_edit_url_view), ), ).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) + Log.i(TAG, "verifyTypedToolbarText: Verified that $expectedText is visible in the toolbar") } fun verifySearchBarPosition(bottomPosition: Boolean) { + Log.i(TAG, "verifySearchBarPosition: Trying to verify that the search bar is set to bottom: $bottomPosition") onView(withId(R.id.toolbar)) .check( if (bottomPosition) { @@ -295,13 +360,17 @@ class SearchRobot { PositionAssertions.isCompletelyAbove(withId(R.id.keyboard_divider)) }, ) + Log.i(TAG, "verifySearchBarPosition: Verified that the search bar is set to bottom: $bottomPosition") } fun deleteSearchKeywordCharacters(numberOfDeletionSteps: Int) { for (i in 1..numberOfDeletionSteps) { + Log.i(TAG, "deleteSearchKeywordCharacters: Trying to click keyboard delete button $i times") mDevice.pressDelete() - Log.i(Constants.TAG, "deleteSearchKeywordCharacters: Pressed keyboard delete button $i times") + Log.i(TAG, "deleteSearchKeywordCharacters: Clicked keyboard delete button $i times") + Log.i(TAG, "deleteSearchKeywordCharacters: Waiting for $waitingTimeShort ms for $appName window to be updated") mDevice.waitForWindowUpdate(appName, waitingTimeShort) + Log.i(TAG, "deleteSearchKeywordCharacters: Waited for $waitingTimeShort ms for $appName window to be updated") } } @@ -310,11 +379,18 @@ class SearchRobot { fun dismissSearchBar(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition { try { + Log.i(TAG, "dismissSearchBar: Waiting for $waitingTime ms for the search wrapper to exist") searchWrapper().waitForExists(waitingTime) + Log.i(TAG, "dismissSearchBar: Waited for $waitingTime ms for the search wrapper to exist") + Log.i(TAG, "dismissSearchBar: Trying to click device back button") mDevice.pressBack() + Log.i(TAG, "dismissSearchBar: Clicked device back button") assertUIObjectIsGone(searchWrapper()) } catch (e: AssertionError) { + Log.i(TAG, "dismissSearchBar: AssertionError caught, executing fallback methods") + Log.i(TAG, "dismissSearchBar: Trying to click device back button") mDevice.pressBack() + Log.i(TAG, "dismissSearchBar: Clicked device back button") assertUIObjectIsGone(searchWrapper()) } @@ -323,9 +399,15 @@ class SearchRobot { } fun openBrowser(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { + Log.i(TAG, "openBrowser: Waiting for device to be idle") mDevice.waitForIdle() + Log.i(TAG, "openBrowser: Waited for device to be idle") + Log.i(TAG, "openBrowser: Trying to set the edit mode toolbar text to: mozilla") browserToolbarEditView().setText("mozilla\n") + Log.i(TAG, "openBrowser: Edit mode toolbar text was set to: mozilla") + Log.i(TAG, "openBrowser: Trying to click device enter button") mDevice.pressEnter() + Log.i(TAG, "openBrowser: Clicked device enter button") BrowserRobot().interact() return BrowserRobot.Transition() @@ -333,9 +415,15 @@ class SearchRobot { fun submitQuery(query: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { sessionLoadedIdlingResource = SessionLoadedIdlingResource() + Log.i(TAG, "submitQuery: Waiting for $waitingTime ms for the search wrapper to exist") searchWrapper().waitForExists(waitingTime) + Log.i(TAG, "submitQuery: Waited for $waitingTime ms for the search wrapper to exist") + Log.i(TAG, "submitQuery: Trying to set the edit mode toolbar text to: $query") browserToolbarEditView().setText(query) + Log.i(TAG, "submitQuery: Edit mode toolbar text was set to: $query") + Log.i(TAG, "submitQuery: Trying to click device enter button") mDevice.pressEnter() + Log.i(TAG, "submitQuery: Clicked device enter button") runWithIdleRes(sessionLoadedIdlingResource) { assertUIObjectExists(itemWithResId("$packageName:id/browserLayout")) @@ -346,7 +434,9 @@ class SearchRobot { } fun clickSearchEngineSettings(interact: SettingsSubMenuSearchRobot.() -> Unit): SettingsSubMenuSearchRobot.Transition { + Log.i(TAG, "clickSearchEngineSettings: Trying to click the \"Search settings\" button") searchShortcutList().getChild(UiSelector().text("Search settings")).click() + Log.i(TAG, "clickSearchEngineSettings: Clicked the \"Search settings\" button") SettingsSubMenuSearchRobot().interact() return SettingsSubMenuSearchRobot.Transition() @@ -354,8 +444,12 @@ class SearchRobot { fun clickSearchSuggestion(searchSuggestion: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { mDevice.findObject(UiSelector().textContains(searchSuggestion)).also { + Log.i(TAG, "clickSearchSuggestion: Waiting for $waitingTime ms for search suggestion: $searchSuggestion to exist") it.waitForExists(waitingTime) + Log.i(TAG, "clickSearchSuggestion: Waited for $waitingTime ms for search suggestion: $searchSuggestion to exist") + Log.i(TAG, "clickSearchSuggestion: Trying to click search suggestion: $searchSuggestion and wait for $waitingTimeShort ms for a new window") it.clickAndWaitForNewWindow(waitingTimeShort) + Log.i(TAG, "clickSearchSuggestion: Clicked search suggestion: $searchSuggestion and waited for $waitingTimeShort ms for a new window") } BrowserRobot().interact() From d371e3bff8be1ebdb885a030c90fd7d034ac3bf8 Mon Sep 17 00:00:00 2001 From: AndiAJ Date: Mon, 19 Feb 2024 13:13:26 +0200 Subject: [PATCH 006/238] Bug 1880812 - Convert private variables to functions so they don't get initialised --- .../fenix/ui/robots/SettingsSubMenuCustomizeRobot.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuCustomizeRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuCustomizeRobot.kt index 20d455d19..d8be72bed 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuCustomizeRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuCustomizeRobot.kt @@ -46,12 +46,12 @@ class SettingsSubMenuCustomizeRobot { .check(matches(hasSibling(allOf(withId(R.id.radio_button), isChecked())))) } - fun clickSwipeToolbarToSwitchTabToggle() = swipeToolbarToggle.click() + fun clickSwipeToolbarToSwitchTabToggle() = swipeToolbarToggle().click() - fun clickPullToRefreshToggle() = pullToRefreshToggle.click() + fun clickPullToRefreshToggle() = pullToRefreshToggle().click() fun verifySwipeToolbarGesturePrefState(isEnabled: Boolean) { - swipeToolbarToggle + swipeToolbarToggle() .check( matches( hasCousin( @@ -69,7 +69,7 @@ class SettingsSubMenuCustomizeRobot { } fun verifyPullToRefreshGesturePrefState(isEnabled: Boolean) { - pullToRefreshToggle + pullToRefreshToggle() .check( matches( hasCousin( @@ -120,10 +120,10 @@ private fun deviceModeToggle(): ViewInteraction { return onView(withText(followDeviceThemeText)) } -private val swipeToolbarToggle = +private fun swipeToolbarToggle() = onView(withText(getStringResource(R.string.preference_gestures_swipe_toolbar_switch_tabs))) -private val pullToRefreshToggle = +private fun pullToRefreshToggle() = onView(withText(getStringResource(R.string.preference_gestures_website_pull_to_refresh))) private fun goBackButton() = From cc1dbb942f815b25baa059d919312055869c4cac Mon Sep 17 00:00:00 2001 From: AndiAJ Date: Mon, 19 Feb 2024 13:28:57 +0200 Subject: [PATCH 007/238] Bug 1880812 - Add logs to SettingsSubMenuCustomizeRobot --- .../robots/SettingsSubMenuCustomizeRobot.kt | 76 ++++++++++++++----- 1 file changed, 58 insertions(+), 18 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuCustomizeRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuCustomizeRobot.kt index d8be72bed..e9ffe4992 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuCustomizeRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuCustomizeRobot.kt @@ -7,9 +7,10 @@ package org.mozilla.fenix.ui.robots import android.os.Build +import android.util.Log import androidx.test.espresso.Espresso.onView import androidx.test.espresso.ViewInteraction -import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.hasSibling @@ -21,6 +22,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withText import org.hamcrest.CoreMatchers.allOf import org.hamcrest.Matchers.endsWith import org.mozilla.fenix.R +import org.mozilla.fenix.helpers.Constants.TAG import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource import org.mozilla.fenix.helpers.TestHelper.hasCousin import org.mozilla.fenix.helpers.TestHelper.mDevice @@ -31,26 +33,66 @@ import org.mozilla.fenix.helpers.click */ class SettingsSubMenuCustomizeRobot { - fun verifyThemes() = assertThemes() + fun verifyThemes() { + Log.i(TAG, "verifyThemes: Trying to verify that the \"Light\" mode option is visible") + lightModeToggle() + .check(matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) + Log.i(TAG, "verifyThemes: Verified that the \"Light\" mode option is visible") + Log.i(TAG, "verifyThemes: Trying to verify that the \"Dark\" mode option is visible") + darkModeToggle() + .check(matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) + Log.i(TAG, "verifyThemes: Verified that the \"Dark\" mode option is visible") + Log.i(TAG, "verifyThemes: Trying to verify that the \"Follow device theme\" option is visible") + deviceModeToggle() + .check(matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) + Log.i(TAG, "verifyThemes: Verified that the \"Follow device theme\" option is visible") + } - fun selectDarkMode() = darkModeToggle().click() + fun selectDarkMode() { + Log.i(TAG, "selectDarkMode: Trying to click the \"Dark\" mode option") + darkModeToggle().click() + Log.i(TAG, "selectDarkMode: Clicked the \"Dark\" mode option") + } - fun selectLightMode() = lightModeToggle().click() + fun selectLightMode() { + Log.i(TAG, "selectLightMode: Trying to click the \"Light\" mode option") + lightModeToggle().click() + Log.i(TAG, "selectLightMode: Clicked the \"Light\" mode option") + } - fun clickTopToolbarToggle() = topToolbarToggle().click() + fun clickTopToolbarToggle() { + Log.i(TAG, "clickTopToolbarToggle: Trying to click the \"Top\" toolbar option") + topToolbarToggle().click() + Log.i(TAG, "clickTopToolbarToggle: Clicked the \"Top\" toolbar option") + } - fun clickBottomToolbarToggle() = bottomToolbarToggle().click() + fun clickBottomToolbarToggle() { + Log.i(TAG, "clickBottomToolbarToggle: Trying to click the \"Bottom\" toolbar option") + bottomToolbarToggle().click() + Log.i(TAG, "clickBottomToolbarToggle: Clicked the \"Bottom\" toolbar option") + } fun verifyToolbarPositionPreference(selectedPosition: String) { + Log.i(TAG, "verifyToolbarPositionPreference: Trying to verify that the $selectedPosition toolbar option is checked") onView(withText(selectedPosition)) .check(matches(hasSibling(allOf(withId(R.id.radio_button), isChecked())))) + Log.i(TAG, "verifyToolbarPositionPreference: Verified that the $selectedPosition toolbar option is checked") } - fun clickSwipeToolbarToSwitchTabToggle() = swipeToolbarToggle().click() + fun clickSwipeToolbarToSwitchTabToggle() { + Log.i(TAG, "clickSwipeToolbarToSwitchTabToggle: Trying to click the \"Swipe toolbar sideways to switch tabs\" toggle") + swipeToolbarToggle().click() + Log.i(TAG, "clickSwipeToolbarToSwitchTabToggle: Clicked the \"Swipe toolbar sideways to switch tabs\" toggle") + } - fun clickPullToRefreshToggle() = pullToRefreshToggle().click() + fun clickPullToRefreshToggle() { + Log.i(TAG, "clickPullToRefreshToggle: Trying to click the \"Pull to refresh\" toggle") + pullToRefreshToggle().click() + Log.i(TAG, "clickPullToRefreshToggle: Clicked the \"Pull to refresh\" toggle") + } fun verifySwipeToolbarGesturePrefState(isEnabled: Boolean) { + Log.i(TAG, "verifySwipeToolbarGesturePrefState: Trying to verify that the \"Swipe toolbar sideways to switch tabs\" toggle is checked: $isEnabled") swipeToolbarToggle() .check( matches( @@ -66,9 +108,11 @@ class SettingsSubMenuCustomizeRobot { ), ), ) + Log.i(TAG, "verifySwipeToolbarGesturePrefState: Verified that the \"Swipe toolbar sideways to switch tabs\" toggle is checked: $isEnabled") } fun verifyPullToRefreshGesturePrefState(isEnabled: Boolean) { + Log.i(TAG, "verifyPullToRefreshGesturePrefState: Trying to verify that the \"Pull to refresh\" toggle is checked: $isEnabled") pullToRefreshToggle() .check( matches( @@ -84,12 +128,17 @@ class SettingsSubMenuCustomizeRobot { ), ), ) + Log.i(TAG, "verifyPullToRefreshGesturePrefState: Verified that the \"Pull to refresh\" toggle is checked: $isEnabled") } class Transition { fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition { + Log.i(TAG, "goBack: Waiting for device to be idle") mDevice.waitForIdle() - goBackButton().perform(ViewActions.click()) + Log.i(TAG, "goBack: Waited for device to be idle") + Log.i(TAG, "goBack: Trying to click the navigate up toolbar button") + goBackButton().perform(click()) + Log.i(TAG, "goBack: Clicked the navigate up toolbar button") SettingsRobot().interact() return SettingsRobot.Transition() @@ -97,15 +146,6 @@ class SettingsSubMenuCustomizeRobot { } } -private fun assertThemes() { - lightModeToggle() - .check(matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) - darkModeToggle() - .check(matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) - deviceModeToggle() - .check(matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) -} - private fun darkModeToggle() = onView(withText("Dark")) private fun lightModeToggle() = onView(withText("Light")) From d8d08dc2c41325f6c1d24ec73838f2b48ec44229 Mon Sep 17 00:00:00 2001 From: AndiAJ Date: Mon, 19 Feb 2024 14:42:24 +0200 Subject: [PATCH 008/238] Bug 1880821 - Add test logs to SettingsSubMenuDataCollectionRobot --- .../SettingsSubMenuDataCollectionRobot.kt | 63 +++++++++++++++---- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDataCollectionRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDataCollectionRobot.kt index 062d3a05c..10a375746 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDataCollectionRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDataCollectionRobot.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.ui.robots +import android.util.Log import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.RootMatchers @@ -17,6 +18,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withText import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.endsWith import org.mozilla.fenix.R +import org.mozilla.fenix.helpers.Constants.TAG import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText @@ -54,7 +56,8 @@ class SettingsSubMenuDataCollectionRobot { ) } - fun verifyUsageAndTechnicalDataToggle(enabled: Boolean) = + fun verifyUsageAndTechnicalDataToggle(enabled: Boolean) { + Log.i(TAG, "verifyUsageAndTechnicalDataToggle: Trying to verify that the \"Usage and technical data\" toggle is checked: $enabled") onView(withText(R.string.preference_usage_data)) .check( matches( @@ -70,8 +73,11 @@ class SettingsSubMenuDataCollectionRobot { ), ), ) + Log.i(TAG, "verifyUsageAndTechnicalDataToggle: Verified that the \"Usage and technical data\" toggle is checked: $enabled") + } - fun verifyMarketingDataToggle(enabled: Boolean) = + fun verifyMarketingDataToggle(enabled: Boolean) { + Log.i(TAG, "verifyMarketingDataToggle: Trying to verify that the \"Marketing data\" toggle is checked: $enabled") onView(withText(R.string.preferences_marketing_data)) .check( matches( @@ -87,8 +93,11 @@ class SettingsSubMenuDataCollectionRobot { ), ), ) + Log.i(TAG, "verifyMarketingDataToggle: Verified that the \"Marketing data\" toggle is checked: $enabled") + } - fun verifyStudiesToggle(enabled: Boolean) = + fun verifyStudiesToggle(enabled: Boolean) { + Log.i(TAG, "verifyStudiesToggle: Trying to verify that the \"Studies\" toggle is checked: $enabled") onView(withId(R.id.studies_switch)) .check( matches( @@ -99,35 +108,63 @@ class SettingsSubMenuDataCollectionRobot { }, ), ) + Log.i(TAG, "verifyStudiesToggle: Verified that the \"Studies\" toggle is checked: $enabled") + } - fun clickUsageAndTechnicalDataToggle() = + fun clickUsageAndTechnicalDataToggle() { + Log.i(TAG, "clickUsageAndTechnicalDataToggle: Trying to click the \"Usage and technical data\" toggle") itemContainingText(getStringResource(R.string.preference_usage_data)).click() + Log.i(TAG, "clickUsageAndTechnicalDataToggle: Clicked the \"Usage and technical data\" toggle") + } - fun clickMarketingDataToggle() = + fun clickMarketingDataToggle() { + Log.i(TAG, "clickUsageAndTechnicalDataToggle: Trying to click the \"Marketing data\" toggle") itemContainingText(getStringResource(R.string.preferences_marketing_data)).click() + Log.i(TAG, "clickUsageAndTechnicalDataToggle: Clicked the \"Marketing data\" toggle") + } - fun clickStudiesOption() = + fun clickStudiesOption() { + Log.i(TAG, "clickStudiesOption: Trying to click the \"Studies\" option") itemContainingText(getStringResource(R.string.preference_experiments_2)).click() + Log.i(TAG, "clickStudiesOption: Clicked the \"Studies\" option") + } - fun clickStudiesToggle() = + fun clickStudiesToggle() { + Log.i(TAG, "clickStudiesToggle: Trying to click the \"Studies\" toggle") itemWithResId("$packageName:id/studies_switch").click() + Log.i(TAG, "clickStudiesToggle: Clicked the \"Studies\" toggle") + } fun verifyStudiesDialog() { assertUIObjectExists( itemWithResId("$packageName:id/alertTitle"), itemContainingText(getStringResource(R.string.studies_restart_app)), ) - studiesDialogOkButton.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) - studiesDialogCancelButton.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) + Log.i(TAG, "verifyStudiesDialog: Trying to verify that the \"Studies\" dialog \"Ok\" button is visible") + studiesDialogOkButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) + Log.i(TAG, "verifyStudiesDialog: Verified that the \"Studies\" dialog \"Ok\" button is visible") + Log.i(TAG, "verifyStudiesDialog: Trying to verify that the \"Studies\" dialog \"Cancel\" button is visible") + studiesDialogCancelButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) + Log.i(TAG, "verifyStudiesDialog: Verified that the \"Studies\" dialog \"Cancel\" button is visible") } - fun clickStudiesDialogCancelButton() = studiesDialogCancelButton.click() + fun clickStudiesDialogCancelButton() { + Log.i(TAG, "clickStudiesDialogCancelButton: Trying to click the \"Studies\" dialog \"Cancel\" button") + studiesDialogCancelButton().click() + Log.i(TAG, "clickStudiesDialogCancelButton: Clicked the \"Studies\" dialog \"Cancel\" button") + } - fun clickStudiesDialogOkButton() = studiesDialogOkButton.click() + fun clickStudiesDialogOkButton() { + Log.i(TAG, "clickStudiesDialogOkButton: Trying to click the \"Studies\" dialog \"Ok\" button") + studiesDialogOkButton().click() + Log.i(TAG, "clickStudiesDialogOkButton: Clicked the \"Studies\" dialog \"Ok\" button") + } class Transition { fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition { + Log.i(TAG, "goBack: Trying to click the navigate up toolbar button") goBackButton().click() + Log.i(TAG, "goBack: Clicked the navigate up toolbar button") SettingsRobot().interact() return SettingsRobot.Transition() @@ -136,5 +173,5 @@ class SettingsSubMenuDataCollectionRobot { } private fun goBackButton() = itemWithDescription("Navigate up") -private val studiesDialogOkButton = onView(withId(android.R.id.button1)).inRoot(RootMatchers.isDialog()) -private val studiesDialogCancelButton = onView(withId(android.R.id.button2)).inRoot(RootMatchers.isDialog()) +private fun studiesDialogOkButton() = onView(withId(android.R.id.button1)).inRoot(RootMatchers.isDialog()) +private fun studiesDialogCancelButton() = onView(withId(android.R.id.button2)).inRoot(RootMatchers.isDialog()) From 5a37a89af2695907b7ade485bd25d973d346f401 Mon Sep 17 00:00:00 2001 From: AndiAJ Date: Mon, 19 Feb 2024 15:01:07 +0200 Subject: [PATCH 009/238] Bug 1880827 - Convert private variables to functions so they don't get initialised --- ...ngsSubMenuDeleteBrowsingDataOnQuitRobot.kt | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDeleteBrowsingDataOnQuitRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDeleteBrowsingDataOnQuitRobot.kt index a818ef487..1f7a8feb4 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDeleteBrowsingDataOnQuitRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDeleteBrowsingDataOnQuitRobot.kt @@ -28,7 +28,7 @@ import org.mozilla.fenix.helpers.isChecked */ class SettingsSubMenuDeleteBrowsingDataOnQuitRobot { - fun verifyNavigationToolBarHeader() = + fun verifyNavigationToolBarHeader() { onView( allOf( withId(R.id.navigationToolbar), @@ -36,9 +36,10 @@ class SettingsSubMenuDeleteBrowsingDataOnQuitRobot { ), ) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + } fun verifyDeleteBrowsingOnQuitEnabled(enabled: Boolean) = - deleteBrowsingOnQuitButton.assertIsChecked(enabled) + deleteBrowsingOnQuitButton().assertIsChecked(enabled) fun verifyDeleteBrowsingOnQuitButtonSummary() = onView( @@ -48,25 +49,25 @@ class SettingsSubMenuDeleteBrowsingDataOnQuitRobot { fun clickDeleteBrowsingOnQuitButtonSwitch() = onView(withResourceName("switch_widget")).click() fun verifyAllTheCheckBoxesText() { - openTabsCheckbox + openTabsCheckbox() .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - browsingHistoryCheckbox + browsingHistoryCheckbox() .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - cookiesAndSiteDataCheckbox + cookiesAndSiteDataCheckbox() .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) onView(withText(R.string.preferences_delete_browsing_data_cookies_subtitle)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - cachedFilesCheckbox + cachedFilesCheckbox() .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) onView(withText(R.string.preferences_delete_browsing_data_cached_files_subtitle)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - sitePermissionsCheckbox + sitePermissionsCheckbox() .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) } @@ -91,7 +92,7 @@ class SettingsSubMenuDeleteBrowsingDataOnQuitRobot { class Transition { fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition { - goBackButton.click() + goBackButton().click() SettingsRobot().interact() return SettingsRobot.Transition() @@ -99,21 +100,21 @@ class SettingsSubMenuDeleteBrowsingDataOnQuitRobot { } } -private val goBackButton = onView(withContentDescription("Navigate up")) +private fun goBackButton() = onView(withContentDescription("Navigate up")) -private val deleteBrowsingOnQuitButton = +private fun deleteBrowsingOnQuitButton() = onView(withClassName(containsString("android.widget.Switch"))) -private val openTabsCheckbox = +private fun openTabsCheckbox() = onView(withText(R.string.preferences_delete_browsing_data_tabs_title_2)) -private val browsingHistoryCheckbox = +private fun browsingHistoryCheckbox() = onView(withText(R.string.preferences_delete_browsing_data_browsing_history_title)) -private val cookiesAndSiteDataCheckbox = onView(withText(R.string.preferences_delete_browsing_data_cookies_and_site_data)) +private fun cookiesAndSiteDataCheckbox() = onView(withText(R.string.preferences_delete_browsing_data_cookies_and_site_data)) -private val cachedFilesCheckbox = +private fun cachedFilesCheckbox() = onView(withText(R.string.preferences_delete_browsing_data_cached_files)) -private val sitePermissionsCheckbox = +private fun sitePermissionsCheckbox() = onView(withText(R.string.preferences_delete_browsing_data_site_permissions)) From 861c384d7fb2bf3288e5e513ef7fe7fa736c8689 Mon Sep 17 00:00:00 2001 From: AndiAJ Date: Mon, 19 Feb 2024 15:18:24 +0200 Subject: [PATCH 010/238] Bug 1880827 - Add test logs to SettingsSubMenuDeleteBrowsingDataOnQuitRobot --- ...ngsSubMenuDeleteBrowsingDataOnQuitRobot.kt | 44 +++++++++++++++---- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDeleteBrowsingDataOnQuitRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDeleteBrowsingDataOnQuitRobot.kt index 1f7a8feb4..af8c30692 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDeleteBrowsingDataOnQuitRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDeleteBrowsingDataOnQuitRobot.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.ui.robots +import android.util.Log import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.Visibility @@ -18,6 +19,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withText import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.containsString import org.mozilla.fenix.R +import org.mozilla.fenix.helpers.Constants.TAG import org.mozilla.fenix.helpers.assertIsChecked import org.mozilla.fenix.helpers.atPosition import org.mozilla.fenix.helpers.click @@ -29,6 +31,7 @@ import org.mozilla.fenix.helpers.isChecked class SettingsSubMenuDeleteBrowsingDataOnQuitRobot { fun verifyNavigationToolBarHeader() { + Log.i(TAG, "verifyNavigationToolBarHeader: Trying to verify that the \"Delete browsing data on quit\" toolbar title is visible") onView( allOf( withId(R.id.navigationToolbar), @@ -36,43 +39,63 @@ class SettingsSubMenuDeleteBrowsingDataOnQuitRobot { ), ) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + Log.i(TAG, "verifyNavigationToolBarHeader: Verified that the \"Delete browsing data on quit\" toolbar title is visible") } - fun verifyDeleteBrowsingOnQuitEnabled(enabled: Boolean) = + fun verifyDeleteBrowsingOnQuitEnabled(enabled: Boolean) { + Log.i(TAG, "verifyDeleteBrowsingOnQuitEnabled: Trying to verify that the \"Delete browsing data on quit\" toggle is checked: $enabled") deleteBrowsingOnQuitButton().assertIsChecked(enabled) + Log.i(TAG, "verifyDeleteBrowsingOnQuitEnabled: Verified that the \"Delete browsing data on quit\" toggle is checked: $enabled") + } - fun verifyDeleteBrowsingOnQuitButtonSummary() = + fun verifyDeleteBrowsingOnQuitButtonSummary() { + Log.i(TAG, "verifyDeleteBrowsingOnQuitButtonSummary: Trying to verify that the \"Delete browsing data on quit\" option summary is visible") onView( withText(R.string.preference_summary_delete_browsing_data_on_quit_2), ).check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + Log.i(TAG, "verifyDeleteBrowsingOnQuitButtonSummary: Verified that the \"Delete browsing data on quit\" option summary is visible") + } - fun clickDeleteBrowsingOnQuitButtonSwitch() = onView(withResourceName("switch_widget")).click() + fun clickDeleteBrowsingOnQuitButtonSwitch() { + Log.i(TAG, "clickDeleteBrowsingOnQuitButtonSwitch: Trying to click the \"Delete browsing data on quit\" toggle") + onView(withResourceName("switch_widget")).click() + Log.i(TAG, "clickDeleteBrowsingOnQuitButtonSwitch: Clicked the \"Delete browsing data on quit\" toggle") + } fun verifyAllTheCheckBoxesText() { + Log.i(TAG, "verifyAllTheCheckBoxesText: Trying to verify that the \"Open tabs\" option is visible") openTabsCheckbox() .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - + Log.i(TAG, "verifyAllTheCheckBoxesText: Verified that the \"Open tabs\" option is visible") + Log.i(TAG, "verifyAllTheCheckBoxesText: Trying to verify that the \"Browsing history\" option is visible") browsingHistoryCheckbox() .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - + Log.i(TAG, "verifyAllTheCheckBoxesText: Verified that the \"Browsing history\" option is visible") + Log.i(TAG, "verifyAllTheCheckBoxesText: Trying to verify that the \"Cookies and site data\" option is visible") cookiesAndSiteDataCheckbox() .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - + Log.i(TAG, "verifyAllTheCheckBoxesText: Verified that the \"Cookies and site data\" option is visible") + Log.i(TAG, "verifyAllTheCheckBoxesText: Trying to verify that the \"Cookies and site data\" option summary is visible") onView(withText(R.string.preferences_delete_browsing_data_cookies_subtitle)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - + Log.i(TAG, "verifyAllTheCheckBoxesText: Verified that the \"Cookies and site data\" option summary is visible") + Log.i(TAG, "verifyAllTheCheckBoxesText: Trying to verify that the \"Cached images and files\" option is visible") cachedFilesCheckbox() .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - + Log.i(TAG, "verifyAllTheCheckBoxesText: Verified that the \"Cached images and files\" option is visible") + Log.i(TAG, "verifyAllTheCheckBoxesText: Trying to verify that the \"Cached images and files\" option summary is visible") onView(withText(R.string.preferences_delete_browsing_data_cached_files_subtitle)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - + Log.i(TAG, "verifyAllTheCheckBoxesText: Verified that the \"Cached images and files\" option summary is visible") + Log.i(TAG, "verifyAllTheCheckBoxesText: Trying to verify that the \"Site permissions\" option is visible") sitePermissionsCheckbox() .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + Log.i(TAG, "verifyAllTheCheckBoxesText: Verified that the \"Site permissions\" option is visible") } fun verifyAllTheCheckBoxesChecked(checked: Boolean) { for (index in 2..7) { + Log.i(TAG, "verifyAllTheCheckBoxesChecked: Trying to verify that the the check box at position ${index - 1} is checked: $checked") onView(withId(R.id.recycler_view)) .check( matches( @@ -87,12 +110,15 @@ class SettingsSubMenuDeleteBrowsingDataOnQuitRobot { ), ), ) + Log.i(TAG, "verifyAllTheCheckBoxesChecked: Verified that the the check box at position ${index - 1} is checked: $checked") } } class Transition { fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition { + Log.i(TAG, "goBack: Trying to click navigate up toolbar button") goBackButton().click() + Log.i(TAG, "goBack: Clicked navigate up toolbar button") SettingsRobot().interact() return SettingsRobot.Transition() From c0111386f82d1b8e87c9bb58b07921c3efa1cd89 Mon Sep 17 00:00:00 2001 From: Titouan Thibaud Date: Tue, 20 Feb 2024 11:16:41 +0100 Subject: [PATCH 011/238] Bug 1880999 - Start the Nightly 125 development cycle --- app/src/main/res/values/strings.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 486a5a0ca..c1a749708 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2141,8 +2141,6 @@ Adjusted rating - Unreliable reviews removed - Based on reliable reviews Highlights from recent reviews @@ -2198,10 +2196,6 @@ If you see this product is back in stock, report it and we’ll work on checking the reviews. Report product is in stock - - Checking review quality - - Checking review quality Checking review quality (%s) From 039c1238a848d4853dc04f20b9beefddc44c0f2c Mon Sep 17 00:00:00 2001 From: James Hugman Date: Mon, 29 Jan 2024 15:05:17 +0000 Subject: [PATCH 012/238] =?UTF-8?q?Bug=201880476=20=E2=80=94=20Remove=20re?= =?UTF-8?q?dundant=20caching=20and=20destroy=20jexl=20helper=20after=20use?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fenix/onboarding/OnboardingFragment.kt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingFragment.kt b/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingFragment.kt index d46adfac3..d216668ca 100644 --- a/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingFragment.kt @@ -23,6 +23,7 @@ import androidx.fragment.app.Fragment import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.navigation.fragment.findNavController import mozilla.components.service.nimbus.evalJexlSafe +import mozilla.components.service.nimbus.messaging.use import mozilla.components.support.base.ext.areNotificationsEnabledSafe import mozilla.components.support.utils.BrowsersCache import org.mozilla.fenix.R @@ -228,7 +229,7 @@ class OnboardingFragment : Fragment() { showAddWidgetPage: Boolean, ): List { val jexlConditions = FxNimbus.features.junoOnboarding.value().conditions - val jexlHelper = requireContext().components.analytics.messagingStorage.helper + val jexlHelper = requireContext().components.analytics.messagingStorage.createMessagingHelper() val privacyCaption = Caption( text = getString(R.string.juno_onboarding_privacy_notice_text), @@ -249,12 +250,14 @@ class OnboardingFragment : Fragment() { }, ), ) - return FxNimbus.features.junoOnboarding.value().cards.values.toPageUiData( - privacyCaption, - showDefaultBrowserPage, - showNotificationPage, - showAddWidgetPage, - jexlConditions, - ) { condition -> jexlHelper.evalJexlSafe(condition) } + return jexlHelper.use { + FxNimbus.features.junoOnboarding.value().cards.values.toPageUiData( + privacyCaption, + showDefaultBrowserPage, + showNotificationPage, + showAddWidgetPage, + jexlConditions, + ) { condition -> jexlHelper.evalJexlSafe(condition) } + } } } From 0ea68ff9fc1267933030081d6cb333f1eb2aa6f6 Mon Sep 17 00:00:00 2001 From: AndiAJ Date: Tue, 20 Feb 2024 12:12:15 +0200 Subject: [PATCH 013/238] Bug 1881010 - Remove redundant assertion functions from SettingsSubMenuDeleteBrowsingDataRobot --- .../SettingsSubMenuDeleteBrowsingDataRobot.kt | 95 ++++++++----------- 1 file changed, 40 insertions(+), 55 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDeleteBrowsingDataRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDeleteBrowsingDataRobot.kt index 21978b4a3..6cc3380f5 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDeleteBrowsingDataRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDeleteBrowsingDataRobot.kt @@ -29,15 +29,22 @@ import org.mozilla.fenix.helpers.click */ class SettingsSubMenuDeleteBrowsingDataRobot { - fun verifyAllCheckBoxesAreChecked() = assertAllCheckBoxesAreChecked() - fun verifyOpenTabsCheckBox(status: Boolean) = assertOpenTabsCheckBox(status) - fun verifyBrowsingHistoryDetails(status: Boolean) = assertBrowsingHistoryCheckBox(status) - fun verifyCookiesCheckBox(status: Boolean) = assertCookiesCheckBox(status) - fun verifyCachedFilesCheckBox(status: Boolean) = assertCachedFilesCheckBox(status) - fun verifySitePermissionsCheckBox(status: Boolean) = assertSitePermissionsCheckBox(status) - fun verifyDownloadsCheckBox(status: Boolean) = assertDownloadsCheckBox(status) - fun verifyOpenTabsDetails(tabNumber: String) = assertOpenTabsDescription(tabNumber) - fun verifyBrowsingHistoryDetails(addresses: String) = assertBrowsingHistoryDescription(addresses) + fun verifyAllCheckBoxesAreChecked() { + openTabsCheckBox().assertIsChecked(true) + browsingHistoryCheckBox().assertIsChecked(true) + cookiesAndSiteDataCheckBox().assertIsChecked(true) + cachedFilesCheckBox().assertIsChecked(true) + sitePermissionsCheckBox().assertIsChecked(true) + downloadsCheckBox().assertIsChecked(true) + } + fun verifyOpenTabsCheckBox(status: Boolean) = openTabsCheckBox().assertIsChecked(status) + fun verifyBrowsingHistoryDetails(status: Boolean) = browsingHistoryCheckBox().assertIsChecked(status) + fun verifyCookiesCheckBox(status: Boolean) = cookiesAndSiteDataCheckBox().assertIsChecked(status) + fun verifyCachedFilesCheckBox(status: Boolean) = cachedFilesCheckBox().assertIsChecked(status) + fun verifySitePermissionsCheckBox(status: Boolean) = sitePermissionsCheckBox().assertIsChecked(status) + fun verifyDownloadsCheckBox(status: Boolean) = downloadsCheckBox().assertIsChecked(status) + fun verifyOpenTabsDetails(tabNumber: String) = openTabsDescription(tabNumber).check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + fun verifyBrowsingHistoryDetails(addresses: String) = assertUIObjectExists(browsingHistoryDescription(addresses)) fun verifyDeleteBrowsingDataDialog() { dialogMessage().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) @@ -56,78 +63,78 @@ class SettingsSubMenuDeleteBrowsingDataRobot { fun selectOnlyOpenTabsCheckBox() { clickBrowsingHistoryCheckBox() - assertBrowsingHistoryCheckBox(false) + browsingHistoryCheckBox().assertIsChecked(false) clickCookiesCheckBox() - assertCookiesCheckBox(false) + cookiesAndSiteDataCheckBox().assertIsChecked(false) clickCachedFilesCheckBox() - assertCachedFilesCheckBox(false) + cachedFilesCheckBox().assertIsChecked(false) clickSitePermissionsCheckBox() - assertSitePermissionsCheckBox(false) + sitePermissionsCheckBox().assertIsChecked(false) clickDownloadsCheckBox() - assertDownloadsCheckBox(false) + downloadsCheckBox().assertIsChecked(false) - assertOpenTabsCheckBox(true) + openTabsCheckBox().assertIsChecked(true) } fun selectOnlyBrowsingHistoryCheckBox() { clickOpenTabsCheckBox() - assertOpenTabsCheckBox(false) + openTabsCheckBox().assertIsChecked(false) clickCookiesCheckBox() - assertCookiesCheckBox(false) + cookiesAndSiteDataCheckBox().assertIsChecked(false) clickCachedFilesCheckBox() - assertCachedFilesCheckBox(false) + cachedFilesCheckBox().assertIsChecked(false) clickSitePermissionsCheckBox() - assertSitePermissionsCheckBox(false) + sitePermissionsCheckBox().assertIsChecked(false) clickDownloadsCheckBox() - assertDownloadsCheckBox(false) + downloadsCheckBox().assertIsChecked(false) - assertBrowsingHistoryCheckBox(true) + browsingHistoryCheckBox().assertIsChecked(true) } fun selectOnlyCookiesCheckBox() { clickOpenTabsCheckBox() - assertOpenTabsCheckBox(false) + openTabsCheckBox().assertIsChecked(false) - assertCookiesCheckBox(true) + cookiesAndSiteDataCheckBox().assertIsChecked(true) clickCachedFilesCheckBox() - assertCachedFilesCheckBox(false) + cachedFilesCheckBox().assertIsChecked(false) clickSitePermissionsCheckBox() - assertSitePermissionsCheckBox(false) + sitePermissionsCheckBox().assertIsChecked(false) clickDownloadsCheckBox() - assertDownloadsCheckBox(false) + downloadsCheckBox().assertIsChecked(false) clickBrowsingHistoryCheckBox() - assertBrowsingHistoryCheckBox(false) + browsingHistoryCheckBox().assertIsChecked(false) } fun selectOnlyCachedFilesCheckBox() { clickOpenTabsCheckBox() - assertOpenTabsCheckBox(false) + openTabsCheckBox().assertIsChecked(false) clickBrowsingHistoryCheckBox() - assertBrowsingHistoryCheckBox(false) + browsingHistoryCheckBox().assertIsChecked(false) clickCookiesCheckBox() - assertCookiesCheckBox(false) + cookiesAndSiteDataCheckBox().assertIsChecked(false) - assertCachedFilesCheckBox(true) + cachedFilesCheckBox().assertIsChecked(true) clickSitePermissionsCheckBox() - assertSitePermissionsCheckBox(false) + sitePermissionsCheckBox().assertIsChecked(false) clickDownloadsCheckBox() - assertDownloadsCheckBox(false) + downloadsCheckBox().assertIsChecked(false) } fun confirmDeletionAndAssertSnackbar() { @@ -179,32 +186,10 @@ private fun dialogMessage() = onView(withText("$appName will delete the selected browsing data.")) .inRoot(isDialog()) -private fun assertAllCheckBoxesAreChecked() { - openTabsCheckBox().assertIsChecked(true) - browsingHistoryCheckBox().assertIsChecked(true) - cookiesAndSiteDataCheckBox().assertIsChecked(true) - cachedFilesCheckBox().assertIsChecked(true) - sitePermissionsCheckBox().assertIsChecked(true) - downloadsCheckBox().assertIsChecked(true) -} - -private fun assertOpenTabsDescription(tabNumber: String) = - openTabsDescription(tabNumber).check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - -private fun assertBrowsingHistoryDescription(addresses: String) = - assertUIObjectExists(browsingHistoryDescription(addresses)) - private fun assertDeleteBrowsingDataSnackbar() = assertUIObjectIsGone(itemWithText("Browsing data deleted")) - private fun clickOpenTabsCheckBox() = openTabsCheckBox().click() -private fun assertOpenTabsCheckBox(status: Boolean) = openTabsCheckBox().assertIsChecked(status) private fun clickBrowsingHistoryCheckBox() = browsingHistoryCheckBox().click() -private fun assertBrowsingHistoryCheckBox(status: Boolean) = browsingHistoryCheckBox().assertIsChecked(status) private fun clickCookiesCheckBox() = cookiesAndSiteDataCheckBox().click() -private fun assertCookiesCheckBox(status: Boolean) = cookiesAndSiteDataCheckBox().assertIsChecked(status) private fun clickCachedFilesCheckBox() = cachedFilesCheckBox().click() -private fun assertCachedFilesCheckBox(status: Boolean) = cachedFilesCheckBox().assertIsChecked(status) private fun clickSitePermissionsCheckBox() = sitePermissionsCheckBox().click() -private fun assertSitePermissionsCheckBox(status: Boolean) = sitePermissionsCheckBox().assertIsChecked(status) private fun clickDownloadsCheckBox() = downloadsCheckBox().click() -private fun assertDownloadsCheckBox(status: Boolean) = downloadsCheckBox().assertIsChecked(status) From 3b1aa89e19709477b9d226c15d66d66cb7edd54d Mon Sep 17 00:00:00 2001 From: AndiAJ Date: Tue, 20 Feb 2024 12:12:35 +0200 Subject: [PATCH 014/238] Bug 1881010 - Add logs to SettingsSubMenuDeleteBrowsingDataRobot --- .../SettingsSubMenuDeleteBrowsingDataRobot.kt | 165 ++++++++++++++++-- 1 file changed, 147 insertions(+), 18 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDeleteBrowsingDataRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDeleteBrowsingDataRobot.kt index 6cc3380f5..44e935660 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDeleteBrowsingDataRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDeleteBrowsingDataRobot.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.ui.robots +import android.util.Log import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.RootMatchers.isDialog @@ -16,6 +17,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.uiautomator.UiSelector import org.hamcrest.CoreMatchers.allOf import org.mozilla.fenix.R +import org.mozilla.fenix.helpers.Constants.TAG import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectIsGone import org.mozilla.fenix.helpers.MatcherHelper.itemWithText @@ -30,26 +32,72 @@ import org.mozilla.fenix.helpers.click class SettingsSubMenuDeleteBrowsingDataRobot { fun verifyAllCheckBoxesAreChecked() { + Log.i(TAG, "verifyAllCheckBoxesAreChecked: Trying to verify that the \"Open tabs\" check box is checked") openTabsCheckBox().assertIsChecked(true) + Log.i(TAG, "verifyAllCheckBoxesAreChecked: Verified that the \"Open tabs\" check box is checked") + Log.i(TAG, "verifyAllCheckBoxesAreChecked: Trying to verify that the \"Browsing history\" check box is checked") browsingHistoryCheckBox().assertIsChecked(true) + Log.i(TAG, "verifyAllCheckBoxesAreChecked: Verified that the \"Browsing history\" check box is checked") + Log.i(TAG, "verifyAllCheckBoxesAreChecked: Trying to verify that the \"Cookies and site data\" check box is checked") cookiesAndSiteDataCheckBox().assertIsChecked(true) + Log.i(TAG, "verifyAllCheckBoxesAreChecked: Verified that the \"Cookies and site data\" check box is checked") + Log.i(TAG, "verifyAllCheckBoxesAreChecked: Trying to verify that the \"Cached images and files\" check box is checked") cachedFilesCheckBox().assertIsChecked(true) + Log.i(TAG, "verifyAllCheckBoxesAreChecked: Verified that the \"Cached images and files\" check box is checked") + Log.i(TAG, "verifyAllCheckBoxesAreChecked: Trying to verify that the \"Site permissions\" check box is checked") sitePermissionsCheckBox().assertIsChecked(true) + Log.i(TAG, "verifyAllCheckBoxesAreChecked: Verified that the \"Site permissions\" check box is checked") + Log.i(TAG, "verifyAllCheckBoxesAreChecked: Trying to verify that the \"Downloads\" check box is checked") downloadsCheckBox().assertIsChecked(true) + Log.i(TAG, "verifyAllCheckBoxesAreChecked: Verified that the \"Downloads\" check box is checked") + } + fun verifyOpenTabsCheckBox(status: Boolean) { + Log.i(TAG, "verifyOpenTabsCheckBox: Trying to verify that the \"Open tabs\" check box is checked: $status") + openTabsCheckBox().assertIsChecked(status) + Log.i(TAG, "verifyOpenTabsCheckBox: Verified that the \"Open tabs\" check box is checked: $status") + } + fun verifyBrowsingHistoryDetails(status: Boolean) { + Log.i(TAG, "verifyBrowsingHistoryDetails: Trying to verify that the \"Browsing history\" check box is checked: $status") + browsingHistoryCheckBox().assertIsChecked(status) + Log.i(TAG, "verifyBrowsingHistoryDetails: Verified that the \"Browsing history\" check box is checked: $status") + } + fun verifyCookiesCheckBox(status: Boolean) { + Log.i(TAG, "verifyCookiesCheckBox: Trying to verify that the \"Cookies and site data\" check box is checked: $status") + cookiesAndSiteDataCheckBox().assertIsChecked(status) + Log.i(TAG, "verifyCookiesCheckBox: Verified that the \"Cookies and site data\" check box is checked: $status") + } + fun verifyCachedFilesCheckBox(status: Boolean) { + Log.i(TAG, "verifyCachedFilesCheckBox: Trying to verify that the \"Cached images and files\" check box is checked: $status") + cachedFilesCheckBox().assertIsChecked(status) + Log.i(TAG, "verifyCachedFilesCheckBox: Verified that the \"Cached images and files\" check box is checked: $status") + } + fun verifySitePermissionsCheckBox(status: Boolean) { + Log.i(TAG, "verifySitePermissionsCheckBox: Trying to verify that the \"Site permissions\" check box is checked: $status") + sitePermissionsCheckBox().assertIsChecked(status) + Log.i(TAG, "verifySitePermissionsCheckBox: Verified that the \"Site permissions\" check box is checked: $status") + } + fun verifyDownloadsCheckBox(status: Boolean) { + Log.i(TAG, "verifyDownloadsCheckBox: Trying to verify that the \"Downloads\" check box is checked: $status") + downloadsCheckBox().assertIsChecked(status) + Log.i(TAG, "verifyDownloadsCheckBox: Verified that the \"Downloads\" check box is checked: $status") + } + fun verifyOpenTabsDetails(tabNumber: String) { + Log.i(TAG, "verifyOpenTabsDetails: Trying to verify that the \"Open tabs\" option summary containing $tabNumber open tabs is visible") + openTabsDescription(tabNumber).check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + Log.i(TAG, "verifyOpenTabsDetails: Verified that the \"Open tabs\" option summary containing $tabNumber open tabs is visible") } - fun verifyOpenTabsCheckBox(status: Boolean) = openTabsCheckBox().assertIsChecked(status) - fun verifyBrowsingHistoryDetails(status: Boolean) = browsingHistoryCheckBox().assertIsChecked(status) - fun verifyCookiesCheckBox(status: Boolean) = cookiesAndSiteDataCheckBox().assertIsChecked(status) - fun verifyCachedFilesCheckBox(status: Boolean) = cachedFilesCheckBox().assertIsChecked(status) - fun verifySitePermissionsCheckBox(status: Boolean) = sitePermissionsCheckBox().assertIsChecked(status) - fun verifyDownloadsCheckBox(status: Boolean) = downloadsCheckBox().assertIsChecked(status) - fun verifyOpenTabsDetails(tabNumber: String) = openTabsDescription(tabNumber).check(matches(withEffectiveVisibility(Visibility.VISIBLE))) fun verifyBrowsingHistoryDetails(addresses: String) = assertUIObjectExists(browsingHistoryDescription(addresses)) fun verifyDeleteBrowsingDataDialog() { + Log.i(TAG, "verifyDeleteBrowsingDataDialog: Trying to verify that the delete browsing data dialog message is visible") dialogMessage().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + Log.i(TAG, "verifyDeleteBrowsingDataDialog: Verified that the delete browsing data dialog message is visible") + Log.i(TAG, "verifyDeleteBrowsingDataDialog: Trying to verify that the delete browsing data dialog \"Cancel\" button is visible") dialogCancelButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + Log.i(TAG, "verifyDeleteBrowsingDataDialog: Verified that the delete browsing data dialog \"Cancel\" button is visible") + Log.i(TAG, "verifyDeleteBrowsingDataDialog: Trying to verify that the delete browsing data dialog \"Delete\" button is visible") dialogDeleteButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + Log.i(TAG, "verifyDeleteBrowsingDataDialog: Verified that the delete browsing data dialog \"Delete\" button is visible") } fun switchOpenTabsCheckBox() = clickOpenTabsCheckBox() @@ -58,93 +106,150 @@ class SettingsSubMenuDeleteBrowsingDataRobot { fun switchCachedFilesCheckBox() = clickCachedFilesCheckBox() fun switchSitePermissionsCheckBox() = clickSitePermissionsCheckBox() fun switchDownloadsCheckBox() = clickDownloadsCheckBox() - fun clickDeleteBrowsingDataButton() = deleteBrowsingDataButton().click() - fun clickDialogCancelButton() = dialogCancelButton().click() + fun clickDeleteBrowsingDataButton() { + Log.i(TAG, "clickDeleteBrowsingDataButton: Trying to click the \"Delete browsing data\" button") + deleteBrowsingDataButton().click() + Log.i(TAG, "clickDeleteBrowsingDataButton: Clicked the \"Delete browsing data\" button") + } + fun clickDialogCancelButton() { + Log.i(TAG, "clickDialogCancelButton: Trying to click the delete browsing data dialog \"Cancel\" button") + dialogCancelButton().click() + Log.i(TAG, "clickDialogCancelButton: Clicked the delete browsing data dialog \"Cancel\" button") + } fun selectOnlyOpenTabsCheckBox() { clickBrowsingHistoryCheckBox() + Log.i(TAG, "selectOnlyOpenTabsCheckBox: Trying to verify that the \"Browsing history\" check box is not checked") browsingHistoryCheckBox().assertIsChecked(false) + Log.i(TAG, "selectOnlyOpenTabsCheckBox: Verified that the \"Browsing history\" check box is not checked") clickCookiesCheckBox() + Log.i(TAG, "selectOnlyOpenTabsCheckBox: Trying to verify that the \"Cookies and site data\" check box is not checked") cookiesAndSiteDataCheckBox().assertIsChecked(false) + Log.i(TAG, "selectOnlyOpenTabsCheckBox: Verified that the \"Cookies and site data\" check box is not checked") clickCachedFilesCheckBox() + Log.i(TAG, "selectOnlyOpenTabsCheckBox: Trying to verify that the \"Cached images and files\" check box is not checked") cachedFilesCheckBox().assertIsChecked(false) + Log.i(TAG, "selectOnlyOpenTabsCheckBox: Verified that the \"Cached images and files\" check box is not checked") clickSitePermissionsCheckBox() + Log.i(TAG, "selectOnlyOpenTabsCheckBox: Trying to verify that the \"Site permissions\" check box is not checked") sitePermissionsCheckBox().assertIsChecked(false) + Log.i(TAG, "selectOnlyOpenTabsCheckBox: Verified that the \"Site permissions\" check box is not checked") clickDownloadsCheckBox() + Log.i(TAG, "selectOnlyOpenTabsCheckBox: Trying to verify that the \"Downloads\" check box is not checked") downloadsCheckBox().assertIsChecked(false) - + Log.i(TAG, "selectOnlyOpenTabsCheckBox: Verified that the \"Downloads\" check box is not checked") + Log.i(TAG, "selectOnlyOpenTabsCheckBox: Trying to verify that the \"Open tabs\" check box is checked") openTabsCheckBox().assertIsChecked(true) + Log.i(TAG, "selectOnlyOpenTabsCheckBox: Trying to verify that the \"Open tabs\" check box is checked") } fun selectOnlyBrowsingHistoryCheckBox() { clickOpenTabsCheckBox() + Log.i(TAG, "selectOnlyBrowsingHistoryCheckBox: Trying to verify that the \"Open tabs\" check box is not checked") openTabsCheckBox().assertIsChecked(false) + Log.i(TAG, "selectOnlyBrowsingHistoryCheckBox: Verified that the \"Open tabs\" check box is not checked") clickCookiesCheckBox() + Log.i(TAG, "selectOnlyBrowsingHistoryCheckBox: Trying to verify that the \"Cookies and site data\" check box is not checked") cookiesAndSiteDataCheckBox().assertIsChecked(false) + Log.i(TAG, "selectOnlyBrowsingHistoryCheckBox: Verified that the \"Cookies and site data\" check box is not checked") clickCachedFilesCheckBox() + Log.i(TAG, "selectOnlyBrowsingHistoryCheckBox: Trying to verify that the \"Cached images and files\" check box is not checked") cachedFilesCheckBox().assertIsChecked(false) + Log.i(TAG, "selectOnlyBrowsingHistoryCheckBox: Verified that the \"Cached images and files\" check box is not checked") clickSitePermissionsCheckBox() + Log.i(TAG, "selectOnlyBrowsingHistoryCheckBox: Trying to verify that the \"Site permissions\" check box is not checked") sitePermissionsCheckBox().assertIsChecked(false) + Log.i(TAG, "selectOnlyBrowsingHistoryCheckBox: Verified that the \"Site permissions\" check box is not checked") clickDownloadsCheckBox() + Log.i(TAG, "selectOnlyBrowsingHistoryCheckBox: Trying to verify that the \"Downloads\" check box is not checked") downloadsCheckBox().assertIsChecked(false) + Log.i(TAG, "selectOnlyBrowsingHistoryCheckBox: Verified that the \"Downloads\" check box is not checked") + Log.i(TAG, "selectOnlyBrowsingHistoryCheckBox: Trying to verify that the \"Browsing history\" check box is checked") browsingHistoryCheckBox().assertIsChecked(true) + Log.i(TAG, "selectOnlyBrowsingHistoryCheckBox: Verified that the \"Browsing history\" check box is checked") } fun selectOnlyCookiesCheckBox() { clickOpenTabsCheckBox() + Log.i(TAG, "selectOnlyCookiesCheckBox: Trying to verify that the \"Open tabs\" check box is not checked") openTabsCheckBox().assertIsChecked(false) - + Log.i(TAG, "selectOnlyCookiesCheckBox: Verified that the \"Open tabs\" check box is not checked") + Log.i(TAG, "selectOnlyCookiesCheckBox: Trying to verify that the \"Cookies and site data\" check box is checked") cookiesAndSiteDataCheckBox().assertIsChecked(true) + Log.i(TAG, "selectOnlyCookiesCheckBox: Verified that the \"Cookies and site data\" check box is checked") clickCachedFilesCheckBox() + Log.i(TAG, "selectOnlyCookiesCheckBox: Trying to verify that the \"Cached images and files\" check box is not checked") cachedFilesCheckBox().assertIsChecked(false) + Log.i(TAG, "selectOnlyCookiesCheckBox: Verified that the \"Cached images and files\" check box is not checked") clickSitePermissionsCheckBox() + Log.i(TAG, "selectOnlyCookiesCheckBox: Trying to verify that the \"Site permissions\" check box is not checked") sitePermissionsCheckBox().assertIsChecked(false) + Log.i(TAG, "selectOnlyCookiesCheckBox: Verified that the \"Site permissions\" check box is not checked") clickDownloadsCheckBox() + Log.i(TAG, "selectOnlyCookiesCheckBox: Trying to verify that the \"Downloads\" check box is not checked") downloadsCheckBox().assertIsChecked(false) + Log.i(TAG, "selectOnlyCookiesCheckBox: Verified that the \"Downloads\" check box is not checked") clickBrowsingHistoryCheckBox() + Log.i(TAG, "selectOnlyCookiesCheckBox: Trying to verify that the \"Browsing history\" check box is not checked") browsingHistoryCheckBox().assertIsChecked(false) + Log.i(TAG, "selectOnlyCookiesCheckBox: Verified that the \"Browsing history\" check box is not checked") } fun selectOnlyCachedFilesCheckBox() { clickOpenTabsCheckBox() + Log.i(TAG, "selectOnlyCachedFilesCheckBox: Trying to verify that the \"Open tabs\" check box is not checked") openTabsCheckBox().assertIsChecked(false) + Log.i(TAG, "selectOnlyCachedFilesCheckBox: Verified that the \"Open tabs\" check box is not checked") clickBrowsingHistoryCheckBox() + Log.i(TAG, "selectOnlyCachedFilesCheckBox: Trying to verify that the \"Browsing history\" check box is not checked") browsingHistoryCheckBox().assertIsChecked(false) + Log.i(TAG, "selectOnlyCachedFilesCheckBox: Verified that the \"Browsing history\" check box is not checked") clickCookiesCheckBox() + Log.i(TAG, "selectOnlyCachedFilesCheckBox: Trying to verify that the \"Cookies and site data\" check box is not checked") cookiesAndSiteDataCheckBox().assertIsChecked(false) - + Log.i(TAG, "selectOnlyCachedFilesCheckBox: Verified that the \"Cookies and site data\" check box is not checked") + Log.i(TAG, "selectOnlyCachedFilesCheckBox: Trying to verify that the \"Cached images and files\" check box is checked") cachedFilesCheckBox().assertIsChecked(true) + Log.i(TAG, "selectOnlyCachedFilesCheckBox: Verified that the \"Cached images and files\" check box is checked") clickSitePermissionsCheckBox() + Log.i(TAG, "selectOnlyCachedFilesCheckBox: Trying to verify that the \"Site permissions\" check box is not checked") sitePermissionsCheckBox().assertIsChecked(false) + Log.i(TAG, "selectOnlyCachedFilesCheckBox: Verified that the \"Site permissions\" check box is not checked") clickDownloadsCheckBox() + Log.i(TAG, "selectOnlyCachedFilesCheckBox: Trying to verify that the \"Downloads\" check box is not checked") downloadsCheckBox().assertIsChecked(false) + Log.i(TAG, "selectOnlyCachedFilesCheckBox: Verified that the \"Downloads\" check box is not checked") } fun confirmDeletionAndAssertSnackbar() { + Log.i(TAG, "confirmDeletionAndAssertSnackbar: Trying to click the delete browsing data dialog \"Delete\" button") dialogDeleteButton().click() + Log.i(TAG, "confirmDeletionAndAssertSnackbar: Clicked the delete browsing data dialog \"Delete\" button") assertDeleteBrowsingDataSnackbar() } class Transition { fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition { + Log.i(TAG, "goBack: Trying to click navigate up toolbar button") goBackButton().click() + Log.i(TAG, "goBack: Clicked the navigate up toolbar button") SettingsRobot().interact() return SettingsRobot.Transition() @@ -187,9 +292,33 @@ private fun dialogMessage() = .inRoot(isDialog()) private fun assertDeleteBrowsingDataSnackbar() = assertUIObjectIsGone(itemWithText("Browsing data deleted")) -private fun clickOpenTabsCheckBox() = openTabsCheckBox().click() -private fun clickBrowsingHistoryCheckBox() = browsingHistoryCheckBox().click() -private fun clickCookiesCheckBox() = cookiesAndSiteDataCheckBox().click() -private fun clickCachedFilesCheckBox() = cachedFilesCheckBox().click() -private fun clickSitePermissionsCheckBox() = sitePermissionsCheckBox().click() -private fun clickDownloadsCheckBox() = downloadsCheckBox().click() +private fun clickOpenTabsCheckBox() { + Log.i(TAG, "clickOpenTabsCheckBox: Trying to click the \"Open tabs\" check box") + openTabsCheckBox().click() + Log.i(TAG, "clickOpenTabsCheckBox: Clicked the \"Open tabs\" check box") +} +private fun clickBrowsingHistoryCheckBox() { + Log.i(TAG, "clickBrowsingHistoryCheckBox: Trying to click the \"Browsing history\" check box") + browsingHistoryCheckBox().click() + Log.i(TAG, "clickBrowsingHistoryCheckBox: Clicked the \"Browsing history\" check box") +} +private fun clickCookiesCheckBox() { + Log.i(TAG, "clickCookiesCheckBox: Trying to click the \"Cookies and site data\" check box") + cookiesAndSiteDataCheckBox().click() + Log.i(TAG, "clickCookiesCheckBox: Clicked the \"Cookies and site data\" check box") +} +private fun clickCachedFilesCheckBox() { + Log.i(TAG, "clickCachedFilesCheckBox: Trying to click the \"Cached images and files\" check box") + cachedFilesCheckBox().click() + Log.i(TAG, "clickCachedFilesCheckBox: Clicked the \"Cached images and files\" check box") +} +private fun clickSitePermissionsCheckBox() { + Log.i(TAG, "clickSitePermissionsCheckBox: Trying to click the \"Site permissions\" check box") + sitePermissionsCheckBox().click() + Log.i(TAG, "clickSitePermissionsCheckBox: Clicked the \"Site permissions\" check box") +} +private fun clickDownloadsCheckBox() { + Log.i(TAG, "clickDownloadsCheckBox: Trying to click the \"Downloads\" check box") + downloadsCheckBox().click() + Log.i(TAG, "clickDownloadsCheckBox: Clicked the \"Downloads\" check box") +} From 0505682776439603ecbedfa2cbd0e661b9a977bb Mon Sep 17 00:00:00 2001 From: iorgamgabriel Date: Mon, 22 Jan 2024 17:13:54 +0200 Subject: [PATCH 015/238] Bug 1856979 - Translations UI Post-Translate Popup --- .../mozilla/fenix/browser/BrowserFragment.kt | 23 + .../translations/TranslationsBottomSheet.kt | 31 +- .../translations/TranslationsDialogBinding.kt | 142 +++++ .../TranslationsDialogBottomSheet.kt | 552 ++++++++++++------ .../TranslationsDialogFragment.kt | 192 +++--- .../TranslationsDialogMiddleware.kt | 62 ++ .../translations/TranslationsDialogStore.kt | 285 +++++++++ app/src/main/res/values/strings.xml | 8 + .../TranslationsDialogBindingTest.kt | 308 ++++++++++ .../TranslationsDialogMiddlewareTest.kt | 101 ++++ .../TranslationsDialogReducerTest.kt | 229 ++++++++ 11 files changed, 1663 insertions(+), 270 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogBinding.kt create mode 100644 app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogMiddleware.kt create mode 100644 app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogStore.kt create mode 100644 app/src/test/java/org/mozilla/fenix/translations/TranslationsDialogBindingTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/translations/TranslationsDialogMiddlewareTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/translations/TranslationsDialogReducerTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt index 5248f0773..0d37c0847 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt @@ -11,6 +11,7 @@ import android.view.ViewGroup import androidx.annotation.VisibleForTesting import androidx.appcompat.content.res.AppCompatResources import androidx.core.content.ContextCompat +import androidx.fragment.app.setFragmentResultListener import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController @@ -52,6 +53,8 @@ import org.mozilla.fenix.shopping.DefaultShoppingExperienceFeature import org.mozilla.fenix.shopping.ReviewQualityCheckFeature import org.mozilla.fenix.shortcut.PwaOnboardingObserver import org.mozilla.fenix.theme.ThemeManager +import org.mozilla.fenix.translations.TranslationsDialogFragment.Companion.SESSION_ID +import org.mozilla.fenix.translations.TranslationsDialogFragment.Companion.TRANSLATION_IN_PROGRESS /** * Fragment used for browsing the web within the main app. @@ -211,6 +214,26 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { owner = viewLifecycleOwner, view = binding.root, ) + + setTranslationFragmentResultListener() + } + + private fun setTranslationFragmentResultListener() { + setFragmentResultListener( + TRANSLATION_IN_PROGRESS, + ) { _, result -> + result.getString(SESSION_ID)?.let { + if (it == getCurrentTab()?.id) { + FenixSnackbar.make( + view = binding.dynamicSnackbarContainer, + duration = Snackbar.LENGTH_LONG, + isDisplayedWithBrowserToolbar = true, + ) + .setText(requireContext().getString(R.string.translation_in_progress_snackbar)) + .show() + } + } + } } private fun initTranslationsAction(context: Context) { diff --git a/app/src/main/java/org/mozilla/fenix/translations/TranslationsBottomSheet.kt b/app/src/main/java/org/mozilla/fenix/translations/TranslationsBottomSheet.kt index efab01c24..98c341ac3 100644 --- a/app/src/main/java/org/mozilla/fenix/translations/TranslationsBottomSheet.kt +++ b/app/src/main/java/org/mozilla/fenix/translations/TranslationsBottomSheet.kt @@ -27,7 +27,6 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import mozilla.components.concept.engine.translate.Language -import mozilla.components.concept.engine.translate.TranslationError import org.mozilla.fenix.theme.FirefoxTheme private const val BOTTOM_SHEET_HANDLE_WIDTH_PERCENT = 0.1f @@ -127,37 +126,29 @@ internal fun TranslationsOptionsAnimation( } } -@Composable @Suppress("LongParameterList") +@Composable internal fun TranslationsDialog( + translationsDialogState: TranslationsDialogState, learnMoreUrl: String, - showFirstTimeTranslation: Boolean, - translateFromLanguages: List?, - translateToLanguages: List?, - initialFrom: Language? = null, - initialTo: Language? = null, - translationError: TranslationError? = null, + showFirstTime: Boolean = false, onSettingClicked: () -> Unit, onLearnMoreClicked: () -> Unit, - onTranslateButtonClick: () -> Unit, - onNotNowButtonClick: () -> Unit, + onPositiveButtonClicked: () -> Unit, + onNegativeButtonClicked: () -> Unit, onFromSelected: (Language) -> Unit, onToSelected: (Language) -> Unit, ) { TranslationsDialogBottomSheet( + translationsDialogState = translationsDialogState, learnMoreUrl = learnMoreUrl, - showFirstTimeTranslation = showFirstTimeTranslation, - translationError = translationError, - translateFromLanguages = translateFromLanguages, - translateToLanguages = translateToLanguages, - initialFrom = initialFrom, - initialTo = initialTo, + showFirstTimeFlow = showFirstTime, onSettingClicked = onSettingClicked, onLearnMoreClicked = onLearnMoreClicked, - onTranslateButtonClicked = onTranslateButtonClick, - onNotNowButtonClicked = onNotNowButtonClick, - onFromSelected = onFromSelected, - onToSelected = onToSelected, + onPositiveButtonClicked = onPositiveButtonClicked, + onNegativeButtonClicked = onNegativeButtonClicked, + onFromDropdownSelected = onFromSelected, + onToDropdownSelected = onToSelected, ) } diff --git a/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogBinding.kt b/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogBinding.kt new file mode 100644 index 000000000..3d1e83819 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogBinding.kt @@ -0,0 +1,142 @@ +/* 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/. */ + +package org.mozilla.fenix.translations + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChangedBy +import kotlinx.coroutines.flow.mapNotNull +import mozilla.components.browser.state.selector.findTab +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.concept.engine.translate.initialFromLanguage +import mozilla.components.concept.engine.translate.initialToLanguage +import mozilla.components.lib.state.helpers.AbstractBinding + +/** + * Helper for observing Translation state from [BrowserStore]. + */ +class TranslationsDialogBinding( + browserStore: BrowserStore, + private val translationsDialogStore: TranslationsDialogStore, + private val sessionId: String, + private val getTranslatedPageTitle: (localizedFrom: String?, localizedTo: String?) -> String, +) : AbstractBinding(browserStore) { + + override suspend fun onState(flow: Flow) { + flow.mapNotNull { state -> state.findTab(sessionId) } + .distinctUntilChangedBy { + it.translationsState + } + .collect { sessionState -> + val translationsState = sessionState.translationsState + + val fromSelected = + translationsState.translationEngineState?.initialFromLanguage( + translationsState.supportedLanguages?.fromLanguages, + ) + + fromSelected?.let { + translationsDialogStore.dispatch( + TranslationsDialogAction.UpdateFromSelectedLanguage( + fromSelected, + ), + ) + } + + val toSelected = + translationsState.translationEngineState?.initialToLanguage( + translationsState.supportedLanguages?.toLanguages, + ) + toSelected?.let { + translationsDialogStore.dispatch( + TranslationsDialogAction.UpdateToSelectedLanguage( + toSelected, + ), + ) + } + + if ( + toSelected != null && fromSelected != null && + translationsDialogStore.state.translatedPageTitle == null + ) { + translationsDialogStore.dispatch( + TranslationsDialogAction.UpdateTranslatedPageTitle( + getTranslatedPageTitle( + fromSelected.localizedDisplayName, + toSelected.localizedDisplayName, + ), + ), + ) + } + + val translateFromLanguages = translationsState.supportedLanguages?.fromLanguages + translateFromLanguages?.let { + translationsDialogStore.dispatch( + TranslationsDialogAction.UpdateTranslateFromLanguages( + translateFromLanguages, + ), + ) + } + + val translateToLanguages = translationsState.supportedLanguages?.toLanguages + translateToLanguages?.let { + translationsDialogStore.dispatch( + TranslationsDialogAction.UpdateTranslateToLanguages( + translateToLanguages, + ), + ) + } + + if (translationsState.isTranslateProcessing) { + updateStoreIfIsTranslateProcessing() + } + + if (translationsState.isTranslated && !translationsState.isTranslateProcessing) { + updateStoreIfTranslated() + } + + if (translationsState.translationError != null) { + translationsDialogStore.dispatch( + TranslationsDialogAction.UpdateTranslationError(translationsState.translationError), + ) + } + } + } + + private fun updateStoreIfIsTranslateProcessing() { + translationsDialogStore.dispatch( + TranslationsDialogAction.UpdateTranslationInProgress( + true, + ), + ) + translationsDialogStore.dispatch( + TranslationsDialogAction.DismissDialog( + dismissDialogState = DismissDialogState.WaitingToBeDismissed, + ), + ) + } + + private fun updateStoreIfTranslated() { + translationsDialogStore.dispatch( + TranslationsDialogAction.UpdateTranslationInProgress( + false, + ), + ) + + translationsDialogStore.dispatch( + TranslationsDialogAction.UpdateTranslated( + true, + ), + ) + + if (translationsDialogStore.state.dismissDialogState == DismissDialogState.WaitingToBeDismissed) { + translationsDialogStore.dispatch( + TranslationsDialogAction.DismissDialog( + dismissDialogState = DismissDialogState.Dismiss, + ), + ) + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogBottomSheet.kt b/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogBottomSheet.kt index 18b1f11cf..13aa8a733 100644 --- a/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogBottomSheet.kt +++ b/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogBottomSheet.kt @@ -67,45 +67,29 @@ private val ICON_SIZE = 24.dp /** * Firefox Translations bottom sheet dialog. * + * @param translationsDialogState The current state of the Translations bottom sheet dialog. * @param learnMoreUrl The learn more link for translations website. - * @param showFirstTimeTranslation Whether translations first flow should be shown. - * @param translateFromLanguages Translation menu items to be shown in the translate from dropdown. - * @param translateToLanguages Translation menu items are to be shown in the translate to dropdown. - * @param initialFrom The initial selection for the translate from dropdown. - * @param initialTo The initial selection for the translate to dropdown. - * @param translationError The type of translation errors that can occur. + * @param showFirstTimeFlow Whether translations first flow should be shown. * @param onSettingClicked Invoked when the user clicks on the settings button. * @param onLearnMoreClicked Invoked when the user clicks on the "Learn More" button. - * @param onTranslateButtonClicked Invoked when the user clicks on the "Translate" button. - * @param onNotNowButtonClicked Invoked when the user clicks on the "Not Now" button. - * @param onFromSelected Invoked when the user selects an item on the from dropdown. - * @param onToSelected Invoked when the user selects an item on the to dropdown. + * @param onPositiveButtonClicked Invoked when the user clicks on the positive button. + * @param onNegativeButtonClicked Invoked when the user clicks on the negative button. + * @param onFromDropdownSelected Invoked when the user selects an item on the from dropdown. + * @param onToDropdownSelected Invoked when the user selects an item on the to dropdown. */ -@Composable @Suppress("LongParameterList") +@Composable fun TranslationsDialogBottomSheet( + translationsDialogState: TranslationsDialogState, learnMoreUrl: String, - showFirstTimeTranslation: Boolean, - translateFromLanguages: List?, - translateToLanguages: List?, - initialFrom: Language? = null, - initialTo: Language? = null, - translationError: TranslationError? = null, + showFirstTimeFlow: Boolean = false, onSettingClicked: () -> Unit, onLearnMoreClicked: () -> Unit, - onTranslateButtonClicked: () -> Unit, - onNotNowButtonClicked: () -> Unit, - onFromSelected: (Language) -> Unit, - onToSelected: (Language) -> Unit, + onPositiveButtonClicked: () -> Unit, + onNegativeButtonClicked: () -> Unit, + onFromDropdownSelected: (Language) -> Unit, + onToDropdownSelected: (Language) -> Unit, ) { - var orientation by remember { mutableIntStateOf(Configuration.ORIENTATION_PORTRAIT) } - - val configuration = LocalConfiguration.current - - LaunchedEffect(configuration) { - snapshotFlow { configuration.orientation }.collect { orientation = it } - } - Column( modifier = Modifier.padding(16.dp), ) { @@ -116,142 +100,347 @@ fun TranslationsDialogBottomSheet( ) TranslationsDialogHeader( - showFirstTimeTranslation = showFirstTimeTranslation, + title = if ( + translationsDialogState.isTranslated && translationsDialogState.translatedPageTitle != null + ) { + translationsDialogState.translatedPageTitle + } else { + getTranslationsDialogTitle( + showFirstTime = showFirstTimeFlow, + ) + }, onSettingClicked = onSettingClicked, ) Spacer(modifier = Modifier.height(8.dp)) - if (showFirstTimeTranslation) { + if (showFirstTimeFlow) { TranslationsDialogInfoMessage( learnMoreUrl = learnMoreUrl, onLearnMoreClicked = onLearnMoreClicked, ) } - translationError?.let { - TranslationErrorWarning(translationError) - } + DialogContentBaseOnTranslationState( + translationsDialogState = translationsDialogState, + learnMoreUrl = learnMoreUrl, + onLearnMoreClicked = onLearnMoreClicked, + onPositiveButtonClicked = onPositiveButtonClicked, + onNegativeButtonClicked = onNegativeButtonClicked, + onFromDropdownSelected = onFromDropdownSelected, + onToDropdownSelected = onToDropdownSelected, + ) + } +} +/** + * Dialog content will adapt based on the [TranslationsDialogState]. + * + * @param translationsDialogState The current state of the Translations bottom sheet dialog. + * @param learnMoreUrl The learn more link for translations website. + * @param onLearnMoreClicked Invoked when the user clicks on the learn more button. + * @param onPositiveButtonClicked Invoked when the user clicks on the positive button. + * @param onNegativeButtonClicked Invoked when the user clicks on the negative button. + * @param onFromDropdownSelected Invoked when the user selects an item on the from dropdown. + * @param onToDropdownSelected Invoked when the user selects an item on the to dropdown. + */ +@Composable +private fun DialogContentBaseOnTranslationState( + translationsDialogState: TranslationsDialogState, + learnMoreUrl: String, + onLearnMoreClicked: () -> Unit, + onPositiveButtonClicked: () -> Unit, + onNegativeButtonClicked: () -> Unit, + onFromDropdownSelected: (Language) -> Unit, + onToDropdownSelected: (Language) -> Unit, +) { + if (translationsDialogState.error != null) { + DialogContentAnErrorOccurred( + error = translationsDialogState.error, + learnMoreUrl = learnMoreUrl, + onLearnMoreClicked = onLearnMoreClicked, + fromLanguages = translationsDialogState.fromLanguages, + toLanguages = translationsDialogState.toLanguages, + initialFrom = translationsDialogState.initialFrom, + initialTo = translationsDialogState.initialTo, + onFromDropdownSelected = onFromDropdownSelected, + onToDropdownSelected = onToDropdownSelected, + onPositiveButtonClicked = onPositiveButtonClicked, + onNegativeButtonClicked = onNegativeButtonClicked, + ) + } else if (translationsDialogState.isTranslated) { + DialogContentTranslated( + translateToLanguages = translationsDialogState.toLanguages, + onFromDropdownSelected = onFromDropdownSelected, + onToDropdownSelected = onToDropdownSelected, + onPositiveButtonClicked = onPositiveButtonClicked, + onNegativeButtonClicked = onNegativeButtonClicked, + positiveButtonType = translationsDialogState.positiveButtonType, + initialTo = translationsDialogState.initialTo, + ) + } else { Spacer(modifier = Modifier.height(14.dp)) - if (translationError !is TranslationError.CouldNotLoadLanguagesError && - translateFromLanguages != null && translateToLanguages != null - ) { - when (orientation) { - Configuration.ORIENTATION_LANDSCAPE -> { - TranslationsDialogContentInLandscapeMode( - translateFromLanguages = translateFromLanguages, - translateToLanguages = translateToLanguages, - initialFrom = initialFrom, - initialTo = initialTo, - onFromSelected = onFromSelected, - onToSelected = onToSelected, - ) - } - - else -> { - TranslationsDialogContentInPortraitMode( - translateFromLanguages = translateFromLanguages, - translateToLanguages = translateToLanguages, - initialFrom = initialFrom, - initialTo = initialTo, - onFromSelected = onFromSelected, - onToSelected = onToSelected, - ) - } - } + TranslationsDialogContent( + translateFromLanguages = translationsDialogState.fromLanguages, + translateToLanguages = translationsDialogState.toLanguages, + initialFrom = translationsDialogState.initialFrom, + initialTo = translationsDialogState.initialTo, + onFromDropdownSelected = onFromDropdownSelected, + onToDropdownSelected = onToDropdownSelected, + ) - Spacer(modifier = Modifier.height(16.dp)) - } + Spacer(modifier = Modifier.height(16.dp)) TranslationsDialogActionButtons( - translationError = translationError, - onTranslateButtonClicked = onTranslateButtonClicked, - onNotNowButtonClicked = onNotNowButtonClicked, + positiveButtonText = stringResource(id = R.string.translations_bottom_sheet_positive_button), + negativeButtonText = stringResource(id = R.string.translations_bottom_sheet_negative_button), + positiveButtonType = translationsDialogState.positiveButtonType, + onNegativeButtonClicked = onNegativeButtonClicked, + onPositiveButtonClicked = onPositiveButtonClicked, ) } } +/** + * Dialog content if the web page was translated. + * + * @param translateToLanguages Translation menu items to be shown in the translate to dropdown. + * @param onFromDropdownSelected Invoked when the user selects an item on the from dropdown. + * @param onToDropdownSelected Invoked when the user selects an item on the to dropdown. + * @param onPositiveButtonClicked Invoked when the user clicks on the positive button. + * @param onNegativeButtonClicked Invoked when the user clicks on the negative button. + * @param positiveButtonType Can be enabled,disabled or in progress. If it is null, the button will be disabled. + * @param initialTo Initial "to" language, based on the translation state and page state. + */ @Composable -private fun TranslationsDialogContentInPortraitMode( - translateFromLanguages: List, - translateToLanguages: List, - initialFrom: Language? = null, +private fun DialogContentTranslated( + translateToLanguages: List?, + onFromDropdownSelected: (Language) -> Unit, + onToDropdownSelected: (Language) -> Unit, + onPositiveButtonClicked: () -> Unit, + onNegativeButtonClicked: () -> Unit, + positiveButtonType: PositiveButtonType? = null, initialTo: Language? = null, - onFromSelected: (Language) -> Unit, - onToSelected: (Language) -> Unit, +) { + Spacer(modifier = Modifier.height(14.dp)) + + TranslationsDialogContent( + translateToLanguages = translateToLanguages, + initialTo = initialTo, + onFromDropdownSelected = onFromDropdownSelected, + onToDropdownSelected = onToDropdownSelected, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + TranslationsDialogActionButtons( + positiveButtonText = stringResource(id = R.string.translations_bottom_sheet_positive_button), + negativeButtonText = stringResource(id = R.string.translations_bottom_sheet_negative_button_restore), + positiveButtonType = positiveButtonType, + onPositiveButtonClicked = onPositiveButtonClicked, + onNegativeButtonClicked = onNegativeButtonClicked, + ) +} +/** + * Dialog content if an [TranslationError] appears during the translation process. + * + * @param error An error that can occur during the translation process. + * @param learnMoreUrl The learn more link for translations website. + * @param onLearnMoreClicked Invoked when the user clicks on the learn more button. + * @param fromLanguages Translation menu items to be shown in the translate from dropdown. + * @param toLanguages Translation menu items to be shown in the translate to dropdown. + * @param initialFrom Initial "from" language, based on the translation state and page state. + * @param initialTo Initial "to" language, based on the translation state and page state. + * @param onFromDropdownSelected Invoked when the user selects an item on the from dropdown. + * @param onToDropdownSelected Invoked when the user selects an item on the to dropdown. + * @param onPositiveButtonClicked Invoked when the user clicks on the positive button. + * @param onNegativeButtonClicked Invoked when the user clicks on the negative button. + */ +@Suppress("LongParameterList") +@Composable +private fun DialogContentAnErrorOccurred( + error: TranslationError, + learnMoreUrl: String, + onLearnMoreClicked: () -> Unit, + fromLanguages: List?, + toLanguages: List?, + initialFrom: Language? = null, + initialTo: Language? = null, + onFromDropdownSelected: (Language) -> Unit, + onToDropdownSelected: (Language) -> Unit, + onPositiveButtonClicked: () -> Unit, + onNegativeButtonClicked: () -> Unit, ) { - Column { - TranslationsDropdown( - header = stringResource(id = R.string.translations_bottom_sheet_translate_from), - modifier = Modifier.fillMaxWidth(), - translateLanguages = translateFromLanguages, - initiallySelected = initialFrom, - onLanguageSelection = onFromSelected, - ) + TranslationErrorWarning( + error, + learnMoreUrl = learnMoreUrl, + onLearnMoreClicked = onLearnMoreClicked, + ) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(14.dp)) - TranslationsDropdown( - header = stringResource(id = R.string.translations_bottom_sheet_translate_to), - modifier = Modifier.fillMaxWidth(), - translateLanguages = translateToLanguages, - initiallySelected = initialTo, - onLanguageSelection = onToSelected, + if (error !is TranslationError.CouldNotLoadLanguagesError) { + TranslationsDialogContent( + translateFromLanguages = fromLanguages, + translateToLanguages = toLanguages, + initialFrom = initialFrom, + initialTo = initialTo, + onFromDropdownSelected = onFromDropdownSelected, + onToDropdownSelected = onToDropdownSelected, ) } + + val negativeButtonTitle = if (error is TranslationError.LanguageNotSupportedError) { + stringResource(id = R.string.translations_bottom_sheet_negative_button_error) + } else { + stringResource(id = R.string.translations_bottom_sheet_negative_button) + } + + val positiveButtonTitle = if (error is TranslationError.CouldNotLoadLanguagesError) { + stringResource(id = R.string.translations_bottom_sheet_positive_button_error) + } else { + stringResource(id = R.string.translations_bottom_sheet_positive_button) + } + + val positiveButtonType = if (error is TranslationError.LanguageNotSupportedError) { + PositiveButtonType.Disabled + } else { + PositiveButtonType.Enabled + } + + TranslationsDialogActionButtons( + positiveButtonText = positiveButtonTitle, + negativeButtonText = negativeButtonTitle, + positiveButtonType = positiveButtonType, + onNegativeButtonClicked = onNegativeButtonClicked, + onPositiveButtonClicked = onPositiveButtonClicked, + ) } @Composable -private fun TranslationsDialogContentInLandscapeMode( - translateFromLanguages: List, - translateToLanguages: List, +private fun TranslationsDialogContent( + translateFromLanguages: List? = null, + translateToLanguages: List? = null, + initialFrom: Language? = null, + initialTo: Language? = null, + onFromDropdownSelected: (Language) -> Unit, + onToDropdownSelected: (Language) -> Unit, +) { + var orientation by remember { mutableIntStateOf(Configuration.ORIENTATION_PORTRAIT) } + + val configuration = LocalConfiguration.current + + LaunchedEffect(configuration) { + snapshotFlow { configuration.orientation }.collect { orientation = it } + } + when (orientation) { + Configuration.ORIENTATION_LANDSCAPE -> { + TranslationsDialogContentInLandscapeMode( + translateFromLanguages = translateFromLanguages, + translateToLanguages = translateToLanguages, + initialFrom = initialFrom, + initialTo = initialTo, + onFromDropdownSelected = onFromDropdownSelected, + onToDropdownSelected = onToDropdownSelected, + ) + } + + else -> { + TranslationsDialogContentInPortraitMode( + translateFromLanguages = translateFromLanguages, + translateToLanguages = translateToLanguages, + initialFrom = initialFrom, + initialTo = initialTo, + onFromDropdownSelected = onFromDropdownSelected, + onToDropdownSelected = onToDropdownSelected, + ) + } + } + + Spacer(modifier = Modifier.height(16.dp)) +} + +@Composable +private fun TranslationsDialogContentInPortraitMode( + translateFromLanguages: List? = null, + translateToLanguages: List? = null, initialFrom: Language? = null, initialTo: Language? = null, - onFromSelected: (Language) -> Unit, - onToSelected: (Language) -> Unit, + onFromDropdownSelected: (Language) -> Unit, + onToDropdownSelected: (Language) -> Unit, ) { Column { - Row { + translateFromLanguages?.let { TranslationsDropdown( header = stringResource(id = R.string.translations_bottom_sheet_translate_from), - modifier = Modifier.weight(1f), - isInLandscapeMode = true, + modifier = Modifier.fillMaxWidth(), + isInLandscapeMode = false, translateLanguages = translateFromLanguages, initiallySelected = initialFrom, - onLanguageSelection = onFromSelected, + onLanguageSelection = onFromDropdownSelected, ) - Spacer(modifier = Modifier.width(16.dp)) + Spacer(modifier = Modifier.height(16.dp)) + } + translateToLanguages?.let { TranslationsDropdown( header = stringResource(id = R.string.translations_bottom_sheet_translate_to), - modifier = Modifier.weight(1f), - isInLandscapeMode = true, - translateLanguages = translateToLanguages, + modifier = Modifier.fillMaxWidth(), + isInLandscapeMode = false, + translateLanguages = it, initiallySelected = initialTo, - onLanguageSelection = onToSelected, + onLanguageSelection = onToDropdownSelected, ) } } } @Composable -private fun TranslationsDialogHeader( - showFirstTimeTranslation: Boolean, - onSettingClicked: () -> Unit, +private fun TranslationsDialogContentInLandscapeMode( + translateFromLanguages: List? = null, + translateToLanguages: List? = null, + initialFrom: Language? = null, + initialTo: Language? = null, + onFromDropdownSelected: (Language) -> Unit, + onToDropdownSelected: (Language) -> Unit, ) { - val title: String = if (showFirstTimeTranslation) { - stringResource( - id = R.string.translations_bottom_sheet_title_first_time, - stringResource(id = R.string.firefox), - ) - } else { - stringResource(id = R.string.translations_bottom_sheet_title) + Column { + Row { + translateFromLanguages?.let { + TranslationsDropdown( + header = stringResource(id = R.string.translations_bottom_sheet_translate_from), + modifier = Modifier.weight(1f), + isInLandscapeMode = true, + translateLanguages = translateFromLanguages, + initiallySelected = initialFrom, + onLanguageSelection = onFromDropdownSelected, + ) + + Spacer(modifier = Modifier.width(16.dp)) + } + + translateToLanguages?.let { + TranslationsDropdown( + header = stringResource(id = R.string.translations_bottom_sheet_translate_to), + modifier = Modifier.weight(1f), + isInLandscapeMode = true, + translateLanguages = it, + initiallySelected = initialTo, + onLanguageSelection = onToDropdownSelected, + ) + } + } } +} +@Composable +private fun TranslationsDialogHeader( + title: String, + onSettingClicked: () -> Unit, +) { Row( verticalAlignment = Alignment.CenterVertically, ) { @@ -280,7 +469,11 @@ private fun TranslationsDialogHeader( } @Composable -private fun TranslationErrorWarning(translationError: TranslationError) { +private fun TranslationErrorWarning( + translationError: TranslationError, + learnMoreUrl: String, + onLearnMoreClicked: () -> Unit, +) { val modifier = Modifier .padding(top = 8.dp) .fillMaxWidth() @@ -303,6 +496,9 @@ private fun TranslationErrorWarning(translationError: TranslationError) { } is TranslationError.LanguageNotSupportedError -> { + val learnMoreText = + stringResource(id = R.string.translation_error_language_not_supported_learn_more) + ReviewQualityCheckInfoCard( title = stringResource( id = R.string.translation_error_language_not_supported_warning_text, @@ -313,9 +509,9 @@ private fun TranslationErrorWarning(translationError: TranslationError) { footer = stringResource( id = R.string.translation_error_language_not_supported_learn_more, ) to LinkTextState( - text = stringResource(id = R.string.translation_error_language_not_supported_learn_more), - url = "https://www.mozilla.org", - onClick = {}, + text = learnMoreText, + url = learnMoreUrl, + onClick = { onLearnMoreClicked() }, ), ) } @@ -353,22 +549,13 @@ private fun TranslationsDialogInfoMessage( } } -/** - * Creates a dropdown with language selection to use to select languages for translation. - * - * @param header The title of the dropdown. - * @param translateLanguages The language choices the dropdown should provide. - * @param modifier Any modifiers for the component. - * @param isInLandscapeMode If the item should layout for landscape mode. - * @param initiallySelected The language initially selected, if null will show "Choose a language". - * @param onLanguageSelection Callback for the selected language. - */ +@Suppress("LongMethod") @Composable private fun TranslationsDropdown( header: String, translateLanguages: List, modifier: Modifier = Modifier, - isInLandscapeMode: Boolean = false, + isInLandscapeMode: Boolean, initiallySelected: Language? = null, onLanguageSelection: (Language) -> Unit, ) { @@ -396,7 +583,8 @@ private fun TranslationsDropdown( Spacer(modifier = Modifier.height(4.dp)) - var initialValue = stringResource(R.string.translations_bottom_sheet_default_dropdown_selection) + var initialValue = + stringResource(R.string.translations_bottom_sheet_default_dropdown_selection) initiallySelected?.localizedDisplayName?.let { initialValue = it } @@ -423,19 +611,21 @@ private fun TranslationsDropdown( onDismissRequest = { expanded = false }, - menuItems = getContextMenuItems( translateLanguages = translateLanguages, - onClickItem = onLanguageSelection, + selectedLanguage = initiallySelected, + onClickItem = { + onLanguageSelection(it) + }, ), - modifier = Modifier .onGloballyPositioned { coordinates -> contextMenuWidthDp = with(density) { coordinates.size.width.toDp() } } - .requiredSizeIn(maxHeight = 200.dp), + .requiredSizeIn(maxHeight = 200.dp) + .padding(horizontal = if (initiallySelected == null) 36.dp else 4.dp), offset = if (isInLandscapeMode) { DpOffset( -contextMenuWidthDp + ICON_SIZE, @@ -455,8 +645,21 @@ private fun TranslationsDropdown( } } +@Composable +private fun getTranslationsDialogTitle( + showFirstTime: Boolean = false, +) = if (showFirstTime) { + stringResource( + id = R.string.translations_bottom_sheet_title_first_time, + stringResource(id = R.string.firefox), + ) +} else { + stringResource(id = R.string.translations_bottom_sheet_title) +} + private fun getContextMenuItems( translateLanguages: List, + selectedLanguage: Language? = null, onClickItem: (Language) -> Unit, ): List { val menuItems = mutableListOf() @@ -465,6 +668,7 @@ private fun getContextMenuItems( menuItems.add( MenuItem( title = it, + isChecked = item == selectedLanguage, onClick = { onClickItem(item) }, @@ -477,64 +681,52 @@ private fun getContextMenuItems( @Composable private fun TranslationsDialogActionButtons( - translationError: TranslationError? = null, - onTranslateButtonClicked: () -> Unit, - onNotNowButtonClicked: () -> Unit, + positiveButtonText: String, + negativeButtonText: String, + positiveButtonType: PositiveButtonType? = null, + onPositiveButtonClicked: () -> Unit, + onNegativeButtonClicked: () -> Unit, ) { - val isTranslationInProgress = remember { mutableStateOf(false) } - Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically, ) { - val negativeButtonTitle = - if (translationError is TranslationError.LanguageNotSupportedError) { - stringResource(id = R.string.translations_bottom_sheet_negative_button_error) - } else { - stringResource(id = R.string.translations_bottom_sheet_negative_button) - } - TextButton( - text = negativeButtonTitle, + text = negativeButtonText, modifier = Modifier, - onClick = onNotNowButtonClicked, + onClick = onNegativeButtonClicked, ) Spacer(modifier = Modifier.width(10.dp)) - if (isTranslationInProgress.value) { - DownloadIndicator( - text = stringResource(id = R.string.translations_bottom_sheet_translating_in_progress), - contentDescription = stringResource( - id = R.string.translations_bottom_sheet_translating_in_progress_content_description, - ), - icon = painterResource(id = R.drawable.mozac_ic_sync_24), - ) - } else { - val positiveButtonTitle = - if (translationError is TranslationError.CouldNotLoadLanguagesError) { - stringResource(id = R.string.translations_bottom_sheet_positive_button_error) - } else { - stringResource(id = R.string.translations_bottom_sheet_positive_button) - } + when (positiveButtonType) { + PositiveButtonType.InProgress -> { + DownloadIndicator( + text = positiveButtonText, + contentDescription = stringResource( + id = R.string.translations_bottom_sheet_translating_in_progress_content_description, + ), + icon = painterResource(id = R.drawable.mozac_ic_sync_24), + ) + } - if (translationError is TranslationError.LanguageNotSupportedError) { - TertiaryButton( - text = positiveButtonTitle, - enabled = false, + PositiveButtonType.Enabled -> { + PrimaryButton( + text = positiveButtonText, modifier = Modifier.wrapContentSize(), ) { - isTranslationInProgress.value = true - onTranslateButtonClicked() + onPositiveButtonClicked() } - } else { - PrimaryButton( - text = positiveButtonTitle, + } + + else -> { + TertiaryButton( + text = positiveButtonText, + enabled = false, modifier = Modifier.wrapContentSize(), ) { - isTranslationInProgress.value = true - onTranslateButtonClicked() + onPositiveButtonClicked() } } } @@ -546,19 +738,19 @@ private fun TranslationsDialogActionButtons( private fun TranslationsDialogBottomSheetPreview() { FirefoxTheme { TranslationsDialogBottomSheet( + translationsDialogState = TranslationsDialogState( + positiveButtonType = PositiveButtonType.Enabled, + toLanguages = getTranslateToLanguageList(), + fromLanguages = getTranslateFromLanguageList(), + ), learnMoreUrl = "", - showFirstTimeTranslation = true, - translationError = TranslationError.LanguageNotSupportedError(null), - translateFromLanguages = getTranslateFromLanguageList(), - translateToLanguages = getTranslateToLanguageList(), - initialFrom = null, - initialTo = null, + showFirstTimeFlow = true, onSettingClicked = {}, onLearnMoreClicked = {}, - onTranslateButtonClicked = {}, - onNotNowButtonClicked = {}, - onToSelected = {}, - onFromSelected = {}, + onPositiveButtonClicked = {}, + onNegativeButtonClicked = {}, + onFromDropdownSelected = {}, + onToDropdownSelected = {}, ) } } diff --git a/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogFragment.kt index e94bc22ce..b28fcbb8e 100644 --- a/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogFragment.kt @@ -5,11 +5,13 @@ package org.mozilla.fenix.translations import android.app.Dialog +import android.content.DialogInterface import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -18,18 +20,17 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.unit.dp +import androidx.core.os.bundleOf +import androidx.fragment.app.setFragmentResult import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import mozilla.components.browser.state.action.TranslationsAction -import mozilla.components.browser.state.selector.findTab import mozilla.components.browser.state.store.BrowserStore -import mozilla.components.concept.engine.translate.TranslationOperation -import mozilla.components.concept.engine.translate.initialFromLanguage -import mozilla.components.concept.engine.translate.initialToLanguage import mozilla.components.lib.state.ext.observeAsComposableState +import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R @@ -53,6 +54,9 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() { private var behavior: BottomSheetBehavior? = null private val args by navArgs() private val browserStore: BrowserStore by lazy { requireComponents.core.store } + private val translationDialogBinding = ViewBoundFeatureWrapper() + private lateinit var translationsDialogStore: TranslationsDialogStore + private var isTranslationInProgress: Boolean? = null override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = super.onCreateDialog(savedInstanceState).apply { @@ -70,40 +74,21 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() { container: ViewGroup?, savedInstanceState: Bundle?, ): View = ComposeView(requireContext()).apply { - // Signalling need to fetch languages - browserStore.dispatch( - TranslationsAction.OperationRequestedAction( - tabId = args.sessionId, - operation = TranslationOperation.FETCH_SUPPORTED_LANGUAGES, + translationsDialogStore = TranslationsDialogStore( + TranslationsDialogState(), + listOf( + TranslationsDialogMiddleware( + browserStore = browserStore, + sessionId = args.sessionId, + ), ), ) - + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { - val translationsState = browserStore.observeAsComposableState { - state -> - state.findTab(args.sessionId) - ?.translationsState - }.value - - var fromSelected by remember { - mutableStateOf( - translationsState?.translationEngineState - ?.initialFromLanguage(translationsState.supportedLanguages?.fromLanguages), - ) - } - - var toSelected by remember { - mutableStateOf( - translationsState?.translationEngineState - ?.initialToLanguage(translationsState.supportedLanguages?.toLanguages), - ) - } - FirefoxTheme { var translationsVisibility by remember { mutableStateOf( - args.translationsDialogAccessPoint == - TranslationsDialogAccessPoint.Translations, + args.translationsDialogAccessPoint == TranslationsDialogAccessPoint.Translations, ) } @@ -139,46 +124,15 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() { }, ) { val learnMoreUrl = SupportUtils.getSumoURLForTopic( - context, + requireContext(), SupportUtils.SumoTopic.TRANSLATIONS, ) - TranslationsDialog( + + TranslationsDialogContent( learnMoreUrl = learnMoreUrl, - showFirstTimeTranslation = context.settings().showFirstTimeTranslation, - translateFromLanguages = translationsState?.supportedLanguages?.fromLanguages, - translateToLanguages = translationsState?.supportedLanguages?.toLanguages, - initialFrom = fromSelected, - initialTo = toSelected, - onSettingClicked = { - translationsVisibility = false - }, - onLearnMoreClicked = { - (requireActivity() as HomeActivity).openToBrowserAndLoad( - searchTermOrURL = learnMoreUrl, - newTab = true, - from = BrowserDirection.FromTranslationsDialogFragment, - ) - }, - onTranslateButtonClick = { - fromSelected?.code?.let { fromLanguage -> - toSelected?.code?.let { toLanguage -> - TranslationsAction.TranslateAction( - tabId = args.sessionId, - fromLanguage = fromLanguage, - toLanguage = toLanguage, - options = null, - ) - } - }?.let { - browserStore.dispatch( - it, - ) - } - }, - onNotNowButtonClick = { dismiss() }, - onFromSelected = { fromSelected = it }, - onToSelected = { toSelected = it }, - ) + ) { + translationsVisibility = false + } } } } @@ -217,4 +171,102 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() { } } } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + translationDialogBinding.set( + feature = TranslationsDialogBinding( + browserStore = browserStore, + translationsDialogStore = translationsDialogStore, + sessionId = args.sessionId, + getTranslatedPageTitle = { localizedFrom, localizedTo -> + requireContext().getString( + R.string.translations_bottom_sheet_title_translation_completed, + localizedFrom, + localizedTo, + ) + }, + ), + owner = this, + view = view, + ) + translationsDialogStore.dispatch(TranslationsDialogAction.InitTranslationsDialog) + } + + @Composable + private fun TranslationsDialogContent(learnMoreUrl: String, onSettingClicked: () -> Unit) { + val translationsDialogState = + translationsDialogStore.observeAsComposableState { it }.value + + translationsDialogState?.let { state -> + isTranslationInProgress = state.isTranslationInProgress + + if (state.dismissDialogState is DismissDialogState.Dismiss) { + dismissDialog() + } + + TranslationsDialog( + translationsDialogState = translationsDialogState, + learnMoreUrl = learnMoreUrl, + showFirstTime = requireContext().settings().showFirstTimeTranslation, + onSettingClicked = onSettingClicked, + onLearnMoreClicked = { openBrowserAndLoad(learnMoreUrl) }, + onPositiveButtonClicked = { + translationsDialogStore.dispatch(TranslationsDialogAction.TranslateAction) + }, + onNegativeButtonClicked = { + if (state.isTranslated) { + translationsDialogStore.dispatch(TranslationsDialogAction.RestoreTranslation) + } + dismiss() + }, + onFromSelected = { + translationsDialogStore.dispatch( + TranslationsDialogAction.UpdateFromSelectedLanguage( + it, + ), + ) + }, + onToSelected = { + translationsDialogStore.dispatch( + TranslationsDialogAction.UpdateToSelectedLanguage( + it, + ), + ) + }, + ) + } + } + + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + if (isTranslationInProgress == true) { + setFragmentResult( + TRANSLATION_IN_PROGRESS, + bundleOf( + SESSION_ID to args.sessionId, + ), + ) + } + } + + private fun openBrowserAndLoad(learnMoreUrl: String) { + (requireActivity() as HomeActivity).openToBrowserAndLoad( + searchTermOrURL = learnMoreUrl, + newTab = true, + from = BrowserDirection.FromTranslationsDialogFragment, + ) + } + + private fun dismissDialog() { + if (requireContext().settings().showFirstTimeTranslation) { + requireContext().settings().showFirstTimeTranslation = false + } + dismiss() + } + + companion object { + const val TRANSLATION_IN_PROGRESS = "translationInProgress" + const val SESSION_ID = "sessionId" + } } diff --git a/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogMiddleware.kt b/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogMiddleware.kt new file mode 100644 index 000000000..39819732e --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogMiddleware.kt @@ -0,0 +1,62 @@ +/* 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/. */ + +package org.mozilla.fenix.translations + +import mozilla.components.browser.state.action.TranslationsAction +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.concept.engine.translate.TranslationOperation +import mozilla.components.lib.state.Middleware +import mozilla.components.lib.state.MiddlewareContext + +/** + * [Middleware] implementation for updating [BrowserStore] based on translation actions. + */ +class TranslationsDialogMiddleware( + private val browserStore: BrowserStore, + private val sessionId: String, +) : Middleware { + + override fun invoke( + context: MiddlewareContext, + next: (TranslationsDialogAction) -> Unit, + action: TranslationsDialogAction, + ) { + when (action) { + is TranslationsDialogAction.FetchSupportedLanguages -> { + browserStore.dispatch( + TranslationsAction.OperationRequestedAction( + tabId = sessionId, + operation = TranslationOperation.FETCH_SUPPORTED_LANGUAGES, + ), + ) + } + + is TranslationsDialogAction.TranslateAction -> { + context.state.initialFrom?.code?.let { fromLanguage -> + context.state.initialTo?.code?.let { toLanguage -> + TranslationsAction.TranslateAction( + tabId = sessionId, + fromLanguage = fromLanguage, + toLanguage = toLanguage, + options = null, + ) + } + }?.let { + browserStore.dispatch( + it, + ) + } + } + + is TranslationsDialogAction.RestoreTranslation -> { + browserStore.dispatch(TranslationsAction.TranslateRestoreAction(sessionId)) + } + + else -> { + next(action) + } + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogStore.kt b/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogStore.kt new file mode 100644 index 000000000..8fb5cd0ce --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogStore.kt @@ -0,0 +1,285 @@ +/* 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/. */ +package org.mozilla.fenix.translations + +import mozilla.components.browser.state.action.TranslationsAction +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.concept.engine.translate.Language +import mozilla.components.concept.engine.translate.TranslationError +import mozilla.components.lib.state.Action +import mozilla.components.lib.state.Middleware +import mozilla.components.lib.state.State +import mozilla.components.lib.state.Store + +/** + * The [TranslationsDialogStore] holds the [TranslationsDialogState] (state tree). + * + * The only way to change the [TranslationsDialogState] inside + * [TranslationsDialogStore] is to dispatch an [Action] on it. + */ +class TranslationsDialogStore( + initialState: TranslationsDialogState, + middlewares: List> = emptyList(), +) : Store( + initialState, + TranslationsDialogReducer::reduce, + middlewares, +) { + init { + dispatch(TranslationsDialogAction.FetchSupportedLanguages) + } +} + +/** + * The current state of the Translations bottom sheet dialog. + * + * @property isTranslated The page is currently translated. + * @property isTranslationInProgress The page is currently attempting a translation. + * @property positiveButtonType Can be enabled,disabled or in progress. + * @property error An error that can occur during the translation process. + * @property dismissDialogState Whether the dialog bottom sheet should be dismissed. + * @property initialFrom Initial "from" language, based on the translation state and page state. + * @property initialTo Initial "to" language, based on the translation state and page state. + * @property fromLanguages Translation menu items to be shown in the translate from dropdown. + * @property toLanguages Translation menu items to be shown in the translate to dropdown. + * @property translatedPageTitle Title of the bottom sheet dialogue if the page was translated. + */ +data class TranslationsDialogState( + var isTranslated: Boolean = false, + val isTranslationInProgress: Boolean = false, + val positiveButtonType: PositiveButtonType? = null, + val error: TranslationError? = null, + val dismissDialogState: DismissDialogState? = null, + val initialFrom: Language? = null, + val initialTo: Language? = null, + val fromLanguages: List? = null, + val toLanguages: List? = null, + val translatedPageTitle: String? = null, +) : State + +/** + * Action to dispatch through the `TranslationsStore` to modify `TranslationsDialogState` through the reducer. + */ +sealed class TranslationsDialogAction : Action { + /** + * Invoked when the [TranslationsDialogStore] is added to the fragment. + */ + object InitTranslationsDialog : TranslationsDialogAction() + + /** + * When FetchSupportedLanguages is dispatched, an [TranslationsAction.OperationRequestedAction] + * will be dispatched to the [BrowserStore] + */ + object FetchSupportedLanguages : TranslationsDialogAction() + + /** + * Invoked when the user wants to translate a website. + */ + object TranslateAction : TranslationsDialogAction() + + /** + * Invoked when the user wants to restore the website to its original pre-translated content. + */ + object RestoreTranslation : TranslationsDialogAction() + + /** + * Invoked when a translation error occurs during the translation process. + */ + data class UpdateTranslationError( + val translationError: TranslationError? = null, + ) : TranslationsDialogAction() + + /** + * Updates translate from languages list. + */ + data class UpdateTranslateFromLanguages( + val translateFromLanguages: List, + ) : TranslationsDialogAction() + + /** + * Updates translate to languages list. + */ + data class UpdateTranslateToLanguages( + val translateToLanguages: List, + ) : TranslationsDialogAction() + + /** + * Updates to the current selected language from the "translateFromLanguages" list. + */ + data class UpdateFromSelectedLanguage( + val language: Language, + ) : TranslationsDialogAction() + + /** + * Updates to the current selected language from the "translateToLanguages" list. + */ + data class UpdateToSelectedLanguage( + val language: Language, + ) : TranslationsDialogAction() + + /** + * Dismiss the translation dialog fragment. + */ + data class DismissDialog( + val dismissDialogState: DismissDialogState, + ) : TranslationsDialogAction() + + /** + * Updates the dialog content to translation in progress status. + */ + data class UpdateTranslationInProgress( + val inProgress: Boolean, + ) : TranslationsDialogAction() + + /** + * Updates the dialog content to translated status. + */ + data class UpdateTranslated( + val isTranslated: Boolean, + ) : TranslationsDialogAction() + + /** + * Updates the dialog title if the page was translated. + */ + data class UpdateTranslatedPageTitle(val title: String) : TranslationsDialogAction() +} + +/** + * Positive button type from the translation bottom sheet. + */ +sealed class PositiveButtonType { + /** + * The translating indicator will appear. + */ + object InProgress : PositiveButtonType() + + /** + * The button is in a disabled state. + */ + object Disabled : PositiveButtonType() + + /** + * The button is in a enabled state. + */ + object Enabled : PositiveButtonType() +} + +/** + * Dismiss translation bottom sheet type. + */ +sealed class DismissDialogState { + /** + * The dialog should be dismissed. + */ + object Dismiss : DismissDialogState() + + /** + * This is the step when translation is in progress and the dialog is waiting to be dismissed. + */ + object WaitingToBeDismissed : DismissDialogState() +} + +internal object TranslationsDialogReducer { + /** + * Reduces the translations dialog state from the current state and an action performed on it. + * + * @param state The current translations dialog state. + * @param action The action to perform. + * @return The new [TranslationsDialogState]. + */ + @Suppress("LongMethod") + fun reduce( + state: TranslationsDialogState, + action: TranslationsDialogAction, + ): TranslationsDialogState { + return when (action) { + is TranslationsDialogAction.UpdateFromSelectedLanguage -> { + state.copy( + initialFrom = action.language, + positiveButtonType = if (state.initialTo != null && action.language != state.initialTo) { + PositiveButtonType.Enabled + } else { + PositiveButtonType.Disabled + }, + ) + } + + is TranslationsDialogAction.UpdateToSelectedLanguage -> { + state.copy( + initialTo = action.language, + positiveButtonType = if (state.initialFrom != null && action.language != state.initialFrom) { + PositiveButtonType.Enabled + } else { + PositiveButtonType.Disabled + }, + ) + } + + is TranslationsDialogAction.UpdateTranslateToLanguages -> { + state.copy(toLanguages = action.translateToLanguages) + } + + is TranslationsDialogAction.UpdateTranslateFromLanguages -> { + state.copy(fromLanguages = action.translateFromLanguages) + } + + is TranslationsDialogAction.DismissDialog -> { + state.copy(dismissDialogState = action.dismissDialogState) + } + + is TranslationsDialogAction.UpdateTranslationInProgress -> { + state.copy( + isTranslationInProgress = action.inProgress, + positiveButtonType = if (action.inProgress) { + PositiveButtonType.InProgress + } else { + state.positiveButtonType + }, + ) + } + + is TranslationsDialogAction.InitTranslationsDialog -> { + state.copy( + positiveButtonType = if (state.initialTo == null || state.initialFrom == null) { + PositiveButtonType.Disabled + } else { + state.positiveButtonType + }, + ) + } + + is TranslationsDialogAction.UpdateTranslationError -> { + state.copy( + error = action.translationError, + positiveButtonType = if ( + action.translationError is TranslationError.LanguageNotSupportedError + ) { + PositiveButtonType.Disabled + } else { + PositiveButtonType.Enabled + }, + ) + } + + is TranslationsDialogAction.UpdateTranslated -> { + state.copy( + isTranslated = action.isTranslated, + positiveButtonType = PositiveButtonType.Disabled, + ) + } + + is TranslationsDialogAction.UpdateTranslatedPageTitle -> { + state.copy(translatedPageTitle = action.title) + } + + is TranslationsDialogAction.TranslateAction, + TranslationsDialogAction.FetchSupportedLanguages, + TranslationsDialogAction.RestoreTranslation, + -> { + // handled by [TranslationsDialogMiddleware] + state + } + } + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c1a749708..8f14076d6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2322,6 +2322,10 @@ Translate this page? + + Page translated from %1$s to %2$s Try private translations in %1$s @@ -2334,6 +2338,8 @@ Translate to Not now + + Show original Done @@ -2354,6 +2360,8 @@ Sorry, we don’t support %1$s yet. Learn more + + Translating… diff --git a/app/src/test/java/org/mozilla/fenix/translations/TranslationsDialogBindingTest.kt b/app/src/test/java/org/mozilla/fenix/translations/TranslationsDialogBindingTest.kt new file mode 100644 index 000000000..4f0bc4590 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/translations/TranslationsDialogBindingTest.kt @@ -0,0 +1,308 @@ +/* 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/. */ + +package org.mozilla.fenix.translations + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import mozilla.components.browser.state.action.TranslationsAction +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.createTab +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.concept.engine.translate.DetectedLanguages +import mozilla.components.concept.engine.translate.Language +import mozilla.components.concept.engine.translate.TranslationEngineState +import mozilla.components.concept.engine.translate.TranslationError +import mozilla.components.concept.engine.translate.TranslationOperation +import mozilla.components.concept.engine.translate.TranslationPair +import mozilla.components.concept.engine.translate.TranslationSupport +import mozilla.components.support.test.ext.joinBlocking +import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify +import org.mozilla.fenix.R + +@RunWith(AndroidJUnit4::class) +class TranslationsDialogBindingTest { + @get:Rule + val coroutineRule = MainCoroutineRule() + + lateinit var browserStore: BrowserStore + private lateinit var translationsDialogStore: TranslationsDialogStore + + private val tabId = "1" + private val tab = createTab(url = tabId, id = tabId) + + @Test + fun `WHEN fromLanguage and toLanguage get updated in the browserStore THEN translations dialog actions dispatched with the update`() = + runTestOnMain { + val englishLanguage = Language("en", "English") + val spanishLanguage = Language("es", "Spanish") + translationsDialogStore = spy(TranslationsDialogStore(TranslationsDialogState())) + browserStore = BrowserStore( + BrowserState( + tabs = listOf(tab), + selectedTabId = tabId, + ), + ) + + val binding = TranslationsDialogBinding( + browserStore = browserStore, + translationsDialogStore = translationsDialogStore, + sessionId = tabId, + getTranslatedPageTitle = { localizedFrom, localizedTo -> + testContext.getString( + R.string.translations_bottom_sheet_title_translation_completed, + localizedFrom, + localizedTo, + ) + }, + ) + binding.start() + + val detectedLanguages = DetectedLanguages( + documentLangTag = englishLanguage.code, + supportedDocumentLang = true, + userPreferredLangTag = spanishLanguage.code, + ) + + val translationEngineState = TranslationEngineState( + detectedLanguages = detectedLanguages, + error = null, + isEngineReady = true, + requestedTranslationPair = TranslationPair( + fromLanguage = englishLanguage.code, + toLanguage = spanishLanguage.code, + ), + ) + + val supportLanguages = TranslationSupport( + fromLanguages = listOf(englishLanguage), + toLanguages = listOf(spanishLanguage), + ) + + browserStore.dispatch( + TranslationsAction.SetSupportedLanguagesAction( + tabId = tab.id, + supportedLanguages = supportLanguages, + ), + ).joinBlocking() + + browserStore.dispatch( + TranslationsAction.TranslateStateChangeAction( + tabId = tabId, + translationEngineState = translationEngineState, + ), + ).joinBlocking() + + verify(translationsDialogStore).dispatch( + TranslationsDialogAction.UpdateFromSelectedLanguage( + englishLanguage, + ), + ) + verify(translationsDialogStore).dispatch( + TranslationsDialogAction.UpdateToSelectedLanguage( + spanishLanguage, + ), + ) + verify(translationsDialogStore).dispatch( + TranslationsDialogAction.UpdateTranslatedPageTitle( + testContext.getString( + R.string.translations_bottom_sheet_title_translation_completed, + englishLanguage.localizedDisplayName, + spanishLanguage.localizedDisplayName, + ), + ), + ) + } + + @Test + fun `WHEN translate action is sent to the browserStore THEN update translation dialog store based on operation`() = + runTestOnMain { + val englishLanguage = Language("en", "English") + val spanishLanguage = Language("es", "Spanish") + translationsDialogStore = spy(TranslationsDialogStore(TranslationsDialogState())) + browserStore = BrowserStore( + BrowserState( + tabs = listOf(tab), + selectedTabId = tabId, + ), + ) + + val binding = TranslationsDialogBinding( + browserStore = browserStore, + translationsDialogStore = translationsDialogStore, + sessionId = tabId, + getTranslatedPageTitle = { localizedFrom, localizedTo -> + testContext.getString( + R.string.translations_bottom_sheet_title_translation_completed, + localizedFrom, + localizedTo, + ) + }, + ) + binding.start() + + browserStore.dispatch( + TranslationsAction.TranslateAction( + tabId = tabId, + fromLanguage = englishLanguage.code, + toLanguage = spanishLanguage.code, + null, + ), + ).joinBlocking() + + verify(translationsDialogStore).dispatch( + TranslationsDialogAction.UpdateTranslationInProgress( + true, + ), + ) + verify(translationsDialogStore).dispatch( + TranslationsDialogAction.DismissDialog( + dismissDialogState = DismissDialogState.WaitingToBeDismissed, + ), + ) + } + + @Test + fun `WHEN translate from languages list and translate to languages list are sent to the browserStore THEN update translation dialog store based on operation`() = + runTestOnMain { + translationsDialogStore = spy(TranslationsDialogStore(TranslationsDialogState())) + browserStore = BrowserStore( + BrowserState( + tabs = listOf(tab), + selectedTabId = tabId, + ), + ) + + val binding = TranslationsDialogBinding( + browserStore = browserStore, + translationsDialogStore = translationsDialogStore, + sessionId = tabId, + getTranslatedPageTitle = { localizedFrom, localizedTo -> + testContext.getString( + R.string.translations_bottom_sheet_title_translation_completed, + localizedFrom, + localizedTo, + ) + }, + ) + binding.start() + + val toLanguage = Language("de", "German") + val fromLanguage = Language("es", "Spanish") + val supportedLanguages = TranslationSupport(listOf(fromLanguage), listOf(toLanguage)) + browserStore.dispatch( + TranslationsAction.SetSupportedLanguagesAction( + tabId = tab.id, + supportedLanguages = supportedLanguages, + ), + ).joinBlocking() + + verify(translationsDialogStore).dispatch( + TranslationsDialogAction.UpdateTranslateFromLanguages( + listOf(fromLanguage), + ), + ) + verify(translationsDialogStore).dispatch( + TranslationsDialogAction.UpdateTranslateToLanguages( + listOf(toLanguage), + ), + ) + } + + @Test + fun `WHEN translate action success is sent to the browserStore THEN update translation dialog store based on operation`() = + runTestOnMain { + translationsDialogStore = + spy(TranslationsDialogStore(TranslationsDialogState(dismissDialogState = DismissDialogState.WaitingToBeDismissed))) + browserStore = BrowserStore( + BrowserState( + tabs = listOf(tab), + selectedTabId = tabId, + ), + ) + + val binding = TranslationsDialogBinding( + browserStore = browserStore, + translationsDialogStore = translationsDialogStore, + sessionId = tabId, + getTranslatedPageTitle = { localizedFrom, localizedTo -> + testContext.getString( + R.string.translations_bottom_sheet_title_translation_completed, + localizedFrom, + localizedTo, + ) + }, + ) + binding.start() + + browserStore.dispatch( + TranslationsAction.TranslateSuccessAction( + tabId = tab.id, + operation = TranslationOperation.TRANSLATE, + ), + ).joinBlocking() + + verify(translationsDialogStore).dispatch( + TranslationsDialogAction.UpdateTranslated( + true, + ), + ) + verify(translationsDialogStore).dispatch( + TranslationsDialogAction.UpdateTranslationInProgress( + false, + ), + ) + verify(translationsDialogStore).dispatch( + TranslationsDialogAction.DismissDialog( + dismissDialogState = DismissDialogState.Dismiss, + ), + ) + } + + @Test + fun `WHEN translate fetch error is sent to the browserStore THEN update translation dialog store based on operation`() = + runTestOnMain { + translationsDialogStore = + spy(TranslationsDialogStore(TranslationsDialogState())) + browserStore = BrowserStore( + BrowserState( + tabs = listOf(tab), + selectedTabId = tabId, + ), + ) + + val binding = TranslationsDialogBinding( + browserStore = browserStore, + translationsDialogStore = translationsDialogStore, + sessionId = tabId, + getTranslatedPageTitle = { localizedFrom, localizedTo -> + testContext.getString( + R.string.translations_bottom_sheet_title_translation_completed, + localizedFrom, + localizedTo, + ) + }, + ) + binding.start() + + val fetchError = TranslationError.CouldNotLoadLanguagesError(null) + browserStore.dispatch( + TranslationsAction.TranslateExceptionAction( + tabId = tab.id, + operation = TranslationOperation.FETCH_SUPPORTED_LANGUAGES, + translationError = fetchError, + ), + ).joinBlocking() + + verify(translationsDialogStore).dispatch( + TranslationsDialogAction.UpdateTranslationError(fetchError), + ) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/translations/TranslationsDialogMiddlewareTest.kt b/app/src/test/java/org/mozilla/fenix/translations/TranslationsDialogMiddlewareTest.kt new file mode 100644 index 000000000..178b4ec86 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/translations/TranslationsDialogMiddlewareTest.kt @@ -0,0 +1,101 @@ +/* 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/. */ + +package org.mozilla.fenix.translations + +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.test.runTest +import mozilla.components.browser.state.action.TranslationsAction +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.concept.engine.translate.Language +import mozilla.components.concept.engine.translate.TranslationOperation +import mozilla.components.support.test.ext.joinBlocking +import mozilla.components.support.test.libstate.ext.waitUntilIdle +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner + +@RunWith(FenixRobolectricTestRunner::class) +class TranslationsDialogMiddlewareTest { + + @Test + fun `GIVEN translationState WHEN FetchSupportedLanguages action is called THEN call OperationRequestedAction from BrowserStore`() = + runTest { + val browserStore = mockk(relaxed = true) + val translationsDialogMiddleware = + TranslationsDialogMiddleware(browserStore = browserStore, sessionId = "tab1") + + val translationStore = TranslationsDialogStore( + initialState = TranslationsDialogState(), + middlewares = listOf(translationsDialogMiddleware), + ) + translationStore.dispatch(TranslationsDialogAction.FetchSupportedLanguages).joinBlocking() + + translationStore.waitUntilIdle() + + verify { + browserStore.dispatch( + TranslationsAction.OperationRequestedAction( + tabId = "tab1", + operation = TranslationOperation.FETCH_SUPPORTED_LANGUAGES, + ), + ) + } + } + + @Test + fun `GIVEN translationState WHEN TranslateAction from TranslationDialogStore is called THEN call TranslateAction from BrowserStore`() = + runTest { + val browserStore = mockk(relaxed = true) + val translationsDialogMiddleware = + TranslationsDialogMiddleware(browserStore = browserStore, sessionId = "tab1") + + val translationStore = TranslationsDialogStore( + initialState = TranslationsDialogState( + initialFrom = Language("en", "English"), + initialTo = Language("fr", "France"), + ), + middlewares = listOf(translationsDialogMiddleware), + ) + translationStore.dispatch(TranslationsDialogAction.TranslateAction).joinBlocking() + + translationStore.waitUntilIdle() + + verify { + browserStore.dispatch( + TranslationsAction.TranslateAction( + tabId = "tab1", + fromLanguage = "en", + toLanguage = "fr", + options = null, + ), + ) + } + } + + @Test + fun `GIVEN translationState WHEN RestoreTranslation from TranslationDialogStore is called THEN call TranslateRestoreAction from BrowserStore`() = + runTest { + val browserStore = mockk(relaxed = true) + val translationsDialogMiddleware = + TranslationsDialogMiddleware(browserStore = browserStore, sessionId = "tab1") + + val translationStore = TranslationsDialogStore( + initialState = TranslationsDialogState(), + middlewares = listOf(translationsDialogMiddleware), + ) + translationStore.dispatch(TranslationsDialogAction.RestoreTranslation).joinBlocking() + + translationStore.waitUntilIdle() + + verify { + browserStore.dispatch( + TranslationsAction.TranslateRestoreAction( + tabId = "tab1", + ), + ) + } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/translations/TranslationsDialogReducerTest.kt b/app/src/test/java/org/mozilla/fenix/translations/TranslationsDialogReducerTest.kt new file mode 100644 index 000000000..0aa43d4d2 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/translations/TranslationsDialogReducerTest.kt @@ -0,0 +1,229 @@ +/* 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/. */ + +package org.mozilla.fenix.translations + +import mozilla.components.concept.engine.translate.Language +import mozilla.components.concept.engine.translate.TranslationError +import mozilla.components.support.test.robolectric.testContext +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.R +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner + +@RunWith(FenixRobolectricTestRunner::class) +class TranslationsDialogReducerTest { + + @Test + fun `WHEN the reducer is called for UpdateFromSelectedLanguage THEN a new state with updated fromSelectedLanguage is returned`() { + val spanishLanguage = Language("es", "Spanish") + val englishLanguage = Language("en", "English") + + val translationsDialogState = TranslationsDialogState(initialTo = spanishLanguage) + + val updatedState = TranslationsDialogReducer.reduce( + translationsDialogState, + TranslationsDialogAction.UpdateFromSelectedLanguage(englishLanguage), + ) + + assertEquals(englishLanguage, updatedState.initialFrom) + assertEquals(PositiveButtonType.Enabled, updatedState.positiveButtonType) + + val updatedStateTwo = TranslationsDialogReducer.reduce( + translationsDialogState, + TranslationsDialogAction.UpdateFromSelectedLanguage(spanishLanguage), + ) + + assertEquals(PositiveButtonType.Disabled, updatedStateTwo.positiveButtonType) + } + + @Test + fun `WHEN the reducer is called for UpdateToSelectedLanguage THEN a new state with updated toSelectedLanguage is returned`() { + val spanishLanguage = Language("es", "Spanish") + val englishLanguage = Language("en", "English") + + val translationsDialogState = TranslationsDialogState(initialFrom = spanishLanguage) + + val updatedState = TranslationsDialogReducer.reduce( + translationsDialogState, + TranslationsDialogAction.UpdateToSelectedLanguage(englishLanguage), + ) + + assertEquals(englishLanguage, updatedState.initialTo) + assertEquals(PositiveButtonType.Enabled, updatedState.positiveButtonType) + + val updatedStateTwo = TranslationsDialogReducer.reduce( + translationsDialogState, + TranslationsDialogAction.UpdateToSelectedLanguage(spanishLanguage), + ) + + assertEquals(PositiveButtonType.Disabled, updatedStateTwo.positiveButtonType) + } + + @Test + fun `WHEN the reducer is called for UpdateTranslateToLanguages THEN a new state with updated translateToLanguages is returned`() { + val spanishLanguage = Language("es", "Spanish") + val englishLanguage = Language("en", "English") + + val translationsDialogState = TranslationsDialogState() + + val updatedState = TranslationsDialogReducer.reduce( + translationsDialogState, + TranslationsDialogAction.UpdateTranslateToLanguages( + listOf( + spanishLanguage, + englishLanguage, + ), + ), + ) + + assertEquals(listOf(spanishLanguage, englishLanguage), updatedState.toLanguages) + } + + @Test + fun `WHEN the reducer is called for UpdateTranslateFromLanguages THEN a new state with updated translatefromLanguages is returned`() { + val spanishLanguage = Language("es", "Spanish") + val englishLanguage = Language("en", "English") + + val translationsDialogState = TranslationsDialogState() + + val updatedState = TranslationsDialogReducer.reduce( + translationsDialogState, + TranslationsDialogAction.UpdateTranslateFromLanguages( + listOf( + spanishLanguage, + englishLanguage, + ), + ), + ) + + assertEquals(listOf(spanishLanguage, englishLanguage), updatedState.fromLanguages) + } + + @Test + fun `WHEN the reducer is called for DismissDialog THEN a new state with updated dismiss dialog is returned`() { + val translationsDialogState = TranslationsDialogState() + + val updatedState = TranslationsDialogReducer.reduce( + translationsDialogState, + TranslationsDialogAction.DismissDialog(DismissDialogState.Dismiss), + ) + + assertEquals(DismissDialogState.Dismiss, updatedState.dismissDialogState) + } + + @Test + fun `WHEN the reducer is called for UpdateInProgress THEN a new state with translation in progress is returned`() { + val translationsDialogState = TranslationsDialogState() + + val updatedState = TranslationsDialogReducer.reduce( + translationsDialogState, + TranslationsDialogAction.UpdateTranslationInProgress(true), + ) + + assertEquals(true, updatedState.isTranslationInProgress) + assertEquals(PositiveButtonType.InProgress, updatedState.positiveButtonType) + } + + @Test + fun `WHEN the reducer is called for InitTranslationsDialog THEN a new state for PositiveButtonType is returned`() { + val translationsDialogState = TranslationsDialogState() + + val updatedState = TranslationsDialogReducer.reduce( + translationsDialogState, + TranslationsDialogAction.InitTranslationsDialog, + ) + + assertEquals(PositiveButtonType.Disabled, updatedState.positiveButtonType) + + val spanishLanguage = Language("es", "Spanish") + val englishLanguage = Language("en", "English") + val translationsDialogStateTwo = TranslationsDialogState( + initialFrom = spanishLanguage, + initialTo = englishLanguage, + positiveButtonType = PositiveButtonType.Enabled, + ) + + val updatedStateTwo = TranslationsDialogReducer.reduce( + translationsDialogStateTwo, + TranslationsDialogAction.InitTranslationsDialog, + ) + + assertEquals(PositiveButtonType.Enabled, updatedStateTwo.positiveButtonType) + } + + @Test + fun `WHEN the reducer is called for UpdateTranslationError THEN a new state with translation error is returned`() { + val translationsDialogState = TranslationsDialogState() + + val updatedState = TranslationsDialogReducer.reduce( + translationsDialogState, + TranslationsDialogAction.UpdateTranslationError( + TranslationError.LanguageNotSupportedError( + null, + ), + ), + ) + + assertTrue(updatedState.error is TranslationError.LanguageNotSupportedError) + assertEquals(PositiveButtonType.Disabled, updatedState.positiveButtonType) + + val updatedStateTwo = TranslationsDialogReducer.reduce( + translationsDialogState, + TranslationsDialogAction.UpdateTranslationError( + TranslationError.CouldNotLoadLanguagesError( + null, + ), + ), + ) + + assertTrue(updatedStateTwo.error is TranslationError.CouldNotLoadLanguagesError) + assertEquals(PositiveButtonType.Enabled, updatedStateTwo.positiveButtonType) + } + + @Test + fun `WHEN the reducer is called for UpdateTranslated THEN a new state with translation translated is returned`() { + val translationsDialogState = TranslationsDialogState() + + val updatedState = TranslationsDialogReducer.reduce( + translationsDialogState, + TranslationsDialogAction.UpdateTranslated( + true, + ), + ) + + assertEquals(PositiveButtonType.Disabled, updatedState.positiveButtonType) + assertEquals(true, updatedState.isTranslated) + } + + @Test + fun `WHEN the reducer is called for UpdateTranslatedPageTitle THEN a new state with translation title is returned`() { + val spanishLanguage = Language("es", "Spanish") + val englishLanguage = Language("en", "English") + val translationsDialogState = + TranslationsDialogState(initialTo = englishLanguage, initialFrom = spanishLanguage) + + val updatedState = TranslationsDialogReducer.reduce( + translationsDialogState, + TranslationsDialogAction.UpdateTranslatedPageTitle( + testContext.getString( + R.string.translations_bottom_sheet_title_translation_completed, + spanishLanguage.localizedDisplayName, + englishLanguage.localizedDisplayName, + ), + ), + ) + + assertEquals( + testContext.getString( + R.string.translations_bottom_sheet_title_translation_completed, + spanishLanguage.localizedDisplayName, + englishLanguage.localizedDisplayName, + ), + updatedState.translatedPageTitle, + ) + } +} From 1b93e27944ca76ba13272e30d082fec596669389 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 19:07:13 +0000 Subject: [PATCH 016/238] Bump cryptography Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.0 to 42.0.2. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/42.0.0...42.0.2) --- updated-dependencies: - dependency-name: cryptography dependency-type: indirect ... Signed-off-by: dependabot[bot] --- .../fenix/syncintegration/Pipfile.lock | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/syncintegration/Pipfile.lock b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/Pipfile.lock index b42b86bfb..1f781e87a 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/syncintegration/Pipfile.lock +++ b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/Pipfile.lock @@ -196,42 +196,42 @@ }, "cryptography": { "hashes": [ - "sha256:0a68bfcf57a6887818307600c3c0ebc3f62fbb6ccad2240aa21887cda1f8df1b", - "sha256:146e971e92a6dd042214b537a726c9750496128453146ab0ee8971a0299dc9bd", - "sha256:14e4b909373bc5bf1095311fa0f7fcabf2d1a160ca13f1e9e467be1ac4cbdf94", - "sha256:206aaf42e031b93f86ad60f9f5d9da1b09164f25488238ac1dc488334eb5e221", - "sha256:3005166a39b70c8b94455fdbe78d87a444da31ff70de3331cdec2c568cf25b7e", - "sha256:324721d93b998cb7367f1e6897370644751e5580ff9b370c0a50dc60a2003513", - "sha256:33588310b5c886dfb87dba5f013b8d27df7ffd31dc753775342a1e5ab139e59d", - "sha256:35cf6ed4c38f054478a9df14f03c1169bb14bd98f0b1705751079b25e1cb58bc", - "sha256:3ca482ea80626048975360c8e62be3ceb0f11803180b73163acd24bf014133a0", - "sha256:56ce0c106d5c3fec1038c3cca3d55ac320a5be1b44bf15116732d0bc716979a2", - "sha256:5a217bca51f3b91971400890905a9323ad805838ca3fa1e202a01844f485ee87", - "sha256:678cfa0d1e72ef41d48993a7be75a76b0725d29b820ff3cfd606a5b2b33fda01", - "sha256:69fd009a325cad6fbfd5b04c711a4da563c6c4854fc4c9544bff3088387c77c0", - "sha256:6cf9b76d6e93c62114bd19485e5cb003115c134cf9ce91f8ac924c44f8c8c3f4", - "sha256:74f18a4c8ca04134d2052a140322002fef535c99cdbc2a6afc18a8024d5c9d5b", - "sha256:85f759ed59ffd1d0baad296e72780aa62ff8a71f94dc1ab340386a1207d0ea81", - "sha256:87086eae86a700307b544625e3ba11cc600c3c0ef8ab97b0fda0705d6db3d4e3", - "sha256:8814722cffcfd1fbd91edd9f3451b88a8f26a5fd41b28c1c9193949d1c689dc4", - "sha256:8fedec73d590fd30c4e3f0d0f4bc961aeca8390c72f3eaa1a0874d180e868ddf", - "sha256:9515ea7f596c8092fdc9902627e51b23a75daa2c7815ed5aa8cf4f07469212ec", - "sha256:988b738f56c665366b1e4bfd9045c3efae89ee366ca3839cd5af53eaa1401bce", - "sha256:a2a8d873667e4fd2f34aedab02ba500b824692c6542e017075a2efc38f60a4c0", - "sha256:bd7cf7a8d9f34cc67220f1195884151426ce616fdc8285df9054bfa10135925f", - "sha256:bdce70e562c69bb089523e75ef1d9625b7417c6297a76ac27b1b8b1eb51b7d0f", - "sha256:be14b31eb3a293fc6e6aa2807c8a3224c71426f7c4e3639ccf1a2f3ffd6df8c3", - "sha256:be41b0c7366e5549265adf2145135dca107718fa44b6e418dc7499cfff6b4689", - "sha256:c310767268d88803b653fffe6d6f2f17bb9d49ffceb8d70aed50ad45ea49ab08", - "sha256:c58115384bdcfe9c7f644c72f10f6f42bed7cf59f7b52fe1bf7ae0a622b3a139", - "sha256:c640b0ef54138fde761ec99a6c7dc4ce05e80420262c20fa239e694ca371d434", - "sha256:ca20550bb590db16223eb9ccc5852335b48b8f597e2f6f0878bbfd9e7314eb17", - "sha256:d97aae66b7de41cdf5b12087b5509e4e9805ed6f562406dfcf60e8481a9a28f8", - "sha256:e9326ca78111e4c645f7e49cbce4ed2f3f85e17b61a563328c85a5208cf34440" + "sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380", + "sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589", + "sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea", + "sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65", + "sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a", + "sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3", + "sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008", + "sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1", + "sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2", + "sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635", + "sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2", + "sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90", + "sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee", + "sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a", + "sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242", + "sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12", + "sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2", + "sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d", + "sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be", + "sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee", + "sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6", + "sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529", + "sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929", + "sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1", + "sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6", + "sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a", + "sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446", + "sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9", + "sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888", + "sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4", + "sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33", + "sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==42.0.0" + "version": "==42.0.2" }, "distro": { "hashes": [ From 2fd8359d30749be7f7d98794f10daca21604892e Mon Sep 17 00:00:00 2001 From: James Hugman Date: Wed, 31 Jan 2024 12:05:20 +0000 Subject: [PATCH 017/238] =?UTF-8?q?Bug=201880476=20=E2=80=94=20Rename=20gl?= =?UTF-8?q?eanplumb=20to=20nimbus=20messaging?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/mozilla/fenix/onboarding/view/OnboardingMapperTest.kt | 4 ++-- app/src/main/java/org/mozilla/fenix/components/Analytics.kt | 2 +- .../org/mozilla/fenix/messaging/DefaultMessageController.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/onboarding/view/OnboardingMapperTest.kt b/app/src/androidTest/java/org/mozilla/fenix/onboarding/view/OnboardingMapperTest.kt index 1a9024ee6..588078298 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/onboarding/view/OnboardingMapperTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/onboarding/view/OnboardingMapperTest.kt @@ -11,7 +11,7 @@ import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mozilla.experiments.nimbus.GleanPlumbMessageHelper +import org.mozilla.experiments.nimbus.NimbusMessagingHelperInterface import org.mozilla.experiments.nimbus.StringHolder import org.mozilla.fenix.R import org.mozilla.fenix.helpers.HomeActivityIntentTestRule @@ -28,7 +28,7 @@ class OnboardingMapperTest { private lateinit var junoOnboardingFeature: JunoOnboarding private lateinit var jexlConditions: Map - private lateinit var jexlHelper: GleanPlumbMessageHelper + private lateinit var jexlHelper: NimbusMessagingHelperInterface private lateinit var evalFunction: (String) -> Boolean @Before diff --git a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt index da0fe53ea..b68ea5e14 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt @@ -168,7 +168,7 @@ class Analytics( NimbusMessagingStorage( context = context, metadataStorage = OnDiskMessageMetadataStorage(context), - gleanPlumb = experiments, + nimbus = experiments, messagingFeature = FxNimbusMessaging.features.messaging, attributeProvider = CustomAttributeProvider, ) diff --git a/app/src/main/java/org/mozilla/fenix/messaging/DefaultMessageController.kt b/app/src/main/java/org/mozilla/fenix/messaging/DefaultMessageController.kt index 21f47f76a..a006ab02b 100644 --- a/app/src/main/java/org/mozilla/fenix/messaging/DefaultMessageController.kt +++ b/app/src/main/java/org/mozilla/fenix/messaging/DefaultMessageController.kt @@ -13,7 +13,7 @@ import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.MessageCl import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.MessageDismissed /** - * Handles default interactions with the ui of GleanPlumb messages. + * Handles default interactions with the ui of Nimbus Messaging messages. */ class DefaultMessageController( private val appStore: AppStore, From 21bc23303f1f61d61f3f9ed9d457a4e31bda5935 Mon Sep 17 00:00:00 2001 From: James Hugman Date: Fri, 26 Jan 2024 16:58:20 +0000 Subject: [PATCH 018/238] =?UTF-8?q?Bug=201880476=20=E2=80=94=C2=A0Reduce?= =?UTF-8?q?=20visibility=20of=20fields=20in=20Message?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/org/mozilla/fenix/HomeActivity.kt | 4 ++-- .../viewholders/onboarding/MessageCardViewHolder.kt | 6 +++--- .../fenix/messaging/MessageNotificationWorker.kt | 11 +++++------ .../fenix/messaging/state/MessagingMiddleware.kt | 4 ++-- .../fenix/messaging/state/MessagingMiddlewareTest.kt | 4 ++-- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index eb59219be..c649a8dd0 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -1229,8 +1229,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { val fenixNimbusMessagingController = FenixNimbusMessagingController(messagingStorage) val researchSurfaceDialogFragment = ResearchSurfaceDialogFragment.newInstance( - keyMessageText = nextMessage.data.text, - keyAcceptButtonText = nextMessage.data.buttonLabel, + keyMessageText = nextMessage.text, + keyAcceptButtonText = nextMessage.buttonLabel, keyDismissButtonText = null, ) diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/MessageCardViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/MessageCardViewHolder.kt index 7fbe7e145..85b0d934e 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/MessageCardViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/MessageCardViewHolder.kt @@ -77,9 +77,9 @@ class MessageCardViewHolder( ) MessageCard( - messageText = message.data.text, - titleText = message.data.title, - buttonText = message.data.buttonLabel, + messageText = message.text, + titleText = message.title, + buttonText = message.buttonLabel, messageColors = messageCardColors, onClick = { interactor.onMessageClicked(message) }, onCloseButtonClick = { interactor.onMessageClosedClicked(message) }, diff --git a/app/src/main/java/org/mozilla/fenix/messaging/MessageNotificationWorker.kt b/app/src/main/java/org/mozilla/fenix/messaging/MessageNotificationWorker.kt index 7211e0047..6a8bd3ba7 100644 --- a/app/src/main/java/org/mozilla/fenix/messaging/MessageNotificationWorker.kt +++ b/app/src/main/java/org/mozilla/fenix/messaging/MessageNotificationWorker.kt @@ -52,9 +52,8 @@ class MessageNotificationWorker( ?: return Result.success() val currentBootUniqueIdentifier = BootUtils.getBootIdentifier(context) - val messageMetadata = nextMessage.metadata // Device has NOT been power cycled. - if (messageMetadata.latestBootIdentifier == currentBootUniqueIdentifier) { + if (nextMessage.hasShownThisCycle(currentBootUniqueIdentifier)) { return Result.success() } @@ -91,8 +90,8 @@ class MessageNotificationWorker( return createBaseNotification( context, ensureMarketingChannelExists(context), - message.data.title, - message.data.text, + message.title, + message.text, onClickPendingIntent, onDismissPendingIntent, ) @@ -189,7 +188,7 @@ class NotificationDismissedService : LifecycleService() { if (message != null) { // Update message as 'dismissed'. - nimbusMessagingController.onMessageDismissed(message.metadata) + nimbusMessagingController.onMessageDismissed(message) } } } @@ -220,7 +219,7 @@ class NotificationClickedReceiverActivity : ComponentActivity() { if (message != null) { // Update message as 'clicked'. - nimbusMessagingController.onMessageClicked(message.metadata) + nimbusMessagingController.onMessageClicked(message) // Create the intent. val intent = nimbusMessagingController.getIntentForMessage(message) diff --git a/app/src/main/java/org/mozilla/fenix/messaging/state/MessagingMiddleware.kt b/app/src/main/java/org/mozilla/fenix/messaging/state/MessagingMiddleware.kt index 9388f3414..4d8f79ad9 100644 --- a/app/src/main/java/org/mozilla/fenix/messaging/state/MessagingMiddleware.kt +++ b/app/src/main/java/org/mozilla/fenix/messaging/state/MessagingMiddleware.kt @@ -93,7 +93,7 @@ class MessagingMiddleware( context.dispatch(UpdateMessages(newMessages)) consumeMessageToShowIfNeeded(context, message) coroutineScope.launch { - controller.onMessageDismissed(message.metadata) + controller.onMessageDismissed(message) } } @@ -103,7 +103,7 @@ class MessagingMiddleware( ) { // Update Nimbus storage. coroutineScope.launch { - controller.onMessageClicked(message.metadata) + controller.onMessageClicked(message) } // Update app state. val newMessages = removeMessage(context, message) diff --git a/app/src/test/java/org/mozilla/fenix/messaging/state/MessagingMiddlewareTest.kt b/app/src/test/java/org/mozilla/fenix/messaging/state/MessagingMiddlewareTest.kt index d55001914..9dd968590 100644 --- a/app/src/test/java/org/mozilla/fenix/messaging/state/MessagingMiddlewareTest.kt +++ b/app/src/test/java/org/mozilla/fenix/messaging/state/MessagingMiddlewareTest.kt @@ -221,7 +221,7 @@ class MessagingMiddlewareTest { store.dispatch(Evaluate(FenixMessageSurfaceId.HOMESCREEN)) store.waitUntilIdle() - assertEquals(1, store.state.messaging.messages.first().metadata.displayCount) + assertEquals(1, store.state.messaging.messages.first().displayCount) } @Test @@ -286,7 +286,7 @@ class MessagingMiddlewareTest { store.dispatch(Evaluate(FenixMessageSurfaceId.HOMESCREEN)).joinBlocking() store.waitUntilIdle() - assertEquals(messageDisplayed.metadata.displayCount, store.state.messaging.messages[0].metadata.displayCount) + assertEquals(messageDisplayed.displayCount, store.state.messaging.messages[0].displayCount) } @Test From 12bbe2dc36ce9b1281c888ab10b5f7bdaf04ddda Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 21 Feb 2024 00:03:28 +0000 Subject: [PATCH 019/238] Import translations from android-l10n --- app/src/main/res/values-en-rGB/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml index 31dfb31b8..7f37c4523 100644 --- a/app/src/main/res/values-en-rGB/strings.xml +++ b/app/src/main/res/values-en-rGB/strings.xml @@ -496,7 +496,7 @@ Secure site not available - Most likely, the web site simply does not support HTTPs. + Most likely, the web site simply does not support HTTPS. However, it’s also possible that an attacker is involved. If you continue to the web site, you should not enter any sensitive info. If you continue, HTTPS-Only mode will be turned off temporarily for the site. From 854ef2915c20bb634857a431bcdc0fd807c6e3fb Mon Sep 17 00:00:00 2001 From: t-p-white Date: Tue, 20 Feb 2024 11:06:51 +0000 Subject: [PATCH 020/238] Bug 1881009 - Added experimental onboarding strings for Nimbus --- app/src/main/res/values/strings.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8f14076d6..85c99f579 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -314,12 +314,18 @@ Firefox privacy notice - + + Learn more in our privacy notice We love keeping you safe + + Find out why millions love Firefox Our non-profit backed browser helps stop companies from secretly following you around the web. + More than 100 million people protect their privacy by choosing a browser that’s backed by a nonprofit. + Our non-profit backed browser helps stop companies from secretly following you around the web.\n\nLearn more in our privacy notice. From 0b977ba6de485fa9ef6ba314f6993022ab4acfe4 Mon Sep 17 00:00:00 2001 From: DreVla Date: Thu, 15 Feb 2024 15:43:29 +0200 Subject: [PATCH 021/238] Bug 1879507 - Show Default prompt when default-browser card disabled As part of the Set as default optimization experiment, treatment branch A will show the "Set as Default" System prompt when the onboarding started, without showing the default-browser card. This will be controlled from the experimenter and will require disabling the default-browser card. If the user already set Firefox as default browser, this prompt will not be shown. --- .../fenix/onboarding/OnboardingFragment.kt | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingFragment.kt b/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingFragment.kt index d216668ca..af4693681 100644 --- a/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingFragment.kt @@ -52,7 +52,7 @@ class OnboardingFragment : Fragment() { private val pagesToDisplay by lazy { pagesToDisplay( - shouldShowDefaultBrowserCard(requireContext()), + isNotDefaultBrowser(requireContext()), canShowNotificationPage(requireContext()), canShowAddWidgetCard(), ) @@ -63,6 +63,7 @@ class OnboardingFragment : Fragment() { @SuppressLint("SourceLockedOrientationActivity") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val context = requireContext() if (pagesToDisplay.isEmpty()) { /* do not continue if there's no onboarding pages to display */ onFinish(null) @@ -72,8 +73,14 @@ class OnboardingFragment : Fragment() { activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT } val filter = IntentFilter(WidgetPinnedReceiver.ACTION) - LocalBroadcastManager.getInstance(requireContext()) + LocalBroadcastManager.getInstance(context) .registerReceiver(pinAppWidgetReceiver, filter) + + if (isNotDefaultBrowser(context) && + pagesToDisplay.none { it.type == OnboardingPageUiData.Type.DEFAULT_BROWSER } + ) { + promptToSetAsDefaultBrowser() + } } @RequiresApi(Build.VERSION_CODES.TIRAMISU) @@ -109,11 +116,7 @@ class OnboardingFragment : Fragment() { OnboardingScreen( pagesToDisplay = pagesToDisplay, onMakeFirefoxDefaultClick = { - activity?.openSetDefaultBrowserOption(useCustomTab = true) - telemetryRecorder.onSetToDefaultClick( - sequenceId = pagesToDisplay.telemetrySequenceId(), - sequencePosition = pagesToDisplay.sequencePosition(OnboardingPageUiData.Type.DEFAULT_BROWSER), - ) + promptToSetAsDefaultBrowser() }, onSkipDefaultClick = { telemetryRecorder.onSkipSetToDefaultClick( @@ -212,7 +215,7 @@ class OnboardingFragment : Fragment() { ) } - private fun shouldShowDefaultBrowserCard(context: Context) = + private fun isNotDefaultBrowser(context: Context) = !BrowsersCache.all(context.applicationContext).isDefaultBrowser private fun canShowNotificationPage(context: Context) = @@ -260,4 +263,12 @@ class OnboardingFragment : Fragment() { ) { condition -> jexlHelper.evalJexlSafe(condition) } } } + + private fun promptToSetAsDefaultBrowser() { + activity?.openSetDefaultBrowserOption(useCustomTab = true) + telemetryRecorder.onSetToDefaultClick( + sequenceId = pagesToDisplay.telemetrySequenceId(), + sequencePosition = pagesToDisplay.sequencePosition(OnboardingPageUiData.Type.DEFAULT_BROWSER), + ) + } } From 260cff62bf76ac2c77f0bf1144f1c1b090bcbdce Mon Sep 17 00:00:00 2001 From: AndiAJ Date: Tue, 20 Feb 2024 15:42:39 +0200 Subject: [PATCH 022/238] Bug 1881228 - Remove redundant assertion functions from SettingsSubMenuHomepageRobot --- .../ui/robots/SettingsSubMenuHomepageRobot.kt | 493 +++++++++--------- 1 file changed, 245 insertions(+), 248 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuHomepageRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuHomepageRobot.kt index 6585616f2..b04ca8a30 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuHomepageRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuHomepageRobot.kt @@ -43,24 +43,218 @@ class SettingsSubMenuHomepageRobot { pocketSwitchEnabled: Boolean = true, sponsoredStoriesCheckBox: Boolean = true, ) { - assertShortcutsButton() - assertShortcutsSwitchState(shortcutsSwitchEnabled) - assertSponsoredShortcutsButton() - assertSponsoredShortcutsCheckBox(sponsoredShortcutsCheckBox) - assertJumpBackInButton() - assertJumpBackInSwitchState(jumpBackInSwitchEnabled) - assertRecentBookmarksButton() - assertRecentBookmarksSwitchState(recentBookmarksSwitchEnabled) - assertRecentlyVisitedButton() - assertRecentlyVisitedSwitchState(recentlyVisitedSwitchEnabled) - assertPocketButton() - assertPocketSwitchState(pocketSwitchEnabled) - assertSponsoredStoriesButton() - assertSponsoredStoriesCheckBox(sponsoredStoriesCheckBox) - assertOpeningScreenHeading() - assertHomepageButton() - assertLastTabButton() - assertHomepageAfterFourHoursButton() + shortcutsButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + + if (shortcutsSwitchEnabled) { + shortcutsButton() + .check( + matches( + TestHelper.hasCousin( + Matchers.allOf( + withClassName(Matchers.endsWith("Switch")), + isChecked(), + ), + ), + ), + ) + } else { + shortcutsButton() + .check( + matches( + TestHelper.hasCousin( + Matchers.allOf( + withClassName(Matchers.endsWith("Switch")), + isNotChecked(), + ), + ), + ), + ) + } + + sponsoredShortcutsButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + + if (sponsoredShortcutsCheckBox) { + sponsoredShortcutsButton() + .check( + matches( + hasSibling( + ViewMatchers.withChild( + allOf( + withClassName(CoreMatchers.endsWith("CheckBox")), + isChecked(), + ), + ), + ), + ), + ) + } else { + sponsoredShortcutsButton() + .check( + matches( + hasSibling( + ViewMatchers.withChild( + allOf( + withClassName(CoreMatchers.endsWith("CheckBox")), + isNotChecked(), + ), + ), + ), + ), + ) + } + + jumpBackInButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + + if (jumpBackInSwitchEnabled) { + jumpBackInButton() + .check( + matches( + TestHelper.hasCousin( + Matchers.allOf( + withClassName(Matchers.endsWith("Switch")), + isChecked(), + ), + ), + ), + ) + } else { + jumpBackInButton() + .check( + matches( + TestHelper.hasCousin( + Matchers.allOf( + withClassName(Matchers.endsWith("Switch")), + isNotChecked(), + ), + ), + ), + ) + } + + recentBookmarksButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + + if (recentBookmarksSwitchEnabled) { + recentBookmarksButton() + .check( + matches( + TestHelper.hasCousin( + Matchers.allOf( + withClassName(Matchers.endsWith("Switch")), + isChecked(), + ), + ), + ), + ) + } else { + recentBookmarksButton() + .check( + matches( + TestHelper.hasCousin( + Matchers.allOf( + withClassName(Matchers.endsWith("Switch")), + isNotChecked(), + ), + ), + ), + ) + } + + recentlyVisitedButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + + if (recentlyVisitedSwitchEnabled) { + recentlyVisitedButton() + .check( + matches( + TestHelper.hasCousin( + Matchers.allOf( + withClassName(Matchers.endsWith("Switch")), + isChecked(), + ), + ), + ), + ) + } else { + recentlyVisitedButton() + .check( + matches( + TestHelper.hasCousin( + Matchers.allOf( + withClassName(Matchers.endsWith("Switch")), + isNotChecked(), + ), + ), + ), + ) + } + + pocketButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + + if (pocketSwitchEnabled) { + pocketButton() + .check( + matches( + TestHelper.hasCousin( + Matchers.allOf( + withClassName(Matchers.endsWith("Switch")), + isChecked(), + ), + ), + ), + ) + } else { + pocketButton() + .check( + matches( + TestHelper.hasCousin( + Matchers.allOf( + withClassName(Matchers.endsWith("Switch")), + isNotChecked(), + ), + ), + ), + ) + } + + sponsoredStoriesButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + + if (sponsoredStoriesCheckBox) { + sponsoredStoriesButton() + .check( + matches( + hasSibling( + ViewMatchers.withChild( + allOf( + withClassName(CoreMatchers.endsWith("CheckBox")), + isChecked(), + ), + ), + ), + ), + ) + } else { + sponsoredStoriesButton() + .check( + matches( + hasSibling( + ViewMatchers.withChild( + allOf( + withClassName(CoreMatchers.endsWith("CheckBox")), + isNotChecked(), + ), + ), + ), + ), + ) + } + + openingScreenHeading().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + + homepageButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + + lastTabButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + + homepageAfterFourHoursButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + Log.i(Constants.TAG, "verifyHomePageView: Verified the home page elements") } @@ -94,12 +288,42 @@ class SettingsSubMenuHomepageRobot { } } - fun openWallpapersMenu() = wallpapersMenuButton.click() + fun openWallpapersMenu() = wallpapersMenuButton().click() fun selectWallpaper(wallpaperName: String) = mDevice.findObject(UiSelector().description(wallpaperName)).click() - fun verifySponsoredShortcutsCheckBox(checked: Boolean) = assertSponsoredShortcutsCheckBox(checked) + fun verifySponsoredShortcutsCheckBox(checked: Boolean) { + if (checked) { + sponsoredShortcutsButton() + .check( + matches( + hasSibling( + ViewMatchers.withChild( + allOf( + withClassName(CoreMatchers.endsWith("CheckBox")), + isChecked(), + ), + ), + ), + ), + ) + } else { + sponsoredShortcutsButton() + .check( + matches( + hasSibling( + ViewMatchers.withChild( + allOf( + withClassName(CoreMatchers.endsWith("CheckBox")), + isNotChecked(), + ), + ), + ), + ), + ) + } + } class Transition { @@ -180,231 +404,4 @@ private fun homepageAfterFourHoursButton() = private fun goBackButton() = onView(allOf(withContentDescription(R.string.action_bar_up_description))) -private fun assertShortcutsButton() = - shortcutsButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) -private fun assertSponsoredShortcutsButton() = - sponsoredShortcutsButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) -private fun assertJumpBackInButton() = - jumpBackInButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) -private fun assertRecentBookmarksButton() = - recentBookmarksButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) -private fun assertRecentlyVisitedButton() = - recentlyVisitedButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) -private fun assertPocketButton() = - pocketButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) -private fun assertSponsoredStoriesButton() = - sponsoredStoriesButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) -private fun assertOpeningScreenHeading() = - openingScreenHeading().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) -private fun assertHomepageButton() = - homepageButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) -private fun assertLastTabButton() = - lastTabButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) -private fun assertHomepageAfterFourHoursButton() = - homepageAfterFourHoursButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - -fun assertShortcutsSwitchState(enabled: Boolean) { - if (enabled) { - shortcutsButton() - .check( - matches( - TestHelper.hasCousin( - Matchers.allOf( - withClassName(Matchers.endsWith("Switch")), - isChecked(), - ), - ), - ), - ) - } else { - shortcutsButton() - .check( - matches( - TestHelper.hasCousin( - Matchers.allOf( - withClassName(Matchers.endsWith("Switch")), - isNotChecked(), - ), - ), - ), - ) - } -} - -fun assertSponsoredShortcutsCheckBox(checked: Boolean) { - if (checked) { - sponsoredShortcutsButton() - .check( - matches( - hasSibling( - ViewMatchers.withChild( - allOf( - withClassName(CoreMatchers.endsWith("CheckBox")), - isChecked(), - ), - ), - ), - ), - ) - } else { - sponsoredShortcutsButton() - .check( - matches( - hasSibling( - ViewMatchers.withChild( - allOf( - withClassName(CoreMatchers.endsWith("CheckBox")), - isNotChecked(), - ), - ), - ), - ), - ) - } -} - -fun assertJumpBackInSwitchState(enabled: Boolean) { - if (enabled) { - jumpBackInButton() - .check( - matches( - TestHelper.hasCousin( - Matchers.allOf( - withClassName(Matchers.endsWith("Switch")), - isChecked(), - ), - ), - ), - ) - } else { - jumpBackInButton() - .check( - matches( - TestHelper.hasCousin( - Matchers.allOf( - withClassName(Matchers.endsWith("Switch")), - isNotChecked(), - ), - ), - ), - ) - } -} - -fun assertRecentBookmarksSwitchState(enabled: Boolean) { - if (enabled) { - recentBookmarksButton() - .check( - matches( - TestHelper.hasCousin( - Matchers.allOf( - withClassName(Matchers.endsWith("Switch")), - isChecked(), - ), - ), - ), - ) - } else { - recentBookmarksButton() - .check( - matches( - TestHelper.hasCousin( - Matchers.allOf( - withClassName(Matchers.endsWith("Switch")), - isNotChecked(), - ), - ), - ), - ) - } -} - -fun assertRecentlyVisitedSwitchState(enabled: Boolean) { - if (enabled) { - recentlyVisitedButton() - .check( - matches( - TestHelper.hasCousin( - Matchers.allOf( - withClassName(Matchers.endsWith("Switch")), - isChecked(), - ), - ), - ), - ) - } else { - recentlyVisitedButton() - .check( - matches( - TestHelper.hasCousin( - Matchers.allOf( - withClassName(Matchers.endsWith("Switch")), - isNotChecked(), - ), - ), - ), - ) - } -} - -fun assertPocketSwitchState(enabled: Boolean) { - if (enabled) { - pocketButton() - .check( - matches( - TestHelper.hasCousin( - Matchers.allOf( - withClassName(Matchers.endsWith("Switch")), - isChecked(), - ), - ), - ), - ) - } else { - pocketButton() - .check( - matches( - TestHelper.hasCousin( - Matchers.allOf( - withClassName(Matchers.endsWith("Switch")), - isNotChecked(), - ), - ), - ), - ) - } -} - -fun assertSponsoredStoriesCheckBox(checked: Boolean) { - if (checked) { - sponsoredStoriesButton() - .check( - matches( - hasSibling( - ViewMatchers.withChild( - allOf( - withClassName(CoreMatchers.endsWith("CheckBox")), - isChecked(), - ), - ), - ), - ), - ) - } else { - sponsoredStoriesButton() - .check( - matches( - hasSibling( - ViewMatchers.withChild( - allOf( - withClassName(CoreMatchers.endsWith("CheckBox")), - isNotChecked(), - ), - ), - ), - ), - ) - } -} - -private val wallpapersMenuButton = onView(withText("Wallpapers")) +private fun wallpapersMenuButton() = onView(withText("Wallpapers")) From 878676dd987d24a57a9876e6e31c6ac1cba7d39d Mon Sep 17 00:00:00 2001 From: AndiAJ Date: Tue, 20 Feb 2024 16:21:41 +0200 Subject: [PATCH 023/238] Bug 1881228 - Add logs to SettingsSubMenuHomepageRobot --- .../ui/robots/SettingsSubMenuHomepageRobot.kt | 148 ++++++++++++++---- 1 file changed, 115 insertions(+), 33 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuHomepageRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuHomepageRobot.kt index b04ca8a30..9d1c919a3 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuHomepageRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuHomepageRobot.kt @@ -22,7 +22,7 @@ import org.hamcrest.CoreMatchers import org.hamcrest.CoreMatchers.allOf import org.hamcrest.Matchers import org.mozilla.fenix.R -import org.mozilla.fenix.helpers.Constants +import org.mozilla.fenix.helpers.Constants.TAG import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort import org.mozilla.fenix.helpers.TestHelper import org.mozilla.fenix.helpers.TestHelper.mDevice @@ -43,9 +43,11 @@ class SettingsSubMenuHomepageRobot { pocketSwitchEnabled: Boolean = true, sponsoredStoriesCheckBox: Boolean = true, ) { + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Shortcuts\" option is visible") shortcutsButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - + Log.i(TAG, "verifyHomePageView: Verified that the \"Shortcuts\" option is visible") if (shortcutsSwitchEnabled) { + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Shortcuts\" toggle is checked") shortcutsButton() .check( matches( @@ -57,7 +59,9 @@ class SettingsSubMenuHomepageRobot { ), ), ) + Log.i(TAG, "verifyHomePageView: Verified that the \"Shortcuts\" toggle is checked") } else { + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Shortcuts\" toggle is not checked") shortcutsButton() .check( matches( @@ -69,11 +73,13 @@ class SettingsSubMenuHomepageRobot { ), ), ) + Log.i(TAG, "verifyHomePageView: Verified that the \"Shortcuts\" toggle is not checked") } - + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Sponsored shortcuts\" option is visible") sponsoredShortcutsButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - + Log.i(TAG, "verifyHomePageView: Verified that the \"Sponsored shortcuts\" option is visible") if (sponsoredShortcutsCheckBox) { + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Sponsored shortcuts\" check box is checked") sponsoredShortcutsButton() .check( matches( @@ -87,7 +93,9 @@ class SettingsSubMenuHomepageRobot { ), ), ) + Log.i(TAG, "verifyHomePageView: Verified that the \"Sponsored shortcuts\" check box is checked") } else { + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Sponsored shortcuts\" check box is not checked") sponsoredShortcutsButton() .check( matches( @@ -101,11 +109,13 @@ class SettingsSubMenuHomepageRobot { ), ), ) + Log.i(TAG, "verifyHomePageView: Verified that the \"Sponsored shortcuts\" check box is not checked") } - + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Jump back in\" option is visible") jumpBackInButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - + Log.i(TAG, "verifyHomePageView: Verified that the \"Jump back in\" option is visible") if (jumpBackInSwitchEnabled) { + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Jump back in\" toggle is checked") jumpBackInButton() .check( matches( @@ -117,7 +127,9 @@ class SettingsSubMenuHomepageRobot { ), ), ) + Log.i(TAG, "verifyHomePageView: Verified that the \"Jump back in\" toggle is checked") } else { + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Jump back in\" toggle is not checked") jumpBackInButton() .check( matches( @@ -129,11 +141,13 @@ class SettingsSubMenuHomepageRobot { ), ), ) + Log.i(TAG, "verifyHomePageView: Verified that the \"Jump back in\" toggle is not checked") } - + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Recent bookmarks\" option is visible") recentBookmarksButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - + Log.i(TAG, "verifyHomePageView: Verified that the \"Recent bookmarks\" option is visible") if (recentBookmarksSwitchEnabled) { + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Recent bookmarks\" toggle is checked") recentBookmarksButton() .check( matches( @@ -145,7 +159,9 @@ class SettingsSubMenuHomepageRobot { ), ), ) + Log.i(TAG, "verifyHomePageView: Verified that the \"Recent bookmarks\" toggle is checked") } else { + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Recent bookmarks\" toggle is not checked") recentBookmarksButton() .check( matches( @@ -157,11 +173,13 @@ class SettingsSubMenuHomepageRobot { ), ), ) + Log.i(TAG, "verifyHomePageView: Verified that the \"Recent bookmarks\" toggle is not checked") } - + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Recently visited\" option is visible") recentlyVisitedButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - + Log.i(TAG, "verifyHomePageView: Verified that the \"Recently visited\" option is visible") if (recentlyVisitedSwitchEnabled) { + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Recently visited\" toggle is checked") recentlyVisitedButton() .check( matches( @@ -173,7 +191,9 @@ class SettingsSubMenuHomepageRobot { ), ), ) + Log.i(TAG, "verifyHomePageView: Verified that the \"Recently visited\" toggle is checked") } else { + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Recently visited\" toggle is not checked") recentlyVisitedButton() .check( matches( @@ -185,11 +205,13 @@ class SettingsSubMenuHomepageRobot { ), ), ) + Log.i(TAG, "verifyHomePageView: Verified that the \"Recently visited\" toggle is not checked") } - + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Thought-provoking stories\" option is visible") pocketButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - + Log.i(TAG, "verifyHomePageView: Verified that the \"Thought-provoking stories\" option is visible") if (pocketSwitchEnabled) { + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Thought-provoking stories\" toggle is checked") pocketButton() .check( matches( @@ -201,7 +223,9 @@ class SettingsSubMenuHomepageRobot { ), ), ) + Log.i(TAG, "verifyHomePageView: Verified that the \"Thought-provoking stories\" toggle is checked") } else { + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Thought-provoking stories\" toggle is not checked") pocketButton() .check( matches( @@ -213,11 +237,13 @@ class SettingsSubMenuHomepageRobot { ), ), ) + Log.i(TAG, "verifyHomePageView: Verified that the \"Thought-provoking stories\" toggle is not checked") } - + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Sponsored stories\" option is visible") sponsoredStoriesButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - + Log.i(TAG, "verifyHomePageView: Verified that the \"Sponsored stories\" option is visible") if (sponsoredStoriesCheckBox) { + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Sponsored stories\" check box is checked") sponsoredStoriesButton() .check( matches( @@ -231,7 +257,9 @@ class SettingsSubMenuHomepageRobot { ), ), ) + Log.i(TAG, "verifyHomePageView: Verified that the \"Sponsored stories\" check box is checked") } else { + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Sponsored stories\" check box is not checked") sponsoredStoriesButton() .check( matches( @@ -245,56 +273,100 @@ class SettingsSubMenuHomepageRobot { ), ), ) + Log.i(TAG, "verifyHomePageView: Verified that the \"Sponsored stories\" check box is not checked") } - + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Opening screen\" heading is visible") openingScreenHeading().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - + Log.i(TAG, "verifyHomePageView: Verified that the \"Opening screen\" heading is visible") + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Homepage\" option is visible") homepageButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - + Log.i(TAG, "verifyHomePageView: Verified that the \"Homepage\" option is visible") + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Last tab\" option is visible") lastTabButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - + Log.i(TAG, "verifyHomePageView: Verified that the \"Last tab\" option is visible") + Log.i(TAG, "verifyHomePageView: Trying to verify that the \"Homepage after four hours of inactivity\" option is visible") homepageAfterFourHoursButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE))) - - Log.i(Constants.TAG, "verifyHomePageView: Verified the home page elements") + Log.i(TAG, "verifyHomePageView: Verified that the \"Homepage after four hours of inactivity\" option is visible") } - fun verifySelectedOpeningScreenOption(openingScreenOption: String) = + fun verifySelectedOpeningScreenOption(openingScreenOption: String) { + Log.i(TAG, "verifySelectedOpeningScreenOption: Trying to verify that the \"Opening screen\" option $openingScreenOption is checked") onView( allOf( withId(R.id.radio_button), hasSibling(withText(openingScreenOption)), ), ).check(matches(isChecked(true))) + Log.i(TAG, "verifySelectedOpeningScreenOption: Verified that the \"Opening screen\" option $openingScreenOption is checked") + } - fun clickShortcutsButton() = shortcutsButton().click() + fun clickShortcutsButton() { + Log.i(TAG, "clickShortcutsButton: Trying to click the \"Shortcuts\" option") + shortcutsButton().click() + Log.i(TAG, "clickShortcutsButton: Clicked the \"Shortcuts\" option") + } - fun clickSponsoredShortcuts() = sponsoredShortcutsButton().click() + fun clickSponsoredShortcuts() { + Log.i(TAG, "clickSponsoredShortcuts: Trying to click the \"Sponsored shortcuts\" option") + sponsoredShortcutsButton().click() + Log.i(TAG, "clickSponsoredShortcuts: Clicked the \"Sponsored shortcuts\" option") + } - fun clickJumpBackInButton() = jumpBackInButton().click() + fun clickJumpBackInButton() { + Log.i(TAG, "clickJumpBackInButton: Trying to click the \"Jump back in\" option") + jumpBackInButton().click() + Log.i(TAG, "clickJumpBackInButton: Clicked the \"Jump back in\" option") + } - fun clickRecentlyVisited() = recentlyVisitedButton().click() + fun clickRecentlyVisited() { + Log.i(TAG, "clickRecentlyVisited: Trying to click the \"Recently visited\" option") + recentlyVisitedButton().click() + Log.i(TAG, "clickRecentlyVisited: Clicked the \"Recently visited\" option") + } - fun clickRecentBookmarksButton() = recentBookmarksButton().click() + fun clickRecentBookmarksButton() { + Log.i(TAG, "clickRecentBookmarksButton: Trying to click the \"Recent bookmarks\" option") + recentBookmarksButton().click() + Log.i(TAG, "clickRecentBookmarksButton: Clicked the \"Recent bookmarks\" option") + } - fun clickRecentSearchesButton() = recentlyVisitedButton().click() + fun clickRecentSearchesButton() { + Log.i(TAG, "clickRecentSearchesButton: Trying to click the \"Recently visited\" option") + recentlyVisitedButton().click() + Log.i(TAG, "clickRecentSearchesButton: Clicked the \"Recently visited\" option") + } - fun clickPocketButton() = pocketButton().click() + fun clickPocketButton() { + Log.i(TAG, "clickPocketButton: Trying to click the \"Thought-provoking stories\" option") + pocketButton().click() + Log.i(TAG, "clickPocketButton: Clicked the \"Thought-provoking stories\" option") + } fun clickOpeningScreenOption(openingScreenOption: String) { + Log.i(TAG, "clickOpeningScreenOption: Trying to click \"Opening screen\" option: $openingScreenOption") when (openingScreenOption) { "Homepage" -> homepageButton().click() "Last tab" -> lastTabButton().click() "Homepage after four hours of inactivity" -> homepageAfterFourHoursButton().click() } + Log.i(TAG, "clickOpeningScreenOption: Clicked \"Opening screen\" option: $openingScreenOption") } - fun openWallpapersMenu() = wallpapersMenuButton().click() + fun openWallpapersMenu() { + Log.i(TAG, "openWallpapersMenu: Trying to click the \"Wallpapers\" option") + wallpapersMenuButton().click() + Log.i(TAG, "openWallpapersMenu: Clicked the \"Wallpapers\" option") + } - fun selectWallpaper(wallpaperName: String) = + fun selectWallpaper(wallpaperName: String) { + Log.i(TAG, "selectWallpaper: Trying to click wallpaper: $wallpaperName") mDevice.findObject(UiSelector().description(wallpaperName)).click() + Log.i(TAG, "selectWallpaper: Clicked wallpaper: $wallpaperName") + } fun verifySponsoredShortcutsCheckBox(checked: Boolean) { if (checked) { + Log.i(TAG, "verifySponsoredShortcutsCheckBox: Trying to verify that the \"Sponsored shortcuts\" check box is checked") sponsoredShortcutsButton() .check( matches( @@ -308,7 +380,9 @@ class SettingsSubMenuHomepageRobot { ), ), ) + Log.i(TAG, "verifySponsoredShortcutsCheckBox: Verified that the \"Sponsored shortcuts\" check box is checked") } else { + Log.i(TAG, "verifySponsoredShortcutsCheckBox: Trying to verify that the \"Sponsored shortcuts\" check box is not checked") sponsoredShortcutsButton() .check( matches( @@ -322,29 +396,37 @@ class SettingsSubMenuHomepageRobot { ), ), ) + Log.i(TAG, "verifySponsoredShortcutsCheckBox: Verified that the \"Sponsored shortcuts\" check box is not checked") } } class Transition { fun goBackToHomeScreen(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition { + Log.i(TAG, "goBackToHomeScreen: Trying to click the navigate up toolbar button") goBackButton().click() + Log.i(TAG, "goBackToHomeScreen: Clicked the navigate up toolbar button") HomeScreenRobot().interact() return HomeScreenRobot.Transition() } fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition { + Log.i(TAG, "goBack: Trying to click the navigate up toolbar button") goBackButton().click() + Log.i(TAG, "goBack: Clicked the navigate up toolbar button") SettingsRobot().interact() return SettingsRobot.Transition() } fun clickSnackBarViewButton(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition { - val snackBarButton = mDevice.findObject(UiSelector().text("VIEW")) - snackBarButton.waitForExists(waitingTimeShort) - snackBarButton.click() + Log.i(TAG, "clickSnackBarViewButton: Waiting for $waitingTimeShort ms for \"VIEW\" snackbar button to exist") + mDevice.findObject(UiSelector().text("VIEW")).waitForExists(waitingTimeShort) + Log.i(TAG, "clickSnackBarViewButton: Waited for $waitingTimeShort ms for \"VIEW\" snackbar button to exist") + Log.i(TAG, "clickSnackBarViewButton: Trying to click the \"VIEW\" snackbar button") + mDevice.findObject(UiSelector().text("VIEW")).click() + Log.i(TAG, "clickSnackBarViewButton: Clicked the \"VIEW\" snackbar button") HomeScreenRobot().interact() return HomeScreenRobot.Transition() From 875e7ac3990fc57665e8b536eba429f13639957d Mon Sep 17 00:00:00 2001 From: AndiAJ Date: Wed, 21 Feb 2024 15:48:26 +0200 Subject: [PATCH 024/238] Bug 1881250 - Convert private variables to functions so they don't get initialised --- .../ui/robots/SettingsSubMenuLanguageRobot.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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 f1feb3bd1..83c92e503 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 @@ -21,8 +21,8 @@ import org.mozilla.fenix.helpers.click class SettingsSubMenuLanguageRobot { fun selectLanguage(language: String) { - languagesList.waitForExists(waitingTime) - languagesList + languagesList().waitForExists(waitingTime) + languagesList() .getChildByText(UiSelector().text(language), language) .click() } @@ -35,9 +35,9 @@ class SettingsSubMenuLanguageRobot { fun verifyLanguageHeaderIsTranslated(translation: String) = assertUIObjectExists(itemWithText(translation)) fun verifySelectedLanguage(language: String) { - languagesList.waitForExists(waitingTime) + languagesList().waitForExists(waitingTime) assertUIObjectExists( - languagesList + languagesList() .getChildByText(UiSelector().text(language), language, true) .getFromParent(UiSelector().resourceId("$packageName:id/locale_selected_icon")), ) @@ -48,8 +48,8 @@ class SettingsSubMenuLanguageRobot { } fun typeInSearchBar(text: String) { - searchBar.waitForExists(waitingTime) - searchBar.text = text + searchBar().waitForExists(waitingTime) + searchBar().text = text } fun verifySearchResultsContains(languageName: String) = @@ -59,7 +59,7 @@ class SettingsSubMenuLanguageRobot { onView(withId(R.id.search_close_btn)).click() } - fun verifyLanguageListIsDisplayed() = assertUIObjectExists(languagesList) + fun verifyLanguageListIsDisplayed() = assertUIObjectExists(languagesList()) class Transition { @@ -76,7 +76,7 @@ class SettingsSubMenuLanguageRobot { private fun goBackButton() = onView(CoreMatchers.allOf(ViewMatchers.withContentDescription("Navigate up"))) -private val languagesList = +private fun languagesList() = UiScrollable( UiSelector() .resourceId("$packageName:id/locale_list") @@ -85,5 +85,5 @@ private val languagesList = private fun language(name: String) = mDevice.findObject(UiSelector().text(name)) -private val searchBar = +private fun searchBar() = mDevice.findObject(UiSelector().resourceId("$packageName:id/search_src_text")) From fc0b206d1f61a602cf8a24564fbbb7fd4893d438 Mon Sep 17 00:00:00 2001 From: AndiAJ Date: Wed, 21 Feb 2024 16:08:17 +0200 Subject: [PATCH 025/238] Bug 1881250 - Add logs to SettingsSubMenuLanguageRobot --- .../ui/robots/SettingsSubMenuLanguageRobot.kt | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) 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 83c92e503..7bca7bb37 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 @@ -4,6 +4,7 @@ package org.mozilla.fenix.ui.robots +import android.util.Log import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions import androidx.test.espresso.matcher.ViewMatchers @@ -12,6 +13,7 @@ import androidx.test.uiautomator.UiScrollable import androidx.test.uiautomator.UiSelector import org.hamcrest.CoreMatchers import org.mozilla.fenix.R +import org.mozilla.fenix.helpers.Constants.TAG import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists import org.mozilla.fenix.helpers.MatcherHelper.itemWithText import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime @@ -21,21 +23,31 @@ import org.mozilla.fenix.helpers.click class SettingsSubMenuLanguageRobot { fun selectLanguage(language: String) { + Log.i(TAG, "selectLanguage: Waiting for $waitingTime ms for language list to exist") languagesList().waitForExists(waitingTime) + Log.i(TAG, "selectLanguage: Waited for $waitingTime ms for language list to exist") + Log.i(TAG, "selectLanguage: Trying to click language: $language") languagesList() .getChildByText(UiSelector().text(language), language) .click() + Log.i(TAG, "selectLanguage: Clicked language: $language") } fun selectLanguageSearchResult(languageName: String) { + Log.i(TAG, "selectLanguageSearchResult: Waiting for $waitingTime ms for language list to exist") language(languageName).waitForExists(waitingTime) + Log.i(TAG, "selectLanguageSearchResult: Waited for $waitingTime ms for language list to exist") + Log.i(TAG, "selectLanguageSearchResult: Trying to click language: $languageName") language(languageName).click() + Log.i(TAG, "selectLanguageSearchResult: Clicked language: $languageName") } fun verifyLanguageHeaderIsTranslated(translation: String) = assertUIObjectExists(itemWithText(translation)) fun verifySelectedLanguage(language: String) { + Log.i(TAG, "verifySelectedLanguage: Waiting for $waitingTime ms for language list to exist") languagesList().waitForExists(waitingTime) + Log.i(TAG, "verifySelectedLanguage: Waited for $waitingTime ms for language list to exist") assertUIObjectExists( languagesList() .getChildByText(UiSelector().text(language), language, true) @@ -44,19 +56,27 @@ class SettingsSubMenuLanguageRobot { } fun openSearchBar() { + Log.i(TAG, "openSearchBar: Trying to click the search bar") onView(withId(R.id.search)).click() + Log.i(TAG, "openSearchBar: Clicked the search bar") } fun typeInSearchBar(text: String) { + Log.i(TAG, "typeInSearchBar: Waiting for $waitingTime ms for search bar to exist") searchBar().waitForExists(waitingTime) + Log.i(TAG, "typeInSearchBar: Waited for $waitingTime ms for search bar to exist") + Log.i(TAG, "typeInSearchBar: Trying to set search bar text to: $text") searchBar().text = text + Log.i(TAG, "typeInSearchBar: Search bar text was set to: $text") } fun verifySearchResultsContains(languageName: String) = assertUIObjectExists(language(languageName)) fun clearSearchBar() { + Log.i(TAG, "clearSearchBar: Trying to click the clear search bar button") onView(withId(R.id.search_close_btn)).click() + Log.i(TAG, "clearSearchBar: Clicked the clear search bar button") } fun verifyLanguageListIsDisplayed() = assertUIObjectExists(languagesList()) @@ -64,8 +84,12 @@ class SettingsSubMenuLanguageRobot { class Transition { fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition { + Log.i(TAG, "goBack: Waiting for device to be idle") mDevice.waitForIdle() + Log.i(TAG, "goBack: Waited for device to be idle") + Log.i(TAG, "goBack: Trying to click the navigate up button") goBackButton().perform(ViewActions.click()) + Log.i(TAG, "goBack: Clicked the navigate up button") SettingsRobot().interact() return SettingsRobot.Transition() From 64b636d2a9b575690268870a7e3562e636951f20 Mon Sep 17 00:00:00 2001 From: James Hugman Date: Mon, 29 Jan 2024 13:03:54 +0000 Subject: [PATCH 026/238] =?UTF-8?q?Bug=201880476=20=E2=80=94=20Move=20nimb?= =?UTF-8?q?us=20from=20components.analytics=20to=20components.nimbus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mozilla/fenix/helpers/Experimentation.kt | 5 +- .../fenix/ui/NimbusMessagingMessageTest.kt | 9 +- .../fenix/ui/NimbusMessagingTriggerTest.kt | 2 +- .../org/mozilla/fenix/FenixApplication.kt | 4 +- .../java/org/mozilla/fenix/HomeActivity.kt | 6 +- .../org/mozilla/fenix/components/Analytics.kt | 20 ----- .../fenix/components/BackgroundServices.kt | 2 +- .../mozilla/fenix/components/Components.kt | 3 +- .../fenix/components/NimbusComponents.kt | 88 +++++++++++++++++++ .../org/mozilla/fenix/home/HomeFragment.kt | 2 +- .../messaging/MessageNotificationWorker.kt | 6 +- .../fenix/nimbus/NimbusBranchesFragment.kt | 4 +- .../fenix/nimbus/NimbusExperimentsFragment.kt | 2 +- .../fenix/onboarding/OnboardingFragment.kt | 2 +- .../fenix/settings/DataChoicesFragment.kt | 2 +- .../fenix/settings/studies/StudiesFragment.kt | 2 +- .../components/BackgroundServicesTest.kt | 5 +- 17 files changed, 114 insertions(+), 50 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/components/NimbusComponents.kt diff --git a/app/src/androidTest/java/org/mozilla/fenix/helpers/Experimentation.kt b/app/src/androidTest/java/org/mozilla/fenix/helpers/Experimentation.kt index 34f4d15e6..4c320a868 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/helpers/Experimentation.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/helpers/Experimentation.kt @@ -9,11 +9,8 @@ import org.mozilla.fenix.ext.components import org.mozilla.fenix.helpers.TestHelper.appContext object Experimentation { - val experiments = - appContext.components.analytics.experiments - fun withHelper(block: NimbusMessagingHelperInterface.() -> Unit) { - val helper = experiments.createMessageHelper() + val helper = appContext.components.nimbus.createJexlHelper() block(helper) } } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingMessageTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingMessageTest.kt index 87b107957..c353b668f 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingMessageTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingMessageTest.kt @@ -20,7 +20,6 @@ import org.junit.Test import org.mozilla.fenix.ext.components import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.TestHelper -import org.mozilla.fenix.messaging.CustomAttributeProvider /** * This test is to test the integrity of messages hardcoded in the FML. @@ -35,7 +34,7 @@ class NimbusMessagingMessageTest { private lateinit var context: Context private val storage - get() = context.components.analytics.messagingStorage + get() = context.components.nimbus.messagingStorage @get:Rule val activityTestRule = @@ -73,10 +72,7 @@ class NimbusMessagingMessageTest { */ @Test fun testAllMessageTriggers() = runTest { - val nimbus = context.components.analytics.experiments - val helper = nimbus.createMessageHelper( - CustomAttributeProvider.getCustomAttributes(context), - ) + val helper = context.components.nimbus.createJexlHelper() val messages = storage.getMessages() messages.forEach { message -> storage.isMessageEligible(message, helper) @@ -84,6 +80,7 @@ class NimbusMessagingMessageTest { fail("${message.id} has a problem with its JEXL trigger: ${storage.malFormedMap.keys}") } } + helper.destroy() } private fun checkIsLocalized(string: String) { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingTriggerTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingTriggerTest.kt index 6ba04c9e8..e061d6845 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingTriggerTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingTriggerTest.kt @@ -38,7 +38,7 @@ class NimbusMessagingTriggerTest { @Before fun setUp() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - nimbus = TestHelper.appContext.components.analytics.experiments + nimbus = TestHelper.appContext.components.nimbus.sdk feature = FxNimbusMessaging.features.messaging.value() } diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index 6dfd7a069..f87e5a0f9 100644 --- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -433,7 +433,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider { fun queueNimbusFetchInForeground() { queue.runIfReadyOrQueue { GlobalScope.launch(Dispatchers.IO) { - components.analytics.experiments.maybeFetchExperiments( + components.nimbus.sdk.maybeFetchExperiments( context = this@FenixApplication, ) } @@ -502,7 +502,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider { beginSetupMegazord() // This lazily constructs the Nimbus object… - val nimbus = components.analytics.experiments + val nimbus = components.nimbus.sdk // … which we then can populate the feature configuration. FxNimbus.initialize { nimbus } } diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index c649a8dd0..1bc06595f 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -225,7 +225,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { val startTimeProfiler = components.core.engine.profiler?.getProfilerTime() // Setup nimbus-cli tooling. This is a NOOP when launching normally. - components.analytics.experiments.initializeTooling(applicationContext, intent) + components.nimbus.sdk.initializeTooling(applicationContext, intent) components.strictMode.attachListenerToDisablePenaltyDeath(supportFragmentManager) MarkersFragmentLifecycleCallbacks.register(supportFragmentManager, components.core.engine) @@ -354,7 +354,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { ?.also { Events.appOpened.record(Events.AppOpenedExtra(it)) // This will record an event in Nimbus' internal event store. Used for behavioral targeting - components.analytics.experiments.recordEvent("app_opened") + components.nimbus.events.recordEvent("app_opened") if (safeIntent.action.equals(ACTION_OPEN_PRIVATE_TAB) && it == APP_ICON) { AppIcon.newPrivateTabTapped.record(NoExtras()) @@ -1221,7 +1221,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { } private suspend fun showFullscreenMessageIfNeeded(context: Context) { - val messagingStorage = context.components.analytics.messagingStorage + val messagingStorage = context.components.nimbus.messagingStorage val messages = messagingStorage.getMessages() val nextMessage = messagingStorage.getNextMessage(FenixMessageSurfaceId.SURVEY, messages) diff --git a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt index b68ea5e14..0208c135b 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt @@ -14,10 +14,6 @@ import mozilla.components.lib.crash.sentry.SentryService import mozilla.components.lib.crash.service.CrashReporterService import mozilla.components.lib.crash.service.GleanCrashReporterService import mozilla.components.lib.crash.service.MozillaSocorroService -import mozilla.components.service.nimbus.NimbusApi -import mozilla.components.service.nimbus.messaging.FxNimbusMessaging -import mozilla.components.service.nimbus.messaging.NimbusMessagingStorage -import mozilla.components.service.nimbus.messaging.OnDiskMessageMetadataStorage import mozilla.components.support.ktx.android.content.isMainProcess import mozilla.components.support.utils.BrowsersCache import mozilla.components.support.utils.RunWhenReadyQueue @@ -33,10 +29,8 @@ import org.mozilla.fenix.components.metrics.InstallReferrerMetricsService import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.components.metrics.MetricsStorage import org.mozilla.fenix.crashes.CrashFactCollector -import org.mozilla.fenix.experiments.createNimbus import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.messaging.CustomAttributeProvider import org.mozilla.fenix.perf.lazyMonitored import org.mozilla.geckoview.BuildConfig.MOZ_APP_BUILDID import org.mozilla.geckoview.BuildConfig.MOZ_APP_VENDOR @@ -159,20 +153,6 @@ class Analytics( context.settings(), ) } - - val experiments: NimbusApi by lazyMonitored { - createNimbus(context, BuildConfig.NIMBUS_ENDPOINT) - } - - val messagingStorage by lazyMonitored { - NimbusMessagingStorage( - context = context, - metadataStorage = OnDiskMessageMetadataStorage(context), - nimbus = experiments, - messagingFeature = FxNimbusMessaging.features.messaging, - attributeProvider = CustomAttributeProvider, - ) - } } private fun isSentryEnabled() = !BuildConfig.SENTRY_TOKEN.isNullOrEmpty() diff --git a/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt b/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt index 250a380ca..9b6f4bfbd 100644 --- a/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt +++ b/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt @@ -230,7 +230,7 @@ internal class TelemetryAccountObserver( // User signed-in into an existing FxA account. AuthType.Signin -> { SyncAuth.signIn.record(NoExtras()) - context.components.analytics.experiments.recordEvent("sync_auth.sign_in") + context.components.nimbus.events.recordEvent("sync_auth.sign_in") } // User created a new FxA account. diff --git a/app/src/main/java/org/mozilla/fenix/components/Components.kt b/app/src/main/java/org/mozilla/fenix/components/Components.kt index 6a4ea2328..fd74fcb37 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Components.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Components.kt @@ -170,6 +170,7 @@ class Components(private val context: Context) { } val analytics by lazyMonitored { Analytics(context, performance.visualCompletenessQueue.queue) } + val nimbus by lazyMonitored { NimbusComponents(context) } val publicSuffixList by lazyMonitored { PublicSuffixList(context) } val clipboardHandler by lazyMonitored { ClipboardHandler(context) } val performance by lazyMonitored { PerformanceComponent() } @@ -231,7 +232,7 @@ class Components(private val context: Context) { context.pocketStoriesSelectedCategoriesDataStore, ), MessagingMiddleware( - messagingStorage = analytics.messagingStorage, + messagingStorage = nimbus.messagingStorage, ), MetricsMiddleware(metrics = analytics.metrics), ), diff --git a/app/src/main/java/org/mozilla/fenix/components/NimbusComponents.kt b/app/src/main/java/org/mozilla/fenix/components/NimbusComponents.kt new file mode 100644 index 000000000..a83b3ef59 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/components/NimbusComponents.kt @@ -0,0 +1,88 @@ +/* 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/. */ + +package org.mozilla.fenix.components + +import android.content.Context +import mozilla.components.service.nimbus.NimbusApi +import mozilla.components.service.nimbus.messaging.FxNimbusMessaging +import mozilla.components.service.nimbus.messaging.NimbusMessagingStorage +import mozilla.components.service.nimbus.messaging.OnDiskMessageMetadataStorage +import org.mozilla.experiments.nimbus.NimbusEventStore +import org.mozilla.experiments.nimbus.NimbusMessagingHelperInterface +import org.mozilla.fenix.BuildConfig +import org.mozilla.fenix.experiments.createNimbus +import org.mozilla.fenix.messaging.CustomAttributeProvider +import org.mozilla.fenix.perf.lazyMonitored + +/** + * Component group for access to Nimbus and other Nimbus services. + */ +class NimbusComponents(private val context: Context) { + + /** + * The main entry point for the Nimbus SDK. Note that almost all access to feature configuration + * should be mediated through a FML generated class, e.g. [FxNimbus]. + */ + val sdk: NimbusApi by lazyMonitored { + createNimbus(context, BuildConfig.NIMBUS_ENDPOINT) + } + + /** + * Convenience method for getting the event store from the SDK. + * + * Before EXP-4354, this is the main write API for recording events to drive + * messaging, experiments and onboarding. + * + * Following EXP-4354, clients will not need to write these events + * themselves. + * + * Read access to the event store should be done through + * the JEXL helper available from [createJexlHelper]. + */ + val events: NimbusEventStore by lazyMonitored { + sdk.events + } + + /** + * Create a new JEXL evaluator suitable for use by any feature. + * + * JEXL evaluator context is provided by the app and changes over time. + * + * For this reason, an evaluator should be not be stored or cached. + * + * Since it has a native peer, to avoid leaking memory, the helper's [destroy] method + * should be called after finishing the set of evaluations. + * + * This can be done automatically using the interface's `use` method, e.g. + * + * ``` + * val isEligible = nimbus.createJexlHelper().use { helper -> + * expressions.all { exp -> helper.evalJexl(exp) } + * } + * ``` + * + * The helper has access to all context needed to drive decisions + * about messaging, onboarding and experimentation. + * + * It also has a built-in cache. + */ + fun createJexlHelper(): NimbusMessagingHelperInterface = + messagingStorage.createMessagingHelper() + + /** + * Low level access to the messaging component. + * + * The app should access this through a [mozilla.components.service.nimbus.messaging.NimbusMessagingController]. + */ + val messagingStorage by lazyMonitored { + NimbusMessagingStorage( + context = context, + metadataStorage = OnDiskMessageMetadataStorage(context), + nimbus = sdk, + messagingFeature = FxNimbusMessaging.features.messaging, + attributeProvider = CustomAttributeProvider, + ) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 588737e35..24fb5ec9f 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -360,7 +360,7 @@ class HomeFragment : Fragment() { engine = components.core.engine, messageController = DefaultMessageController( appStore = components.appStore, - messagingController = FenixNimbusMessagingController(components.analytics.messagingStorage), + messagingController = FenixNimbusMessagingController(components.nimbus.messagingStorage), homeActivity = activity, ), store = store, diff --git a/app/src/main/java/org/mozilla/fenix/messaging/MessageNotificationWorker.kt b/app/src/main/java/org/mozilla/fenix/messaging/MessageNotificationWorker.kt index 6a8bd3ba7..f75b4ef28 100644 --- a/app/src/main/java/org/mozilla/fenix/messaging/MessageNotificationWorker.kt +++ b/app/src/main/java/org/mozilla/fenix/messaging/MessageNotificationWorker.kt @@ -45,7 +45,7 @@ class MessageNotificationWorker( override suspend fun doWork(): Result { val context = applicationContext - val messagingStorage = context.components.analytics.messagingStorage + val messagingStorage = context.components.nimbus.messagingStorage val messages = messagingStorage.getMessages() val nextMessage = messagingStorage.getNextMessage(FenixMessageSurfaceId.NOTIFICATION, messages) @@ -178,7 +178,7 @@ class NotificationDismissedService : LifecycleService() { if (intent != null) { val nimbusMessagingController = - FenixNimbusMessagingController(applicationContext.components.analytics.messagingStorage) + FenixNimbusMessagingController(applicationContext.components.nimbus.messagingStorage) lifecycleScope.launch { // Get the relevant message. @@ -209,7 +209,7 @@ class NotificationClickedReceiverActivity : ComponentActivity() { super.onCreate(savedInstanceState) val nimbusMessagingController = - FenixNimbusMessagingController(components.analytics.messagingStorage) + FenixNimbusMessagingController(components.nimbus.messagingStorage) lifecycleScope.launch { // Get the relevant message. diff --git a/app/src/main/java/org/mozilla/fenix/nimbus/NimbusBranchesFragment.kt b/app/src/main/java/org/mozilla/fenix/nimbus/NimbusBranchesFragment.kt index 9b0854cd5..f2ec44227 100644 --- a/app/src/main/java/org/mozilla/fenix/nimbus/NimbusBranchesFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/nimbus/NimbusBranchesFragment.kt @@ -51,7 +51,7 @@ class NimbusBranchesFragment : Fragment() { context = requireContext(), navController = findNavController(), nimbusBranchesStore = nimbusBranchesStore, - experiments = requireContext().components.analytics.experiments, + experiments = requireContext().components.nimbus.sdk, experimentId = args.experimentId, ) @@ -77,7 +77,7 @@ class NimbusBranchesFragment : Fragment() { private fun loadExperimentBranches() { lifecycleScope.launch(Dispatchers.IO) { try { - val experiments = requireContext().components.analytics.experiments + val experiments = requireContext().components.nimbus.sdk val branches = experiments.getExperimentBranches(args.experimentId) ?: emptyList() val selectedBranch = experiments.getExperimentBranch(args.experimentId) ?: "" diff --git a/app/src/main/java/org/mozilla/fenix/nimbus/NimbusExperimentsFragment.kt b/app/src/main/java/org/mozilla/fenix/nimbus/NimbusExperimentsFragment.kt index 37f50d6ed..3cfe906d7 100644 --- a/app/src/main/java/org/mozilla/fenix/nimbus/NimbusExperimentsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/nimbus/NimbusExperimentsFragment.kt @@ -35,7 +35,7 @@ class NimbusExperimentsFragment : Fragment() { setContent { FirefoxTheme { val experiments = - requireContext().components.analytics.experiments.getAvailableExperiments() + requireContext().components.nimbus.sdk.getAvailableExperiments() NimbusExperiments( experiments = experiments, diff --git a/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingFragment.kt b/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingFragment.kt index af4693681..97d87682e 100644 --- a/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/onboarding/OnboardingFragment.kt @@ -232,7 +232,7 @@ class OnboardingFragment : Fragment() { showAddWidgetPage: Boolean, ): List { val jexlConditions = FxNimbus.features.junoOnboarding.value().conditions - val jexlHelper = requireContext().components.analytics.messagingStorage.createMessagingHelper() + val jexlHelper = requireContext().components.nimbus.createJexlHelper() val privacyCaption = Caption( text = getString(R.string.juno_onboarding_privacy_notice_text), diff --git a/app/src/main/java/org/mozilla/fenix/settings/DataChoicesFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/DataChoicesFragment.kt index e029e2ebf..f64fecc63 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/DataChoicesFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/DataChoicesFragment.kt @@ -37,7 +37,7 @@ class DataChoicesFragment : PreferenceFragmentCompat() { // Reset experiment identifiers on both opt-in and opt-out; it's likely // that in future we will need to pass in the new telemetry client_id // to this method when the user opts back in. - context.components.analytics.experiments.resetTelemetryIdentifiers() + context.components.nimbus.sdk.resetTelemetryIdentifiers() } else if (key == getPreferenceKey(R.string.pref_key_marketing_telemetry)) { if (context.settings().isMarketingTelemetryEnabled) { context.components.analytics.metrics.start(MetricServiceType.Marketing) diff --git a/app/src/main/java/org/mozilla/fenix/settings/studies/StudiesFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/studies/StudiesFragment.kt index 87d9c6e1b..f2ecc0ea6 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/studies/StudiesFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/studies/StudiesFragment.kt @@ -30,7 +30,7 @@ class StudiesFragment : Fragment() { container: ViewGroup?, savedInstanceState: Bundle?, ): View { - val experiments = requireComponents.analytics.experiments + val experiments = requireComponents.nimbus.sdk _binding = SettingsStudiesBinding.inflate(inflater, container, false) val interactor = DefaultStudiesInteractor((activity as HomeActivity), experiments) StudiesView( diff --git a/app/src/test/java/org/mozilla/fenix/components/BackgroundServicesTest.kt b/app/src/test/java/org/mozilla/fenix/components/BackgroundServicesTest.kt index 5cf747049..3723d4977 100644 --- a/app/src/test/java/org/mozilla/fenix/components/BackgroundServicesTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/BackgroundServicesTest.kt @@ -56,8 +56,9 @@ class BackgroundServicesTest { val mockComponents: Components = mockk() every { mockComponents.settings } returns settings - every { mockComponents.analytics } returns mockk { - every { experiments } returns nimbus + every { mockComponents.nimbus } returns mockk { + every { sdk } returns nimbus + every { events } returns nimbus } every { context.components } returns mockComponents every { nimbus.recordEvent(any()) } returns Unit From 30fe200284f4d0979dd82ee36a31762c55522a4a Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 22 Feb 2024 00:03:29 +0000 Subject: [PATCH 027/238] Import translations from android-l10n --- app/src/main/res/values-br/strings.xml | 4 +++- app/src/main/res/values-nb-rNO/strings.xml | 2 +- app/src/main/res/values-uk/strings.xml | 14 +++++++------- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/src/main/res/values-br/strings.xml b/app/src/main/res/values-br/strings.xml index f9fc661f1..59e397fc1 100644 --- a/app/src/main/res/values-br/strings.xml +++ b/app/src/main/res/values-br/strings.xml @@ -2055,7 +2055,7 @@ Klask %s - + Digeriñ liammoù al lec’hiennoù, posteloù ha kemennadennoù e Firefox en un doare emgefreek. @@ -2249,6 +2249,8 @@ O treiñ + + Dibab ur yezh Ur gudenn zo bet gant an droidigezh. Klaskit en-dro. diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 86347ea1a..aef3dd0fc 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -721,7 +721,7 @@ Betalingskort - Betalingsmetoder + Betalingsmåter Adresser diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index b9f1506f0..8ce5dea5f 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -603,7 +603,7 @@ Власна збірка додатків - Гаразд + OK Скасувати @@ -926,7 +926,7 @@ Програма припинить застосовувати зміни - Гаразд + OK Скасувати @@ -1135,13 +1135,13 @@ Очистити дозволи - Гаразд + OK Скасувати Очистити дозвіл - Гаразд + OK Скасувати @@ -1249,7 +1249,7 @@ Переглянути - Гаразд + OK Скасувати @@ -1385,7 +1385,7 @@ Вебадреса недійсна. - Гаразд + OK Ви дійсно хочете видалити %1$s? @@ -2140,7 +2140,7 @@ Назва ярлика - Гаразд + OK Скасувати From 907d1986a33c9fc3d614a69f3b3bc93fb316c925 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 02:36:09 +0000 Subject: [PATCH 028/238] Bump cryptography Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.2 to 42.0.4. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/42.0.2...42.0.4) --- updated-dependencies: - dependency-name: cryptography dependency-type: indirect ... Signed-off-by: dependabot[bot] --- .../fenix/syncintegration/Pipfile.lock | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/syncintegration/Pipfile.lock b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/Pipfile.lock index 1f781e87a..5f780ffb4 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/syncintegration/Pipfile.lock +++ b/app/src/androidTest/java/org/mozilla/fenix/syncintegration/Pipfile.lock @@ -196,42 +196,42 @@ }, "cryptography": { "hashes": [ - "sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380", - "sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589", - "sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea", - "sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65", - "sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a", - "sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3", - "sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008", - "sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1", - "sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2", - "sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635", - "sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2", - "sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90", - "sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee", - "sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a", - "sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242", - "sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12", - "sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2", - "sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d", - "sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be", - "sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee", - "sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6", - "sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529", - "sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929", - "sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1", - "sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6", - "sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a", - "sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446", - "sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9", - "sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888", - "sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4", - "sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33", - "sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f" + "sha256:01911714117642a3f1792c7f376db572aadadbafcd8d75bb527166009c9f1d1b", + "sha256:0e89f7b84f421c56e7ff69f11c441ebda73b8a8e6488d322ef71746224c20fce", + "sha256:12d341bd42cdb7d4937b0cabbdf2a94f949413ac4504904d0cdbdce4a22cbf88", + "sha256:15a1fb843c48b4a604663fa30af60818cd28f895572386e5f9b8a665874c26e7", + "sha256:1cdcdbd117681c88d717437ada72bdd5be9de117f96e3f4d50dab3f59fd9ab20", + "sha256:1df6fcbf60560d2113b5ed90f072dc0b108d64750d4cbd46a21ec882c7aefce9", + "sha256:3c6048f217533d89f2f8f4f0fe3044bf0b2090453b7b73d0b77db47b80af8dff", + "sha256:3e970a2119507d0b104f0a8e281521ad28fc26f2820687b3436b8c9a5fcf20d1", + "sha256:44a64043f743485925d3bcac548d05df0f9bb445c5fcca6681889c7c3ab12764", + "sha256:4e36685cb634af55e0677d435d425043967ac2f3790ec652b2b88ad03b85c27b", + "sha256:5f8907fcf57392cd917892ae83708761c6ff3c37a8e835d7246ff0ad251d9298", + "sha256:69b22ab6506a3fe483d67d1ed878e1602bdd5912a134e6202c1ec672233241c1", + "sha256:6bfadd884e7280df24d26f2186e4e07556a05d37393b0f220a840b083dc6a824", + "sha256:6d0fbe73728c44ca3a241eff9aefe6496ab2656d6e7a4ea2459865f2e8613257", + "sha256:6ffb03d419edcab93b4b19c22ee80c007fb2d708429cecebf1dd3258956a563a", + "sha256:810bcf151caefc03e51a3d61e53335cd5c7316c0a105cc695f0959f2c638b129", + "sha256:831a4b37accef30cccd34fcb916a5d7b5be3cbbe27268a02832c3e450aea39cb", + "sha256:887623fe0d70f48ab3f5e4dbf234986b1329a64c066d719432d0698522749929", + "sha256:a0298bdc6e98ca21382afe914c642620370ce0470a01e1bef6dd9b5354c36854", + "sha256:a1327f280c824ff7885bdeef8578f74690e9079267c1c8bd7dc5cc5aa065ae52", + "sha256:c1f25b252d2c87088abc8bbc4f1ecbf7c919e05508a7e8628e6875c40bc70923", + "sha256:c3a5cbc620e1e17009f30dd34cb0d85c987afd21c41a74352d1719be33380885", + "sha256:ce8613beaffc7c14f091497346ef117c1798c202b01153a8cc7b8e2ebaaf41c0", + "sha256:d2a27aca5597c8a71abbe10209184e1a8e91c1fd470b5070a2ea60cafec35bcd", + "sha256:dad9c385ba8ee025bb0d856714f71d7840020fe176ae0229de618f14dae7a6e2", + "sha256:db4b65b02f59035037fde0998974d84244a64c3265bdef32a827ab9b63d61b18", + "sha256:e09469a2cec88fb7b078e16d4adec594414397e8879a4341c6ace96013463d5b", + "sha256:e53dc41cda40b248ebc40b83b31516487f7db95ab8ceac1f042626bc43a2f992", + "sha256:f1e85a178384bf19e36779d91ff35c7617c885da487d689b05c1366f9933ad74", + "sha256:f47be41843200f7faec0683ad751e5ef11b9a56a220d57f300376cd8aba81660", + "sha256:fb0cef872d8193e487fc6bdb08559c3aa41b659a7d9be48b2e10747f47863925", + "sha256:ffc73996c4fca3d2b6c1c8c12bfd3ad00def8621da24f547626bf06441400449" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==42.0.2" + "version": "==42.0.4" }, "distro": { "hashes": [ From 31613f70884b368b46e2698bc2f76e4999582312 Mon Sep 17 00:00:00 2001 From: mcarare <48995920+mcarare@users.noreply.github.com> Date: Tue, 20 Feb 2024 13:37:36 +0200 Subject: [PATCH 029/238] Bug 1880077 - Switch to using Compose BOM instead of individual versioning --- app/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/build.gradle b/app/build.gradle index 4a689a89a..44a84cfff 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -523,6 +523,9 @@ tasks.withType(KotlinCompile).configureEach { } dependencies { + implementation platform(ComponentsDependencies.androidx_compose_bom) + androidTestImplementation platform(ComponentsDependencies.androidx_compose_bom) + implementation project(':browser-engine-gecko') implementation ComponentsDependencies.kotlin_coroutines From 2dd692a68275788706512ac25fe1f09874926ec1 Mon Sep 17 00:00:00 2001 From: t-p-white Date: Tue, 20 Feb 2024 12:09:26 +0000 Subject: [PATCH 030/238] Bug 1881016 - Added experimental onboarding strings for Nimbus --- app/src/main/res/values/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 85c99f579..194a28d3c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -321,11 +321,15 @@ Find out why millions love Firefox + + Safe browsing with more choices Our non-profit backed browser helps stop companies from secretly following you around the web. More than 100 million people protect their privacy by choosing a browser that’s backed by a nonprofit. + Known trackers? Blocked automatically. Extensions? Try all 700. PDFs? Our built-in reader makes them easy to manage. + Our non-profit backed browser helps stop companies from secretly following you around the web.\n\nLearn more in our privacy notice. From 7907e8e60620c8ead28c6b8b863ee4da55e0d537 Mon Sep 17 00:00:00 2001 From: AndiAJ Date: Wed, 21 Feb 2024 14:06:42 +0200 Subject: [PATCH 031/238] Bug 1881238 - Convert private variables to functions so they don't get initialised --- .../SettingsSubMenuHttpsOnlyModeRobot.kt | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuHttpsOnlyModeRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuHttpsOnlyModeRobot.kt index 189482655..f13acc5e5 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuHttpsOnlyModeRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuHttpsOnlyModeRobot.kt @@ -39,7 +39,7 @@ class SettingsSubMenuHttpsOnlyModeRobot { } fun verifyHttpsOnlyModeIsEnabled(shouldBeEnabled: Boolean) { - httpsModeOnlySwitch.check( + httpsModeOnlySwitch().check( matches( if (shouldBeEnabled) { isChecked(true) @@ -50,36 +50,36 @@ class SettingsSubMenuHttpsOnlyModeRobot { ) } - fun clickHttpsOnlyModeSwitch() = httpsModeOnlySwitch.click() + fun clickHttpsOnlyModeSwitch() = httpsModeOnlySwitch().click() fun verifyHttpsOnlyModeOptionsEnabled(shouldBeEnabled: Boolean) { - allTabsOption.assertIsEnabled(shouldBeEnabled) - onlyPrivateTabsOption.assertIsEnabled(shouldBeEnabled) + allTabsOption().assertIsEnabled(shouldBeEnabled) + onlyPrivateTabsOption().assertIsEnabled(shouldBeEnabled) } fun verifyHttpsOnlyOptionSelected(allTabsOptionSelected: Boolean, privateTabsOptionSelected: Boolean) { if (allTabsOptionSelected) { - allTabsOption.assertIsChecked(true) - onlyPrivateTabsOption.assertIsChecked(false) + allTabsOption().assertIsChecked(true) + onlyPrivateTabsOption().assertIsChecked(false) } else if (privateTabsOptionSelected) { - allTabsOption.assertIsChecked(false) - onlyPrivateTabsOption.assertIsChecked(true) + allTabsOption().assertIsChecked(false) + onlyPrivateTabsOption().assertIsChecked(true) } } fun selectHttpsOnlyModeOption(allTabsOptionSelected: Boolean, privateTabsOptionSelected: Boolean) { if (allTabsOptionSelected) { - allTabsOption.click() - allTabsOption.assertIsChecked(true) + allTabsOption().click() + allTabsOption().assertIsChecked(true) } else if (privateTabsOptionSelected) { - onlyPrivateTabsOption.click() - onlyPrivateTabsOption.assertIsChecked(true) + onlyPrivateTabsOption().click() + onlyPrivateTabsOption().assertIsChecked(true) } } class Transition { fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition { - goBackButton.perform(click()) + goBackButton().perform(click()) SettingsRobot().interact() return SettingsRobot.Transition() @@ -87,9 +87,9 @@ class SettingsSubMenuHttpsOnlyModeRobot { } } -private val httpsModeOnlySwitch = onView(withId(R.id.https_only_switch)) +private fun httpsModeOnlySwitch() = onView(withId(R.id.https_only_switch)) -private val allTabsOption = +private fun allTabsOption() = onView( allOf( withId(R.id.https_only_all_tabs), @@ -97,7 +97,7 @@ private val allTabsOption = ), ) -private val onlyPrivateTabsOption = +private fun onlyPrivateTabsOption() = onView( allOf( withId(R.id.https_only_private_tabs), @@ -105,4 +105,4 @@ private val onlyPrivateTabsOption = ), ) -private val goBackButton = onView(withContentDescription("Navigate up")) +private fun goBackButton() = onView(withContentDescription("Navigate up")) From f106a41c3d6c9452b326cd30433c48f9865fad3b Mon Sep 17 00:00:00 2001 From: AndiAJ Date: Wed, 21 Feb 2024 14:26:14 +0200 Subject: [PATCH 032/238] Bug 1881238 - Add logs to SettingsSubMenuHttpsOnlyModeRobot --- .../SettingsSubMenuHttpsOnlyModeRobot.kt | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuHttpsOnlyModeRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuHttpsOnlyModeRobot.kt index f13acc5e5..d4a0c5c64 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuHttpsOnlyModeRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuHttpsOnlyModeRobot.kt @@ -4,8 +4,8 @@ package org.mozilla.fenix.ui.robots +import android.util.Log import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.ViewInteraction import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.hasSibling @@ -15,6 +15,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import org.hamcrest.CoreMatchers.allOf import org.mozilla.fenix.R +import org.mozilla.fenix.helpers.Constants.TAG import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource import org.mozilla.fenix.helpers.assertIsChecked import org.mozilla.fenix.helpers.assertIsEnabled @@ -23,22 +24,30 @@ import org.mozilla.fenix.helpers.isChecked class SettingsSubMenuHttpsOnlyModeRobot { - fun verifyHttpsOnlyModeMenuHeader(): ViewInteraction = + fun verifyHttpsOnlyModeMenuHeader() { + Log.i(TAG, "verifyHttpsOnlyModeMenuHeader: Trying to verify that the \"HTTPS-Only Mode\" toolbar items are visible") onView( allOf( withText(getStringResource(R.string.preferences_https_only_title)), hasSibling(withContentDescription("Navigate up")), ), ).check(matches(isDisplayed())) + Log.i(TAG, "verifyHttpsOnlyModeMenuHeader: Verified that the \"HTTPS-Only Mode\" toolbar items are visible") + } fun verifyHttpsOnlyModeSummary() { + Log.i(TAG, "verifyHttpsOnlyModeSummary: Trying to verify that the \"HTTPS-Only Mode\" title is visible") onView(withId(R.id.https_only_title)) .check(matches(withText("HTTPS-Only Mode"))) + Log.i(TAG, "verifyHttpsOnlyModeSummary: Verified that the \"HTTPS-Only Mode\" title is visible") + Log.i(TAG, "verifyHttpsOnlyModeSummary: Trying to verify that the \"HTTPS-Only Mode\" summary is visible") onView(withId(R.id.https_only_summary)) .check(matches(withText("Automatically attempts to connect to sites using HTTPS encryption protocol for increased security. Learn more"))) + Log.i(TAG, "verifyHttpsOnlyModeSummary: Verified that the \"HTTPS-Only Mode\" summary is visible") } fun verifyHttpsOnlyModeIsEnabled(shouldBeEnabled: Boolean) { + Log.i(TAG, "verifyHttpsOnlyModeIsEnabled: Trying to verify that the \"HTTPS-Only Mode\" toggle is checked: $shouldBeEnabled") httpsModeOnlySwitch().check( matches( if (shouldBeEnabled) { @@ -48,38 +57,65 @@ class SettingsSubMenuHttpsOnlyModeRobot { }, ), ) + Log.i(TAG, "verifyHttpsOnlyModeIsEnabled: Verified that the \"HTTPS-Only Mode\" toggle is checked: $shouldBeEnabled") } - fun clickHttpsOnlyModeSwitch() = httpsModeOnlySwitch().click() + fun clickHttpsOnlyModeSwitch() { + Log.i(TAG, "clickHttpsOnlyModeSwitch: Trying to click the \"HTTPS-Only Mode\" toggle") + httpsModeOnlySwitch().click() + Log.i(TAG, "clickHttpsOnlyModeSwitch: Clicked the \"HTTPS-Only Mode\" toggle") + } fun verifyHttpsOnlyModeOptionsEnabled(shouldBeEnabled: Boolean) { + Log.i(TAG, "verifyHttpsOnlyModeOptionsEnabled: Trying to verify that the \"Enable in all tabs\" option is enabled: $shouldBeEnabled") allTabsOption().assertIsEnabled(shouldBeEnabled) + Log.i(TAG, "verifyHttpsOnlyModeOptionsEnabled: Verified that the \"Enable in all tabs\" option is enabled: $shouldBeEnabled") + Log.i(TAG, "verifyHttpsOnlyModeOptionsEnabled: Trying to verify that the \"Enable only in private tabs\" option is enabled: $shouldBeEnabled") onlyPrivateTabsOption().assertIsEnabled(shouldBeEnabled) + Log.i(TAG, "verifyHttpsOnlyModeOptionsEnabled: Verified that the \"Enable only in private tabs\" option is enabled: $shouldBeEnabled") } fun verifyHttpsOnlyOptionSelected(allTabsOptionSelected: Boolean, privateTabsOptionSelected: Boolean) { if (allTabsOptionSelected) { + Log.i(TAG, "verifyHttpsOnlyOptionSelected: Trying to verify that the \"Enable in all tabs\" option is checked: $allTabsOptionSelected") allTabsOption().assertIsChecked(true) + Log.i(TAG, "verifyHttpsOnlyOptionSelected: Verified that the \"Enable in all tabs\" option is checked: $allTabsOptionSelected") + Log.i(TAG, "verifyHttpsOnlyOptionSelected: Trying to verify that the \"Enable only in private tabs\" option is checked: $privateTabsOptionSelected") onlyPrivateTabsOption().assertIsChecked(false) + Log.i(TAG, "verifyHttpsOnlyOptionSelected: Verified that the \"Enable only in private tabs\" option is checked: $privateTabsOptionSelected") } else if (privateTabsOptionSelected) { + Log.i(TAG, "verifyHttpsOnlyOptionSelected: Trying to verify that the \"Enable in all tabs\" option is checked: $allTabsOptionSelected") allTabsOption().assertIsChecked(false) + Log.i(TAG, "verifyHttpsOnlyOptionSelected: Verified that the \"Enable in all tabs\" option is checked: $allTabsOptionSelected") + Log.i(TAG, "verifyHttpsOnlyOptionSelected: Trying to verify that the \"Enable only in private tabs\" option is checked: $privateTabsOptionSelected") onlyPrivateTabsOption().assertIsChecked(true) + Log.i(TAG, "verifyHttpsOnlyOptionSelected: Verified that the \"Enable only in private tabs\" option is checked: $privateTabsOptionSelected") } } fun selectHttpsOnlyModeOption(allTabsOptionSelected: Boolean, privateTabsOptionSelected: Boolean) { if (allTabsOptionSelected) { + Log.i(TAG, "selectHttpsOnlyModeOption: Trying to click the \"Enable in all tabs\" option") allTabsOption().click() + Log.i(TAG, "selectHttpsOnlyModeOption: Clicked the \"Enable in all tabs\" option") + Log.i(TAG, "selectHttpsOnlyModeOption: Trying to verify that the \"Enable in all tabs\" option is checked: $allTabsOptionSelected") allTabsOption().assertIsChecked(true) + Log.i(TAG, "selectHttpsOnlyModeOption: Verified that the \"Enable in all tabs\" option is checked: $allTabsOptionSelected") } else if (privateTabsOptionSelected) { + Log.i(TAG, "selectHttpsOnlyModeOption: Trying to click the \"Enable only in private tabs\" option") onlyPrivateTabsOption().click() + Log.i(TAG, "selectHttpsOnlyModeOption: Clicked the \"Enable only in private tabs\" option") + Log.i(TAG, "selectHttpsOnlyModeOption: Trying to verify that the \"Enable only in private tabs\" option is checked: $privateTabsOptionSelected") onlyPrivateTabsOption().assertIsChecked(true) + Log.i(TAG, "selectHttpsOnlyModeOption: Verified that the \"Enable only in private tabs\" option is checked: $privateTabsOptionSelected") } } class Transition { fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition { + Log.i(TAG, "goBack: Trying to click the navigate up toolbar button") goBackButton().perform(click()) + Log.i(TAG, "goBack: Clicked the navigate up toolbar button") SettingsRobot().interact() return SettingsRobot.Transition() From b5c5527556592d52dd045c744f2c41238e82b108 Mon Sep 17 00:00:00 2001 From: James Hugman Date: Mon, 29 Jan 2024 17:00:24 +0000 Subject: [PATCH 033/238] =?UTF-8?q?Bug=201880476=20=E2=80=94=20Messaging:?= =?UTF-8?q?=20promote=20NimbusMessagingController=20to=20components.nimbus?= =?UTF-8?q?.messaging?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fenix/ui/NimbusMessagingMessageTest.kt | 22 +----- .../java/org/mozilla/fenix/HomeActivity.kt | 21 ++---- .../mozilla/fenix/components/Components.kt | 2 +- .../fenix/components/NimbusComponents.kt | 15 +++- .../org/mozilla/fenix/home/HomeFragment.kt | 3 +- .../messaging/DefaultMessageController.kt | 9 ++- .../FenixNimbusMessagingController.kt | 23 ------ .../messaging/MessageNotificationWorker.kt | 36 ++++------ .../messaging/state/MessagingMiddleware.kt | 30 ++++---- .../messaging/DefaultMessageControllerTest.kt | 10 +-- .../state/MessagingMiddlewareTest.kt | 71 ++++++++++++------- 11 files changed, 101 insertions(+), 141 deletions(-) delete mode 100644 app/src/main/java/org/mozilla/fenix/messaging/FenixNimbusMessagingController.kt diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingMessageTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingMessageTest.kt index c353b668f..4cbd0735c 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingMessageTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingMessageTest.kt @@ -33,8 +33,8 @@ class NimbusMessagingMessageTest { private lateinit var context: Context - private val storage - get() = context.components.nimbus.messagingStorage + private val messaging + get() = context.components.nimbus.messaging @get:Rule val activityTestRule = @@ -54,7 +54,7 @@ class NimbusMessagingMessageTest { */ @Test fun testAllMessageIntegrity() = runTest { - val messages = storage.getMessages() + val messages = messaging.getMessages() val rawMessages = feature.messages assertTrue(rawMessages.isNotEmpty()) @@ -67,22 +67,6 @@ class NimbusMessagingMessageTest { assertEquals(messages.size, rawMessages.size) } - /** - * Check if the messages' triggers are well formed JEXL. - */ - @Test - fun testAllMessageTriggers() = runTest { - val helper = context.components.nimbus.createJexlHelper() - val messages = storage.getMessages() - messages.forEach { message -> - storage.isMessageEligible(message, helper) - if (storage.malFormedMap.isNotEmpty()) { - fail("${message.id} has a problem with its JEXL trigger: ${storage.malFormedMap.keys}") - } - } - helper.destroy() - } - private fun checkIsLocalized(string: String) { assertFalse(string.isBlank()) // The check will almost always succeed, since the generated code diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index 1bc06595f..e218b2e13 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -121,7 +121,6 @@ import org.mozilla.fenix.home.intent.SpeechProcessingIntentProcessor import org.mozilla.fenix.home.intent.StartSearchIntentProcessor import org.mozilla.fenix.library.bookmarks.DesktopFolders import org.mozilla.fenix.messaging.FenixMessageSurfaceId -import org.mozilla.fenix.messaging.FenixNimbusMessagingController import org.mozilla.fenix.messaging.MessageNotificationWorker import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.onboarding.ReEngagementNotificationWorker @@ -1221,13 +1220,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { } private suspend fun showFullscreenMessageIfNeeded(context: Context) { - val messagingStorage = context.components.nimbus.messagingStorage - val messages = messagingStorage.getMessages() - val nextMessage = - messagingStorage.getNextMessage(FenixMessageSurfaceId.SURVEY, messages) - ?: return - - val fenixNimbusMessagingController = FenixNimbusMessagingController(messagingStorage) + val messaging = context.components.nimbus.messaging + val nextMessage = messaging.getNextMessage(FenixMessageSurfaceId.SURVEY) ?: return val researchSurfaceDialogFragment = ResearchSurfaceDialogFragment.newInstance( keyMessageText = nextMessage.text, keyAcceptButtonText = nextMessage.buttonLabel, @@ -1235,7 +1229,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { ) researchSurfaceDialogFragment.onAccept = { - processIntent(fenixNimbusMessagingController.getIntentForMessage(nextMessage)) + processIntent(messaging.getIntentForMessage(nextMessage)) components.appStore.dispatch(AppAction.MessagingAction.MessageClicked(nextMessage)) } @@ -1252,15 +1246,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { // Update message as displayed. val currentBootUniqueIdentifier = BootUtils.getBootIdentifier(context) - val updatedMessage = - fenixNimbusMessagingController.updateMessageAsDisplayed( - nextMessage, - currentBootUniqueIdentifier, - ) - - fenixNimbusMessagingController.onMessageDisplayed(updatedMessage) - return + messaging.onMessageDisplayed(nextMessage, currentBootUniqueIdentifier) } companion object { diff --git a/app/src/main/java/org/mozilla/fenix/components/Components.kt b/app/src/main/java/org/mozilla/fenix/components/Components.kt index fd74fcb37..4c806ae86 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Components.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Components.kt @@ -232,7 +232,7 @@ class Components(private val context: Context) { context.pocketStoriesSelectedCategoriesDataStore, ), MessagingMiddleware( - messagingStorage = nimbus.messagingStorage, + controller = nimbus.messaging, ), MetricsMiddleware(metrics = analytics.metrics), ), diff --git a/app/src/main/java/org/mozilla/fenix/components/NimbusComponents.kt b/app/src/main/java/org/mozilla/fenix/components/NimbusComponents.kt index a83b3ef59..ad9004205 100644 --- a/app/src/main/java/org/mozilla/fenix/components/NimbusComponents.kt +++ b/app/src/main/java/org/mozilla/fenix/components/NimbusComponents.kt @@ -7,6 +7,8 @@ package org.mozilla.fenix.components import android.content.Context import mozilla.components.service.nimbus.NimbusApi import mozilla.components.service.nimbus.messaging.FxNimbusMessaging +import mozilla.components.service.nimbus.messaging.NimbusMessagingController +import mozilla.components.service.nimbus.messaging.NimbusMessagingControllerInterface import mozilla.components.service.nimbus.messaging.NimbusMessagingStorage import mozilla.components.service.nimbus.messaging.OnDiskMessageMetadataStorage import org.mozilla.experiments.nimbus.NimbusEventStore @@ -71,12 +73,23 @@ class NimbusComponents(private val context: Context) { fun createJexlHelper(): NimbusMessagingHelperInterface = messagingStorage.createMessagingHelper() + /** + * The main entry point for UI surfaces to interact with (get, click, dismiss) messages + * from the Nimbus Messaging component. + */ + val messaging: NimbusMessagingControllerInterface by lazyMonitored { + NimbusMessagingController( + messagingStorage = messagingStorage, + deepLinkScheme = BuildConfig.DEEP_LINK_SCHEME, + ) + } + /** * Low level access to the messaging component. * * The app should access this through a [mozilla.components.service.nimbus.messaging.NimbusMessagingController]. */ - val messagingStorage by lazyMonitored { + private val messagingStorage by lazyMonitored { NimbusMessagingStorage( context = context, metadataStorage = OnDiskMessageMetadataStorage(context), diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 24fb5ec9f..fccae3ff9 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -117,7 +117,6 @@ import org.mozilla.fenix.home.toolbar.SearchSelectorBinding import org.mozilla.fenix.home.toolbar.SearchSelectorMenuBinding import org.mozilla.fenix.home.topsites.DefaultTopSitesView import org.mozilla.fenix.messaging.DefaultMessageController -import org.mozilla.fenix.messaging.FenixNimbusMessagingController import org.mozilla.fenix.messaging.MessagingFeature import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks @@ -360,7 +359,7 @@ class HomeFragment : Fragment() { engine = components.core.engine, messageController = DefaultMessageController( appStore = components.appStore, - messagingController = FenixNimbusMessagingController(components.nimbus.messagingStorage), + messagingController = components.nimbus.messaging, homeActivity = activity, ), store = store, diff --git a/app/src/main/java/org/mozilla/fenix/messaging/DefaultMessageController.kt b/app/src/main/java/org/mozilla/fenix/messaging/DefaultMessageController.kt index a006ab02b..a72305c88 100644 --- a/app/src/main/java/org/mozilla/fenix/messaging/DefaultMessageController.kt +++ b/app/src/main/java/org/mozilla/fenix/messaging/DefaultMessageController.kt @@ -4,9 +4,8 @@ package org.mozilla.fenix.messaging -import android.content.Intent import mozilla.components.service.nimbus.messaging.Message -import mozilla.components.service.nimbus.messaging.NimbusMessagingController +import mozilla.components.service.nimbus.messaging.NimbusMessagingControllerInterface import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.components.AppStore import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.MessageClicked @@ -17,13 +16,13 @@ import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.MessageDi */ class DefaultMessageController( private val appStore: AppStore, - private val messagingController: NimbusMessagingController, + private val messagingController: NimbusMessagingControllerInterface, private val homeActivity: HomeActivity, ) : MessageController { override fun onMessagePressed(message: Message) { - val actionUri = messagingController.processMessageActionToUri(message) - homeActivity.processIntent(Intent(Intent.ACTION_VIEW, actionUri)) + val intent = messagingController.getIntentForMessage(message) + homeActivity.processIntent(intent) appStore.dispatch(MessageClicked(message)) } diff --git a/app/src/main/java/org/mozilla/fenix/messaging/FenixNimbusMessagingController.kt b/app/src/main/java/org/mozilla/fenix/messaging/FenixNimbusMessagingController.kt deleted file mode 100644 index 6a1c31e9f..000000000 --- a/app/src/main/java/org/mozilla/fenix/messaging/FenixNimbusMessagingController.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* 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/. */ - -package org.mozilla.fenix.messaging - -import mozilla.components.service.nimbus.messaging.NimbusMessagingController -import mozilla.components.service.nimbus.messaging.NimbusMessagingStorage -import org.mozilla.fenix.BuildConfig - -/** - * Bookkeeping for message actions in terms of Glean messages and the messaging store. - * Specialized implementation of NimbusMessagingController defining the deepLinkScheme - * used by Fenix - */ -class FenixNimbusMessagingController( - messagingStorage: NimbusMessagingStorage, - now: () -> Long = { System.currentTimeMillis() }, -) : NimbusMessagingController( - messagingStorage = messagingStorage, - deepLinkScheme = BuildConfig.DEEP_LINK_SCHEME, - now = now, -) diff --git a/app/src/main/java/org/mozilla/fenix/messaging/MessageNotificationWorker.kt b/app/src/main/java/org/mozilla/fenix/messaging/MessageNotificationWorker.kt index f75b4ef28..173552066 100644 --- a/app/src/main/java/org/mozilla/fenix/messaging/MessageNotificationWorker.kt +++ b/app/src/main/java/org/mozilla/fenix/messaging/MessageNotificationWorker.kt @@ -45,10 +45,10 @@ class MessageNotificationWorker( override suspend fun doWork(): Result { val context = applicationContext - val messagingStorage = context.components.nimbus.messagingStorage - val messages = messagingStorage.getMessages() + val messaging = context.components.nimbus.messaging + val nextMessage = - messagingStorage.getNextMessage(FenixMessageSurfaceId.NOTIFICATION, messages) + messaging.getNextMessage(FenixMessageSurfaceId.NOTIFICATION) ?: return Result.success() val currentBootUniqueIdentifier = BootUtils.getBootIdentifier(context) @@ -57,23 +57,15 @@ class MessageNotificationWorker( return Result.success() } - val nimbusMessagingController = FenixNimbusMessagingController(messagingStorage) - // Update message as displayed. - val updatedMessage = - nimbusMessagingController.updateMessageAsDisplayed( - nextMessage, - currentBootUniqueIdentifier, - ) - - nimbusMessagingController.onMessageDisplayed(updatedMessage) + messaging.onMessageDisplayed(nextMessage, currentBootUniqueIdentifier) context.components.notificationsDelegate.notify( MESSAGE_TAG, - SharedIdsHelper.getIdForTag(context, updatedMessage.id), + SharedIdsHelper.getIdForTag(context, nextMessage.id), buildNotification( context, - updatedMessage, + nextMessage, ), ) @@ -177,18 +169,17 @@ class NotificationDismissedService : LifecycleService() { super.onStartCommand(intent, flags, startId) if (intent != null) { - val nimbusMessagingController = - FenixNimbusMessagingController(applicationContext.components.nimbus.messagingStorage) + val messaging = applicationContext.components.nimbus.messaging lifecycleScope.launch { // Get the relevant message. val message = intent.getStringExtra(DISMISSED_MESSAGE_ID)?.let { messageId -> - nimbusMessagingController.getMessage(messageId) + messaging.getMessage(messageId) } if (message != null) { // Update message as 'dismissed'. - nimbusMessagingController.onMessageDismissed(message) + messaging.onMessageDismissed(message) } } } @@ -208,21 +199,20 @@ class NotificationClickedReceiverActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val nimbusMessagingController = - FenixNimbusMessagingController(components.nimbus.messagingStorage) + val messaging = applicationContext.components.nimbus.messaging lifecycleScope.launch { // Get the relevant message. val message = intent.getStringExtra(CLICKED_MESSAGE_ID)?.let { messageId -> - nimbusMessagingController.getMessage(messageId) + messaging.getMessage(messageId) } if (message != null) { // Update message as 'clicked'. - nimbusMessagingController.onMessageClicked(message) + messaging.onMessageClicked(message) // Create the intent. - val intent = nimbusMessagingController.getIntentForMessage(message) + val intent = messaging.getIntentForMessage(message) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) diff --git a/app/src/main/java/org/mozilla/fenix/messaging/state/MessagingMiddleware.kt b/app/src/main/java/org/mozilla/fenix/messaging/state/MessagingMiddleware.kt index 4d8f79ad9..5340996f4 100644 --- a/app/src/main/java/org/mozilla/fenix/messaging/state/MessagingMiddleware.kt +++ b/app/src/main/java/org/mozilla/fenix/messaging/state/MessagingMiddleware.kt @@ -10,8 +10,7 @@ import kotlinx.coroutines.launch import mozilla.components.lib.state.Middleware import mozilla.components.lib.state.MiddlewareContext import mozilla.components.service.nimbus.messaging.Message -import mozilla.components.service.nimbus.messaging.NimbusMessagingController -import mozilla.components.service.nimbus.messaging.NimbusMessagingStorage +import mozilla.components.service.nimbus.messaging.NimbusMessagingControllerInterface import org.mozilla.fenix.components.appstate.AppAction import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.ConsumeMessageToShow import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.Evaluate @@ -21,13 +20,11 @@ import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.Restore import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.UpdateMessageToShow import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.UpdateMessages import org.mozilla.fenix.components.appstate.AppState -import org.mozilla.fenix.messaging.FenixNimbusMessagingController typealias AppStoreMiddlewareContext = MiddlewareContext class MessagingMiddleware( - private val messagingStorage: NimbusMessagingStorage, - private val controller: NimbusMessagingController = FenixNimbusMessagingController(messagingStorage), + private val controller: NimbusMessagingControllerInterface, private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO), ) : Middleware { @@ -39,13 +36,13 @@ class MessagingMiddleware( when (action) { is Restore -> { coroutineScope.launch { - val messages = messagingStorage.getMessages() + val messages = controller.getMessages() context.store.dispatch(UpdateMessages(messages)) } } is Evaluate -> { - val message = messagingStorage.getNextMessage( + val message = controller.getNextMessage( action.surface, context.state.messaging.messages, ) @@ -72,16 +69,15 @@ class MessagingMiddleware( oldMessage: Message, context: AppStoreMiddlewareContext, ) { - val newMessage = controller.updateMessageAsDisplayed(oldMessage) - val newMessages = if (!newMessage.isExpired) { - updateMessage(context, oldMessage, newMessage) - } else { - consumeMessageToShowIfNeeded(context, oldMessage) - removeMessage(context, oldMessage) - } - context.dispatch(UpdateMessages(newMessages)) coroutineScope.launch { - controller.onMessageDisplayed(newMessage) + val newMessage = controller.onMessageDisplayed(oldMessage) + val newMessages = if (!newMessage.isExpired) { + updateMessage(context, oldMessage, newMessage) + } else { + consumeMessageToShowIfNeeded(context, oldMessage) + removeMessage(context, oldMessage) + } + context.store.dispatch(UpdateMessages(newMessages)) } } @@ -136,7 +132,7 @@ class MessagingMiddleware( val actualMessageToShow = context.state.messaging.messageToShow[updatedMessage.surface] if (actualMessageToShow?.id == oldMessage.id) { - context.dispatch(UpdateMessageToShow(updatedMessage)) + context.store.dispatch(UpdateMessageToShow(updatedMessage)) } val oldMessageIndex = context.state.messaging.messages.indexOfFirst { it.id == updatedMessage.id } val newList = context.state.messaging.messages.toMutableList() diff --git a/app/src/test/java/org/mozilla/fenix/messaging/DefaultMessageControllerTest.kt b/app/src/test/java/org/mozilla/fenix/messaging/DefaultMessageControllerTest.kt index 733d86de5..aeced21c2 100644 --- a/app/src/test/java/org/mozilla/fenix/messaging/DefaultMessageControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/messaging/DefaultMessageControllerTest.kt @@ -4,13 +4,11 @@ package org.mozilla.fenix.messaging -import androidx.core.net.toUri -import io.mockk.every import io.mockk.mockk import io.mockk.verify import mozilla.components.service.nimbus.messaging.Message import mozilla.components.service.nimbus.messaging.MessageData -import mozilla.components.service.nimbus.messaging.NimbusMessagingController +import mozilla.components.service.nimbus.messaging.NimbusMessagingControllerInterface import mozilla.components.support.test.robolectric.testContext import mozilla.telemetry.glean.testing.GleanTestRule import org.junit.Before @@ -30,7 +28,7 @@ class DefaultMessageControllerTest { val gleanTestRule = GleanTestRule(testContext) private val homeActivity: HomeActivity = mockk(relaxed = true) - private val messagingController: NimbusMessagingController = mockk(relaxed = true) + private val messagingController: NimbusMessagingControllerInterface = mockk(relaxed = true) private lateinit var defaultMessageController: DefaultMessageController private val appStore: AppStore = mockk(relaxed = true) @@ -46,12 +44,10 @@ class DefaultMessageControllerTest { @Test fun `WHEN calling onMessagePressed THEN process the action intent and update the app store`() { val message = mockMessage() - val uri = "action".toUri() - every { messagingController.processMessageActionToUri(message) }.returns(uri) defaultMessageController.onMessagePressed(message) - verify { messagingController.processMessageActionToUri(message) } + verify { messagingController.getIntentForMessage(message) } verify { homeActivity.processIntent(any()) } verify { appStore.dispatch(MessageClicked(message)) } } diff --git a/app/src/test/java/org/mozilla/fenix/messaging/state/MessagingMiddlewareTest.kt b/app/src/test/java/org/mozilla/fenix/messaging/state/MessagingMiddlewareTest.kt index 9dd968590..3fc27ec6e 100644 --- a/app/src/test/java/org/mozilla/fenix/messaging/state/MessagingMiddlewareTest.kt +++ b/app/src/test/java/org/mozilla/fenix/messaging/state/MessagingMiddlewareTest.kt @@ -12,13 +12,13 @@ import kotlinx.coroutines.test.advanceUntilIdle import mozilla.components.service.nimbus.messaging.Message import mozilla.components.service.nimbus.messaging.MessageData import mozilla.components.service.nimbus.messaging.NimbusMessagingController -import mozilla.components.service.nimbus.messaging.NimbusMessagingStorage import mozilla.components.service.nimbus.messaging.StyleData import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.rule.MainCoroutineRule import mozilla.components.support.test.rule.runTestOnMain import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Rule @@ -30,20 +30,17 @@ import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.MessageDi import org.mozilla.fenix.components.appstate.AppAction.MessagingAction.Restore import org.mozilla.fenix.components.appstate.AppState import org.mozilla.fenix.messaging.FenixMessageSurfaceId -import org.mozilla.fenix.messaging.FenixNimbusMessagingController import org.mozilla.fenix.messaging.MessagingState class MessagingMiddlewareTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() private val coroutineScope = coroutinesTestRule.scope - private lateinit var messagingStorage: NimbusMessagingStorage private lateinit var controller: NimbusMessagingController @Before fun setUp() { - messagingStorage = mockk(relaxed = true) - controller = FenixNimbusMessagingController(messagingStorage) { 0 } + controller = mockk(relaxed = true) } @Test @@ -55,12 +52,12 @@ class MessagingMiddlewareTest { ), ), listOf( - MessagingMiddleware(messagingStorage, controller, coroutineScope), + MessagingMiddleware(controller, coroutineScope), ), ) val message = createMessage() - coEvery { messagingStorage.getMessages() } returns listOf(message) + coEvery { controller.getMessages() } returns listOf(message) store.dispatch(Restore).joinBlocking() store.waitUntilIdle() @@ -81,12 +78,12 @@ class MessagingMiddlewareTest { ), ), listOf( - MessagingMiddleware(messagingStorage, controller, coroutineScope), + MessagingMiddleware(controller, coroutineScope), ), ) every { - messagingStorage.getNextMessage( + controller.getNextMessage( FenixMessageSurfaceId.HOMESCREEN, any(), ) @@ -111,7 +108,7 @@ class MessagingMiddlewareTest { ), ), listOf( - MessagingMiddleware(messagingStorage, controller, coroutineScope), + MessagingMiddleware(controller, coroutineScope), ), ) @@ -121,7 +118,7 @@ class MessagingMiddlewareTest { store.waitUntilIdle() assertTrue(store.state.messaging.messages.isEmpty()) - coVerify { messagingStorage.updateMetadata(createMetadata(pressed = true)) } + coVerify { controller.onMessageClicked(message = message) } } @Test @@ -135,14 +132,14 @@ class MessagingMiddlewareTest { ), ), listOf( - MessagingMiddleware(messagingStorage, controller, coroutineScope), + MessagingMiddleware(controller, coroutineScope), ), ) store.dispatch(MessageDismissed(message)).joinBlocking() store.waitUntilIdle() assertTrue(store.state.messaging.messages.isEmpty()) - coVerify { messagingStorage.updateMetadata(metadata.copy(dismissed = true)) } + coVerify { controller.onMessageDismissed(message = message) } } @Test @@ -158,7 +155,7 @@ class MessagingMiddlewareTest { ), ), listOf( - MessagingMiddleware(messagingStorage, controller, coroutineScope), + MessagingMiddleware(controller, coroutineScope), ), ) @@ -184,7 +181,7 @@ class MessagingMiddlewareTest { ), ), listOf( - MessagingMiddleware(messagingStorage, controller, coroutineScope), + MessagingMiddleware(controller, coroutineScope), ), ) @@ -198,6 +195,7 @@ class MessagingMiddlewareTest { @Test fun `WHEN updateMessage THEN update available messages`() = runTestOnMain { val message = createMessage() + val messageDisplayed = message.copy(metadata = createMetadata(displayCount = 1)) val store = AppStore( AppState( messaging = MessagingState( @@ -207,20 +205,25 @@ class MessagingMiddlewareTest { ), ), listOf( - MessagingMiddleware(messagingStorage, controller, coroutineScope), + MessagingMiddleware(controller, coroutineScope), ), ) every { - messagingStorage.getNextMessage( + controller.getNextMessage( FenixMessageSurfaceId.HOMESCREEN, any(), ) } returns message - store.dispatch(Evaluate(FenixMessageSurfaceId.HOMESCREEN)) + coEvery { + controller.onMessageDisplayed(eq(message), any()) + } returns messageDisplayed + + store.dispatch(Evaluate(FenixMessageSurfaceId.HOMESCREEN)).joinBlocking() store.waitUntilIdle() + assertEquals(1, store.state.messaging.messages.count()) assertEquals(1, store.state.messaging.messages.first().displayCount) } @@ -240,17 +243,21 @@ class MessagingMiddlewareTest { ), ), listOf( - MessagingMiddleware(messagingStorage, controller, coroutineScope), + MessagingMiddleware(controller, coroutineScope), ), ) every { - messagingStorage.getNextMessage( + controller.getNextMessage( FenixMessageSurfaceId.HOMESCREEN, any(), ) } returns message1 + coEvery { + controller.onMessageDisplayed(eq(message1), any()) + } returns messageDisplayed1 + store.dispatch(Evaluate(FenixMessageSurfaceId.HOMESCREEN)).joinBlocking() store.waitUntilIdle() @@ -272,17 +279,21 @@ class MessagingMiddlewareTest { ), ), listOf( - MessagingMiddleware(messagingStorage, controller, coroutineScope), + MessagingMiddleware(controller, coroutineScope), ), ) every { - messagingStorage.getNextMessage( + controller.getNextMessage( FenixMessageSurfaceId.HOMESCREEN, any(), ) } returns message + coEvery { + controller.onMessageDisplayed(eq(message), any()) + } returns messageDisplayed + store.dispatch(Evaluate(FenixMessageSurfaceId.HOMESCREEN)).joinBlocking() store.waitUntilIdle() @@ -291,7 +302,11 @@ class MessagingMiddlewareTest { @Test fun `GIVEN a message with that surpassed the maxDisplayCount WHEN onMessagedDisplayed THEN remove the message and consume it`() = runTestOnMain { - val message = createMessage(createMetadata(displayCount = 6)) + val message = createMessage(createMetadata(displayCount = 4)) + val messageDisplayed = createMessage(createMetadata(displayCount = 5)) + assertFalse(message.isExpired) + assertTrue(messageDisplayed.isExpired) + val store = AppStore( AppState( messaging = MessagingState( @@ -302,17 +317,21 @@ class MessagingMiddlewareTest { ), ), listOf( - MessagingMiddleware(messagingStorage, controller, coroutineScope), + MessagingMiddleware(controller, coroutineScope), ), ) every { - messagingStorage.getNextMessage( + controller.getNextMessage( FenixMessageSurfaceId.HOMESCREEN, any(), ) } returns message + coEvery { + controller.onMessageDisplayed(eq(message), any()) + } returns messageDisplayed + store.dispatch(Evaluate(FenixMessageSurfaceId.HOMESCREEN)).joinBlocking() store.waitUntilIdle() @@ -322,7 +341,7 @@ class MessagingMiddlewareTest { } private fun createMessage( metadata: Message.Metadata = createMetadata(), - messageId: String = "control-id", + messageId: String = "message-id", data: MessageData = mockk(relaxed = true), action: String = "action", styleData: StyleData = StyleData(), From 31a92ca2db14ea857976ae1f0d44de3a496d457d Mon Sep 17 00:00:00 2001 From: Roger Yang Date: Wed, 21 Feb 2024 15:58:48 -0500 Subject: [PATCH 034/238] Bug 1881336 - Remove telemetry probes expiring in v125 --- app/metrics.yaml | 19 ----- .../metrics/fonts/FontEnumerationWorker.kt | 72 ------------------- 2 files changed, 91 deletions(-) diff --git a/app/metrics.yaml b/app/metrics.yaml index 636f46ff2..546aeea5a 100644 --- a/app/metrics.yaml +++ b/app/metrics.yaml @@ -2654,25 +2654,6 @@ metrics: metadata: tags: - Experiments - font_list_json: - type: text - lifetime: ping - description: | - A JSON blob representing the installed fonts - send_in_pings: - - font-list - bugs: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1858193 - data_reviews: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1858193#c2 - data_sensitivity: - # Text metrics are _required_ to be web_activity or highly_sensitive, so even though this - # is more like 'technical' (per the Data Review), I'm marking highly sensitive. - - highly_sensitive - notification_emails: - - android-probes@mozilla.com - - tom@mozilla.com - expires: 124 customize_home: most_visited_sites: diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/fonts/FontEnumerationWorker.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/fonts/FontEnumerationWorker.kt index 7722af20e..afce9acb7 100644 --- a/app/src/main/java/org/mozilla/fenix/components/metrics/fonts/FontEnumerationWorker.kt +++ b/app/src/main/java/org/mozilla/fenix/components/metrics/fonts/FontEnumerationWorker.kt @@ -5,11 +5,9 @@ package org.mozilla.fenix.components.metrics.fonts import android.content.Context -import android.content.res.Configuration import android.graphics.fonts.Font import android.graphics.fonts.SystemFonts import android.os.Build -import android.os.LocaleList import androidx.work.BackoffPolicy import androidx.work.CoroutineWorker import androidx.work.ExistingWorkPolicy @@ -18,15 +16,10 @@ import androidx.work.WorkManager import androidx.work.WorkerParameters import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import org.json.JSONArray -import org.json.JSONException -import org.json.JSONObject import org.mozilla.fenix.Config -import org.mozilla.fenix.GleanMetrics.Metrics import org.mozilla.fenix.GleanMetrics.Pings import org.mozilla.fenix.ext.settings import java.io.File -import java.util.Locale import java.util.concurrent.TimeUnit import kotlin.time.Duration.Companion.hours @@ -40,15 +33,12 @@ class FontEnumerationWorker( ) : CoroutineWorker(context, workerParameters) { @Suppress("TooGenericExceptionCaught") override suspend fun doWork(): Result = withContext(Dispatchers.IO) { - val s: String try { readAllFonts() - s = createJSONString() } catch (e: Exception) { return@withContext Result.retry() } - Metrics.fontListJson.set(s) Pings.fontList.submit() // To avoid getting multiple submissions from new installs, set directly @@ -79,68 +69,6 @@ class FontEnumerationWorker( } } - /** - * This function creates a single JSON String containing - * The user's phone information, as well as all the fonts and their information, - * And the names of files that encountered a parsing error. - */ - @Throws(JSONException::class) - fun createJSONString(): String { - val submission = JSONObject() - - run { - submission.put("submission", kDesiredSubmissions) - submission.put("brand", Build.BRAND) - submission.put("device", Build.DEVICE) - submission.put("hardware", Build.HARDWARE) - submission.put("manufacturer", Build.MANUFACTURER) - submission.put("model", Build.MODEL) - submission.put("product", Build.PRODUCT) - submission.put("release_version", Build.VERSION.RELEASE) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - submission.put("security_patch", Build.VERSION.SECURITY_PATCH) - submission.put("base_os", Build.VERSION.BASE_OS) - } else { - submission.put("security_patch", "too-low-version") - submission.put("base_os", "too-low-version") - } - val config: Configuration = this.applicationContext.resources.configuration - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - val supportedLocales: LocaleList = LocaleList.getDefault() - val sb = StringBuilder() - for (i in 0 until supportedLocales.size()) { - val locale: Locale = supportedLocales.get(i) - sb.append(locale.toString()) - sb.append(",") - } - submission.put("current_locale", config.locales[0].toString()) - submission.put("all_locales", sb.toString()) - } else { - @Suppress("DEPRECATION") - submission.put("current_locale", config.locale.toString()) - submission.put("all_locales", "too-low-version") - } - } - - val fontArr = JSONArray() - for (fontDetails in fonts) { - fontArr.put(fontDetails.toJson()) - } - - val errorArr = JSONArray() - for (error in brokenFonts) { - val errorObj = JSONObject() - errorObj.put("path", error.first) - errorObj.put("hash", error.second) - errorArr.put(errorObj) - } - - submission.put("fonts", fontArr) - submission.put("errors", errorArr) - - return submission.toString() - } - companion object { private const val FONT_ENUMERATOR_WORK_NAME = "org.mozilla.fenix.metrics.font.work" private val HOUR_MILLIS: Long = 1.hours.inWholeMilliseconds From 8a0b8f58c2ab2e239830f36d5937bc1715d28ed1 Mon Sep 17 00:00:00 2001 From: James Hugman Date: Wed, 31 Jan 2024 13:43:49 +0000 Subject: [PATCH 035/238] =?UTF-8?q?Bug=201880476=20=E2=80=94=20Refactor=20?= =?UTF-8?q?onMessageDisplayed=20bookkeeping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../messaging/DefaultMessageControllerTest.kt | 3 +++ .../state/MessagingMiddlewareTest.kt | 20 +++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/src/test/java/org/mozilla/fenix/messaging/DefaultMessageControllerTest.kt b/app/src/test/java/org/mozilla/fenix/messaging/DefaultMessageControllerTest.kt index aeced21c2..35d5d8b48 100644 --- a/app/src/test/java/org/mozilla/fenix/messaging/DefaultMessageControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/messaging/DefaultMessageControllerTest.kt @@ -4,6 +4,8 @@ package org.mozilla.fenix.messaging +import android.content.Intent +import io.mockk.every import io.mockk.mockk import io.mockk.verify import mozilla.components.service.nimbus.messaging.Message @@ -44,6 +46,7 @@ class DefaultMessageControllerTest { @Test fun `WHEN calling onMessagePressed THEN process the action intent and update the app store`() { val message = mockMessage() + every { messagingController.getIntentForMessage(message) }.returns(Intent()) defaultMessageController.onMessagePressed(message) diff --git a/app/src/test/java/org/mozilla/fenix/messaging/state/MessagingMiddlewareTest.kt b/app/src/test/java/org/mozilla/fenix/messaging/state/MessagingMiddlewareTest.kt index 3fc27ec6e..b4f72cd14 100644 --- a/app/src/test/java/org/mozilla/fenix/messaging/state/MessagingMiddlewareTest.kt +++ b/app/src/test/java/org/mozilla/fenix/messaging/state/MessagingMiddlewareTest.kt @@ -232,7 +232,7 @@ class MessagingMiddlewareTest { val message1 = createMessage() val message2 = message1.copy(id = "message2", action = "action2") // An updated message1 that has been displayed once. - val messageDisplayed1 = message1.copy(metadata = createMetadata(displayCount = 1)) + val messageDisplayed1 = incrementDisplayCount(message1) val store = AppStore( AppState( messaging = MessagingState( @@ -253,6 +253,9 @@ class MessagingMiddlewareTest { any(), ) } returns message1 + coEvery { + controller.onMessageDisplayed(message1, any()) + } returns messageDisplayed1 coEvery { controller.onMessageDisplayed(eq(message1), any()) @@ -269,7 +272,7 @@ class MessagingMiddlewareTest { fun `GIVEN a message has not surpassed the maxDisplayCount WHEN evaluate THEN update the message displayCount`() = runTestOnMain { val message = createMessage() // An updated message that has been displayed once. - val messageDisplayed = message.copy(metadata = createMetadata(displayCount = 1)) + val messageDisplayed = incrementDisplayCount(message) val store = AppStore( AppState( messaging = MessagingState( @@ -289,6 +292,9 @@ class MessagingMiddlewareTest { any(), ) } returns message + coEvery { + controller.onMessageDisplayed(message, any()) + } returns messageDisplayed coEvery { controller.onMessageDisplayed(eq(message), any()) @@ -327,6 +333,9 @@ class MessagingMiddlewareTest { any(), ) } returns message + coEvery { + controller.onMessageDisplayed(message, any()) + } returns incrementDisplayCount(message) coEvery { controller.onMessageDisplayed(eq(message), any()) @@ -363,3 +372,10 @@ private fun createMetadata( lastTimeShown, latestBootIdentifier, ) + +private fun incrementDisplayCount(message: Message) = + message.copy( + metadata = createMetadata( + displayCount = message.displayCount + 1, + ), + ) From bb2620b9da74c31034fc57ba5a21051e4af9887c Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 23 Feb 2024 00:03:13 +0000 Subject: [PATCH 036/238] Import translations from android-l10n --- app/src/main/res/values-azb/strings.xml | 261 ++++++++++++++++++++++++ app/src/main/res/values-sl/strings.xml | 3 + app/src/main/res/values-tr/strings.xml | 2 +- 3 files changed, 265 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-azb/strings.xml b/app/src/main/res/values-azb/strings.xml index 033094275..39348b26c 100644 --- a/app/src/main/res/values-azb/strings.xml +++ b/app/src/main/res/values-azb/strings.xml @@ -923,20 +923,137 @@ آچیق تاغلاریْ اوْتوماتیک باغلا + + + آچیلیش اکرانی آنایارپاق + + سوْن تاغ + + دؤرد ساعاتلیق حرکتسیزلیکدن سونرا آچیلان اصلی صفحه ال‌ ایله باغلاییْن بیر گۆندن سوْنرا باغلایین + + بیر هفته سونرا باغلا + + بیر آی سونرا باغلا + + آنایارپاق‌دا آچ + + سون تاغدا آچ + + دؤرد ساعاتدان سونرا آنایارپاق‌دا آچ + + + + کؤهنه تاغلاری ایشده اولمایان تاغا آپار + + ایکی هفته‌ده باخمادیغینیز تاغلار ایشده اولمایان بؤلومه آپاریلیر. + قالدیر + + ایشده + + + %1$s ایمکانی وار هردن بیر آراشدیرمالاری نصب ائلیه و چالیشدیرا. + + آرتیق بیلین + + + دگیشمه‌لری یئرینه سالماق اوچون اپلیکیشن‌دن چیخاجاق + + تامام + + لغو + + دگیشمه‌لری یئرینه سالماق اوچون اپلیکیشن‌دن چیْخیلیر… + + + + آچیْق تاغ‌لار + + گیزلی تاغ‌لار + + دؤنگل‌لنمیش تاغ‌لار + + تاغ آرتیر + + گیزلی تاغ آرتیر + + گیزلی + + دؤنگل + + بوتون تاغ‌لاری پایلاش + + سوْن باغلانان تاغ‌لار + + سون باغلانان‌لار + + حساب تنظیم‌لری + + تاغ تنظیم‌لری + + بوتون تاغ‌لاریْ باغلا + + بوکمارک + + باغلا + + + سئچیلمیش تاغ‌لاری پایلاش + + سئچیلن تاغ‌لار منوسو + + تاغی مجموعه‌دن چیخار + + تاغ‌لاری سئچین + + تاغی باغلا + + %s تاغی باغلا + + آچیق تاغ‌لار منوسونو آچ + + تاغ‌لاریْ مجموعه‌ده ساخلاییْن + + مجموعه‌نی سیل + + + مجموعه‌نی یئنی‌دن آدلاندیْر + + تاغ‌لاریْ آچ + + مجموعه آدیْ + + یئنی‌دن آدلاندیْر + + قالدیر + + گئچمیش‌دن سیل + + + %1$s (گیزلی حالت) + + + + آختاریش شرطلرینی گیر + + گئچمیشی سیل + + گئچمیش سیلیندی + + %1$s سیلیندی سیل باغیشلایین، %1$s بو صفحه‌نی دولدورا بیلمه‌دی. + + سیْنماق راپورتونو موْزیلایا گؤندر + + تاغی باغلا + + تاغی گئنه آل + + + + بو قوْولوغو سیلمک ایسته‌دیگینیزه آرخایینسیز؟ + + %s سئچیلن موریدلری سیله‌جک. لغو + + قوْولوق اکله + + بوکمارک ساخلاندی! + + دوزه‌لیش دوزه‌لیش + + کوْپی + + پایلاش + + + یئنی تاغدا آچ + + گیزلی تاغدا آچ + + هامیْسینی یئنی تاغ‌لاردا آچ + + هامیْسینی گیزلی تاغ‌لاردا آچ + + سیل + + ساخلا + + %1$d سئچیلیدی + + بوکمارک دوز‌ه‌لیشی + + قوْولوق دوزه‌لیشی + + دؤنگل ائدیلن بوکمارک‌لاری گؤرمک اوچون اوْتوروُم آچین + + URL + + قوْولوق + + آد + + قوْولوق اکله + + قوْولوق سئچ + + + بیر عنوان وئرمه‌لیسیز + + گئچرسیز URL + + هئچ بوکمارک بوردا یوخ + + %1$s سیلیندی + + بوکمارک‌لار سیلیندی + + سئچیلن قوْولوق سیلینیر + + لغو + + + آختاریش شرطلرینی گیر + + + + تنظیم‌لره گئدین + + یئیین تنظیم‌لر صفحه‌سی + + توصیه اولموش + + ایجازه‌لری پوز + + تامام + + لغو + + ایجازه‌‌نی پوْز + + + تامام + + لغو + + بوتون سایتلارداکی ایجازه‌لری پوْز + + اؤزاؤزونه چالما + + + کامرا + + میکروفون + + قونوم + + بیلدیریش‌ + + قالیجی ساخلاما + + سایتلار آراسی کوکی‌لر + + DRM ایله کونترول اولونان موحتوا + + + ایجازه ایسته‌یین + + مسدود اوْلوندو + + ایجازه وئریلدی + + اندروید طرفیندن مسدود اوْلوندو + + آیریْ توُتمالار + + + باغلیْ + + استاندارد + + بَرک + + اؤزل + + + سس و ویدئویا ایجازه وئر + + سس و ویدئویا ایجازه وئر + + + سیزین اوچون اؤنملی اولان شئی‌لری یئغین. \n داها سوْنرا یئیین گیریش اوچون اوْخشار آختاریش‌لاریْ، سایتلاریْ و تاغ‌لاریْ قروُپلاشدیرین. + diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index 91d638d45..4abd63817 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -2170,6 +2170,9 @@ Iskalnik %s + + + Zamenjajte privzeti brskalnik Nastavite, naj se povezave s spletnih strani, e-pošte in sporočil samodejno odpirajo v Firefoxu. diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index c273ae92e..115191558 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -714,7 +714,7 @@ Son eşitleme: %s - Son eşitleme: hiç + Son eşitleme: yok سس و ویدئویا ایجازه وئر + + یالنیز موبایل سلولاردا سس و ویدئونو بلوْکلا + + سس و ویدئو وایفای‌ اوستونده چالیناجاق + + یالنیز سس مسدودو + + یالنیز سس مسدودو + + سس و ویدئویو مسدود ائله + + سس و ویدئویو مسدود ائله + + آچیق + + باغلیْ + + آچیق + + باغلیْ + + + + مجموعه‌لر + + مجموعه‌لر منوسو سیزین اوچون اؤنملی اولان شئی‌لری یئغین. \n داها سوْنرا یئیین گیریش اوچون اوْخشار آختاریش‌لاریْ، سایتلاریْ و تاغ‌لاریْ قروُپلاشدیرین. + + تاغ‌لاری سئچین + + مجموعه سئچین + + مجموعه‌ آدی + + یئنی مجموعه اکله + + + هامیسینی سئچ + + هامیسینی سئچمه + + ساخلاناجاق تاغ‌لاری سئچ + + %d تاغ سئچیلدی + + %d تاغ سئچیلدی + + تاغ‌لار ساخلاندیْ! + + مجموعه ساخلاندیْ! + + + تاغ ساخلاندیْ! + + باغلا + + ساخلا + + گؤستر + + تامام + + لغو + + + %d مجموعه‌سی + diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index f99fb52bd..4de3758d7 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -1562,7 +1562,7 @@ Απομόνωση cookie μεταξύ ιστοτόπων - Αποστολή αιτήματος μη πώλησης και κοινοποίησης των δεδομένων μου στους ιστοτόπους + Αποστολή αιτήματος μη πώλησης και κοινοποίησης δεδομένων στους ιστοτόπους Περιεχόμενο καταγραφής diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 152ab7fc3..f37d2d2b5 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -250,6 +250,9 @@ history and go back to the home screen. --> ล้างประวัติการเรียกดู + + แปลหน้า + ภาษาที่เลือก @@ -1714,8 +1717,12 @@ ข้อยกเว้น การเข้าสู่ระบบและรหัสผ่านที่ไม่ได้บันทึกจะถูกแสดงที่นี่ + + %s จะไม่บันทึกรหัสผ่านสำหรับไซต์ที่แสดงอยู่ที่นี่ การเข้าสู่ระบบและรหัสผ่านจะไม่ถูกบันทึกสำหรับไซต์เหล่านี้ + + %s จะไม่บันทึกรหัสผ่านสำหรับไซต์เหล่านี้ ลบข้อยกเว้นทั้งหมด @@ -1754,8 +1761,12 @@ ปลดล็อกเพื่อดูรหัสผ่านที่บันทึกไว้ของคุณ รักษาความปลอดภัยการเข้าสู่ระบบและรหัสผ่านของคุณ + + รักษาความปลอดภัยให้กับรหัสผ่านที่บันทึกไว้ของคุณ ตั้งค่ารูปแบบการล็อกอุปกรณ์, PIN, หรือรหัสผ่านเพื่อปกป้องไม่ให้ใครเข้าถึงการเข้าสู่ระบบและรหัสผ่านที่บันทึกไว้ของคุณหากคนอื่นมีอุปกรณ์ของคุณ + + ตั้งค่ารูปแบบการล็อกอุปกรณ์, PIN, หรือรหัสผ่านเพื่อปกป้องไม่ให้ใครเข้าถึงรหัสผ่านที่บันทึกไว้ของคุณหากคนอื่นมีอุปกรณ์ของคุณ ภายหลัง @@ -1775,6 +1786,9 @@ เรียงเมนูเข้าสู่ระบบ + + เมนูเรียงลำดับรหัสผ่าน + การเติมอัตโนมัติ @@ -1790,6 +1804,8 @@ บันทึกและเติมวิธีการชำระเงิน ข้อมูลถูกเข้ารหัส + + %s จะเข้ารหัสลับวิธีการชำระเงินทั้งหมดที่คุณบันทึกไว้ ซิงค์บัตรระหว่างอุปกรณ์ @@ -1864,8 +1880,12 @@ ปลดล็อกเพื่อดูบัตรเครดิตที่บันทึกไว้ของคุณ รักษาความปลอดภัยให้กับบัตรเครดิตของคุณ + + รักษาความปลอดภัยให้กับวิธีการชำระเงินที่บันทึกไว้ของคุณ ตั้งค่ารูปแบบการล็อกอุปกรณ์, PIN, หรือรหัสผ่านเพื่อปกป้องไม่ให้ใครเข้าถึงบัตรเครดิตที่บันทึกไว้ของคุณหากคนอื่นมีอุปกรณ์ของคุณ + + ตั้งค่ารูปแบบการล็อกอุปกรณ์, PIN, หรือรหัสผ่านเพื่อปกป้องไม่ให้ใครเข้าถึงวิธีการชำระเงินที่บันทึกไว้ของคุณหากคนอื่นมีอุปกรณ์ของคุณ ตั้งค่าตอนนี้ @@ -1875,6 +1895,8 @@ ปลดล็อกเพื่อใช้ข้อมูลบัตรเครดิตที่เก็บไว้ + + ปลดล็อกเพื่อใช้วิธีการชำระเงินที่บันทึกไว้ เพิ่มที่อยู่ @@ -1912,6 +1934,8 @@ คุณแน่ใจหรือไม่ว่าต้องการลบที่อยู่นี้? + + ลบที่อยู่นี้หรือไม่? ลบ @@ -2010,18 +2034,28 @@ แก้ไข คุณแน่ใจหรือไม่ที่ต้องการจะลบการเข้าสู่ระบบนี้? + + คุณแน่ใจหรือไม่ว่าต้องการลบรหัสผ่านนี้? ลบ ยกเลิก ตัวเลือกการเข้าสู่ระบบ + + ตัวเลือกรหัสผ่าน ช่องข้อความที่แก้ไขได้สำหรับที่อยู่เว็บของการเข้าสู่ระบบ + + ช่องข้อความที่แก้ไขได้สำหรับที่อยู่เว็บไซต์ ช่องข้อความที่แก้ไขได้สำหรับชื่อผู้ใช้ของการเข้าสู่ระบบ + + ช่องข้อความที่แก้ไขได้สำหรับชื่อผู้ใช้ ช่องข้อความที่แก้ไขได้สำหรับรหัสผ่านของการเข้าสู่ระบบ + + ช่องข้อความที่แก้ไขได้สำหรับรหัสผ่าน บันทึกการเปลี่ยนแปลงเพื่อเข้าสู่ระบบ @@ -2040,6 +2074,8 @@ ใส่รหัสผ่าน ต้องการชื่อผู้ใช้ + + ป้อนชื่อผู้ใช้ ต้องการชื่อโฮสต์ @@ -2140,6 +2176,9 @@ ค้นหาด้วย %s + + เปลี่ยนเบราว์เซอร์เริ่มต้นของคุณ + ตั้งลิงก์จากเว็บไซต์ อีเมล และข้อความให้เปิดโดยอัตโนมัติใน Firefox @@ -2395,6 +2434,8 @@ กำลังแปล อยู่ระหว่างการแปล + + เลือกภาษา เกิดปัญหาในการแปล โปรดลองอีกครั้ง @@ -2416,6 +2457,10 @@ ไม่ต้องแปล %1$s เลย ไม่ต้องแปลไซต์นี้เลย + + แทนที่การตั้งค่าอื่นๆ ทั้งหมด + + แทนที่การเสนอให้แปล การตั้งค่าการแปล diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 115191558..747d55275 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -2258,7 +2258,7 @@ Ayarlar - Değerlendirme denetleyicisinde reklamları göster + Değerlendirme kontrolcüsünde reklamları göster Ara sıra ilgili ürünlerin reklamlarını görebilirsiniz. Yalnızca güvenilir değerlendirmeleri olan ürünlerin reklamlarını kabul ediyoruz. %s @@ -2284,7 +2284,7 @@ Ürün mevcut değil - Bu ürünün yeniden stoğa girdiğini görürseniz bize bildirin, biz de değerlendirmeleri kontrol etmeye çalışalım. + Bu ürünün yeniden stoka girdiğini görürseniz bize bildirin, biz de değerlendirmeleri kontrol etmeye çalışalım. Ürünün stokta olduğunu bildir From c2ebb15742e653de7d337b4b79afb6ffb6bd9573 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 25 Feb 2024 00:03:43 +0000 Subject: [PATCH 044/238] Import translations from android-l10n --- app/src/main/res/values-hu/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index fad21e3e8..38ba20bd6 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -799,9 +799,9 @@ Görgetés az eszköztár elrejtéséhez - Eszköztár oldalra seprése a lapok közti váltáshoz + Eszköztár oldalra csúsztatása a lapok közti váltáshoz - Eszköztár felfelé seprése a lapok megnyitásához + Eszköztár felfelé csúsztatása a lapok megnyitásához From b742795cd308c6f6cb088fb98fe9609517dca8a1 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 26 Feb 2024 00:03:51 +0000 Subject: [PATCH 045/238] Import translations from android-l10n --- app/src/main/res/values-azb/strings.xml | 44 ++++++++++++++++++++++ app/src/main/res/values-es-rES/strings.xml | 13 +++++++ 2 files changed, 57 insertions(+) diff --git a/app/src/main/res/values-azb/strings.xml b/app/src/main/res/values-azb/strings.xml index d317cc475..eaf9def6d 100644 --- a/app/src/main/res/values-azb/strings.xml +++ b/app/src/main/res/values-azb/strings.xml @@ -1309,6 +1309,50 @@ %d مجموعه‌سی + + + پایلاش + + پایلاش + + PDF اولاراق ساخلا + + PDF یارادیلانمادیْ + + باغلا + + بو صفحه‌ پرینت اوْلانمیر + + پرینت + + جهازا گؤندر + + بوتون عمل‌لر + + یاخین‌دا ایشله‌دیلمیش + + کلیپ‌بوردا کوْپی ائله + + کلیپ‌بوْردا کوْپی اوْلوندو + + دؤنگله گیریش + + دیتا دؤنگلی و ساخلاماسی + + بوتون جهازلارا گؤندر + + دونگلی کَس + + آفلاین + + باشقا جهازا باغلا + + + بیر تاغ گؤندرمک اوچون آزی بیر باشقا جهازدا فایرفاکسا داخیل اوْلون. + + آنلادیم + diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 5f00a5e88..e9888675a 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -2106,8 +2106,12 @@ Introduce una contraseña Se requiere nombre de usuario + + Introduce un nombre de usuario Se requiere nombre de servidor + + Introduce una dirección web Búsqueda por voz @@ -2203,6 +2207,9 @@ Buscar con %s + + + Cambia tu navegador predeterminado Configura enlaces de sitios web, correos electrónicos y mensajes para que se abran automáticamente en Firefox. @@ -2460,6 +2467,8 @@ Traducción en curso + + Selecciona un idioma Ha surgido un problema al traducir. Por favor inténtalo de nuevo. @@ -2480,6 +2489,10 @@ No traducir nunca %1$s No traducir nunca este sitio + + Anula todas las demás configuraciones + + Anula las ofertas de traducción Ajustes de traducción From 62dd5fe59ec44040a120af4fc15efc8725ef15d7 Mon Sep 17 00:00:00 2001 From: rahulsainani Date: Fri, 23 Feb 2024 11:21:27 +0100 Subject: [PATCH 046/238] Bug 1881703 - Notify app store of mode change from TabsTray --- .../java/org/mozilla/fenix/tabstray/TabsTrayController.kt | 4 +++- .../mozilla/fenix/tabstray/DefaultTabsTrayControllerTest.kt | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayController.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayController.kt index 8c73da20b..a48e1d622 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayController.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayController.kt @@ -536,7 +536,9 @@ class DefaultTabsTrayController( selected.isEmpty() && tabsTrayStore.state.mode.isSelect().not() -> { TabsTray.openedExistingTab.record(TabsTray.OpenedExistingTabExtra(source ?: "unknown")) tabsUseCases.selectTab(tab.id) - browsingModeManager.mode = BrowsingMode.fromBoolean(tab.content.private) + val mode = BrowsingMode.fromBoolean(tab.content.private) + browsingModeManager.mode = mode + appStore.dispatch(AppAction.ModeChange(mode)) handleNavigateToBrowser() } tab.id in selected.map { it.id } -> handleTabUnselected(tab) diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/DefaultTabsTrayControllerTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/DefaultTabsTrayControllerTest.kt index 82d73cd67..3f522dea4 100644 --- a/app/src/test/java/org/mozilla/fenix/tabstray/DefaultTabsTrayControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/tabstray/DefaultTabsTrayControllerTest.kt @@ -63,6 +63,7 @@ import org.mozilla.fenix.collections.CollectionsDialog import org.mozilla.fenix.collections.show import org.mozilla.fenix.components.AppStore import org.mozilla.fenix.components.TabCollectionStorage +import org.mozilla.fenix.components.appstate.AppAction import org.mozilla.fenix.components.bookmarks.BookmarksUseCase import org.mozilla.fenix.ext.maxActiveTime import org.mozilla.fenix.ext.potentialInactiveTabs @@ -958,6 +959,7 @@ class DefaultTabsTrayControllerTest { assertEquals(privateTab.id, browserStore.state.selectedTabId) assertEquals(true, browsingModeManager.mode.isPrivate) + verify { appStore.dispatch(AppAction.ModeChange(BrowsingMode.Private)) } controller.handleTabDeletion("privateTab") browserStore.dispatch(TabListAction.SelectTabAction(normalTab.id)).joinBlocking() @@ -965,6 +967,7 @@ class DefaultTabsTrayControllerTest { assertEquals(normalTab.id, browserStore.state.selectedTabId) assertEquals(false, browsingModeManager.mode.isPrivate) + verify { appStore.dispatch(AppAction.ModeChange(BrowsingMode.Normal)) } } finally { unmockkStatic("mozilla.components.browser.state.selector.SelectorsKt") } From dbb3c610a2c3f781c5f566e404495d17c7ce523d Mon Sep 17 00:00:00 2001 From: "oana.horvath" Date: Tue, 13 Feb 2024 15:41:33 +0200 Subject: [PATCH 047/238] Bug 1879525 - Create TestSetup helper: Compose Test classes --- .../fenix/helpers/AppAndSystemHelper.kt | 55 +++++++++++++++++-- .../org/mozilla/fenix/helpers/TestSetup.kt | 47 +++++++++++----- .../mozilla/fenix/ui/ComposeBookmarksTest.kt | 34 +----------- .../mozilla/fenix/ui/ComposeCollectionTest.kt | 26 +-------- .../fenix/ui/ComposeContextMenusTest.kt | 31 ++--------- .../mozilla/fenix/ui/ComposeHistoryTest.kt | 44 ++------------- .../mozilla/fenix/ui/ComposeHomeScreenTest.kt | 27 +-------- .../fenix/ui/ComposeMediaNotificationTest.kt | 34 +----------- .../fenix/ui/ComposeNavigationToolbarTest.kt | 28 +--------- .../org/mozilla/fenix/ui/ComposeSearchTest.kt | 11 ++-- ...oseSettingsDeleteBrowsingDataOnQuitTest.kt | 26 +-------- .../ComposeSettingsDeleteBrowsingDataTest.kt | 22 +------- .../fenix/ui/ComposeTabbedBrowsingTest.kt | 34 +----------- .../mozilla/fenix/ui/ComposeTopSitesTest.kt | 27 +-------- .../mozilla/fenix/ui/robots/HistoryRobot.kt | 2 +- 15 files changed, 123 insertions(+), 325 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/helpers/AppAndSystemHelper.kt b/app/src/androidTest/java/org/mozilla/fenix/helpers/AppAndSystemHelper.kt index bc10b79ab..6162bbf0f 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/helpers/AppAndSystemHelper.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/helpers/AppAndSystemHelper.kt @@ -34,6 +34,9 @@ import androidx.test.uiautomator.UiSelector import androidx.test.uiautomator.Until import junit.framework.AssertionFailedError import kotlinx.coroutines.runBlocking +import mozilla.appservices.places.BookmarkRoot +import mozilla.components.browser.storage.sync.PlacesBookmarksStorage +import mozilla.components.browser.storage.sync.PlacesHistoryStorage import org.junit.Assert import org.junit.Assert.assertEquals import org.mozilla.fenix.Config @@ -43,6 +46,7 @@ import org.mozilla.fenix.helpers.Constants.PackageName.PIXEL_LAUNCHER import org.mozilla.fenix.helpers.Constants.PackageName.YOUTUBE_APP import org.mozilla.fenix.helpers.Constants.TAG import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort +import org.mozilla.fenix.helpers.TestHelper.appContext import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.ext.waitNotNull import org.mozilla.fenix.helpers.idlingresource.NetworkConnectionIdlingResource @@ -70,7 +74,7 @@ object AppAndSystemHelper { fun deleteDownloadedFileOnStorage(fileName: String) { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) { val storageManager: StorageManager? = - TestHelper.appContext.getSystemService(Context.STORAGE_SERVICE) as StorageManager? + appContext.getSystemService(Context.STORAGE_SERVICE) as StorageManager? val storageVolumes = storageManager!!.storageVolumes val storageVolume: StorageVolume = storageVolumes[0] val file = File(storageVolume.directory!!.path + "/Download/" + fileName) @@ -110,7 +114,7 @@ object AppAndSystemHelper { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) { Log.i(TAG, "clearDownloadsFolder: API > 29") val storageManager: StorageManager? = - TestHelper.appContext.getSystemService(Context.STORAGE_SERVICE) as StorageManager? + appContext.getSystemService(Context.STORAGE_SERVICE) as StorageManager? val storageVolumes = storageManager!!.storageVolumes val storageVolume: StorageVolume = storageVolumes[0] val downloadsFolder = File(storageVolume.directory!!.path + "/Download/") @@ -121,16 +125,26 @@ object AppAndSystemHelper { val files = downloadsFolder.listFiles() // Check if the folder is not empty + // If you run this method before a test, files.isNotEmpty() will always return false. if (files != null && files.isNotEmpty()) { Log.i( TAG, - "clearDownloadsFolder: Verified that \"DOWNLOADS\" folder is not empty", + "clearDownloadsFolder: Before cleanup: Downloads storage contains: ${files.size} file(s)", ) // Delete all files in the folder for (file in files) { file.delete() - Log.i(TAG, "clearDownloadsFolder: Deleted $file from \"DOWNLOADS\" folder") + Log.i( + TAG, + "clearDownloadsFolder: Deleted $file from \"DOWNLOADS\" folder." + + " Downloads storage contains ${files.size} file(s): $file", + ) } + } else { + Log.i( + TAG, + "clearDownloadsFolder: Downloads storage is empty.", + ) } } } else { @@ -139,6 +153,7 @@ object AppAndSystemHelper { Log.i(TAG, "clearDownloadsFolder: Verifying if any download files exist.") Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) .listFiles()?.forEach { + Log.i(TAG, "clearDownloadsFolder: Downloads storage contains: $it.") it.delete() Log.i(TAG, "clearDownloadsFolder: Download file $it deleted.") } @@ -146,6 +161,36 @@ object AppAndSystemHelper { } } + suspend fun deleteHistoryStorage() { + val historyStorage = PlacesHistoryStorage(appContext.applicationContext) + Log.i( + TAG, + "deleteHistoryStorage before cleanup: History storage contains: ${historyStorage.getVisited()}", + ) + if (historyStorage.getVisited().isNotEmpty()) { + Log.i(TAG, "deleteHistoryStorage: Trying to delete all history storage.") + historyStorage.deleteEverything() + Log.i( + TAG, + "deleteHistoryStorage after cleanup: History storage contains: ${historyStorage.getVisited()}", + ) + } + } + + suspend fun deleteBookmarksStorage() { + val bookmarksStorage = PlacesBookmarksStorage(appContext.applicationContext) + val bookmarks = bookmarksStorage.getTree(BookmarkRoot.Mobile.id)?.children + Log.i(TAG, "deleteBookmarksStorage before cleanup: Bookmarks storage contains: $bookmarks") + if (bookmarks?.isNotEmpty() == true) { + bookmarks.forEach { + Log.i(TAG, "deleteBookmarksStorage: Trying to delete $it bookmark from storage.") + bookmarksStorage.deleteNode(it.guid) + // TODO: Follow-up with a method to handle the DB update; the logs will still show the bookmarks in the storage before the test starts. + Log.i(TAG, "deleteBookmarksStorage: Bookmark deleted. Bookmarks storage contains: $bookmarks") + } + } + } + fun setNetworkEnabled(enabled: Boolean) { val networkDisconnectedIdlingResource = NetworkConnectionIdlingResource(false) val networkConnectedIdlingResource = NetworkConnectionIdlingResource(true) @@ -228,7 +273,7 @@ object AppAndSystemHelper { */ fun isExternalAppBrowserActivityInCurrentTask(): Boolean { Log.i(TAG, "Trying to verify that the latest activity of the application is used for custom tabs or PWAs") - val activityManager = TestHelper.appContext.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val activityManager = appContext.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager mDevice.waitForIdle(TestAssetHelper.waitingTimeShort) diff --git a/app/src/androidTest/java/org/mozilla/fenix/helpers/TestSetup.kt b/app/src/androidTest/java/org/mozilla/fenix/helpers/TestSetup.kt index b080154c4..02b24f439 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/helpers/TestSetup.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/helpers/TestSetup.kt @@ -1,41 +1,51 @@ +/* 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/. */ package org.mozilla.fenix.helpers import android.util.Log import kotlinx.coroutines.runBlocking -import mozilla.appservices.places.BookmarkRoot -import mozilla.components.browser.storage.sync.PlacesBookmarksStorage +import mozilla.components.browser.state.store.BrowserStore import okhttp3.mockwebserver.MockWebServer +import org.junit.After import org.junit.Before +import org.mozilla.fenix.ext.components import org.mozilla.fenix.helpers.Constants.TAG import org.mozilla.fenix.helpers.TestHelper.appContext import org.mozilla.fenix.ui.robots.notificationShade +/** + * Standard Test setup and tear down methods to run before each test. + * Some extra clean-up is required when we're using the org.mozilla.fenix.helpers.RetryTestRule (the instrumentation does not do that in this case). + * + */ open class TestSetup { lateinit var mockWebServer: MockWebServer - private val bookmarksStorage = PlacesBookmarksStorage(appContext.applicationContext) + lateinit var browserStore: BrowserStore @Before - fun setUp() { + open fun setUp() { Log.i(TAG, "TestSetup: Starting the @Before setup") - // Clear pre-existing notifications + // Initializing this as part of class construction, below the rule would throw a NPE. + // So we are initializing this here instead of in all related tests. + Log.i(TAG, "TestSetup: Initializing the browserStore instance") + browserStore = appContext.components.core.store + // Clear pre-existing notifications. notificationShade { cancelAllShownNotifications() } runBlocking { // Reset locale to EN-US if needed. AppAndSystemHelper.resetSystemLocaleToEnUS() - // Check and clear the downloads folder + // Check and clear the downloads folder, in case the tearDown method is not executed. + // This will only work in case of a RetryTestRule execution. AppAndSystemHelper.clearDownloadsFolder() - // Make sure the Wifi and Mobile Data connections are on + // Make sure the Wifi and Mobile Data connections are on. AppAndSystemHelper.setNetworkEnabled(true) - // Clear bookmarks left after a failed test - val bookmarks = bookmarksStorage.getTree(BookmarkRoot.Mobile.id)?.children - Log.i(TAG, "Before cleanup: Bookmarks storage contains: $bookmarks") - bookmarks?.forEach { - bookmarksStorage.deleteNode(it.guid) - // TODO: Follow-up with a method to handle the DB update; the logs will still show the bookmarks in the storage before the test starts. - Log.i(TAG, "After cleanup: Bookmarks storage contains: $bookmarks") - } + // Clear bookmarks left after a failed test, before a retry. + AppAndSystemHelper.deleteBookmarksStorage() + // Clear history left after a failed test, before a retry. + AppAndSystemHelper.deleteHistoryStorage() } mockWebServer = MockWebServer().apply { dispatcher = AndroidAssetDispatcher() @@ -49,4 +59,11 @@ open class TestSetup { mockWebServer.start() } } + + @After + open fun tearDown() { + Log.i(TAG, "TestSetup: Starting the @After tearDown methods.") + // Check and clear the downloads folder. + AppAndSystemHelper.clearDownloadsFolder() + } } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeBookmarksTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeBookmarksTest.kt index 26d31d920..263a30f93 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeBookmarksTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeBookmarksTest.kt @@ -8,18 +8,10 @@ import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu import androidx.test.espresso.Espresso.pressBack import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation -import androidx.test.uiautomator.UiDevice -import kotlinx.coroutines.runBlocking -import mozilla.appservices.places.BookmarkRoot -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.fenix.R import org.mozilla.fenix.customannotations.SmokeTest -import org.mozilla.fenix.ext.bookmarkStorage -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AppAndSystemHelper.registerAndCleanupIdlingResources import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.RecyclerViewIdlingResource @@ -28,7 +20,9 @@ import org.mozilla.fenix.helpers.TestAssetHelper import org.mozilla.fenix.helpers.TestHelper.clickSnackbarButton import org.mozilla.fenix.helpers.TestHelper.exitMenu import org.mozilla.fenix.helpers.TestHelper.longTapSelectItem +import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.bookmarksMenu import org.mozilla.fenix.ui.robots.browserScreen import org.mozilla.fenix.ui.robots.homeScreen @@ -38,9 +32,7 @@ import org.mozilla.fenix.ui.robots.navigationToolbar /** * Tests for verifying basic functionality of bookmarks */ -class ComposeBookmarksTest { - private lateinit var mockWebServer: MockWebServer - private lateinit var mDevice: UiDevice +class ComposeBookmarksTest : TestSetup() { private val bookmarksFolderName = "New Folder" private val testBookmark = object { var title: String = "Bookmark title" @@ -59,26 +51,6 @@ class ComposeBookmarksTest { @JvmField val retryTestRule = RetryTestRule(3) - @Before - fun setUp() { - mDevice = UiDevice.getInstance(getInstrumentation()) - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - // Clearing all bookmarks data after each test to avoid overlapping data - val bookmarksStorage = activityTestRule.activity?.bookmarkStorage - runBlocking { - val bookmarks = bookmarksStorage?.getTree(BookmarkRoot.Mobile.id)?.children - bookmarks?.forEach { bookmarksStorage.deleteNode(it.guid) } - } - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/522919 @Test fun verifyEmptyBookmarksMenuTest() { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeCollectionTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeCollectionTest.kt index a18366cc7..e570ae59e 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeCollectionTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeCollectionTest.kt @@ -5,19 +5,15 @@ package org.mozilla.fenix.ui import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.fenix.customannotations.SmokeTest -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset import org.mozilla.fenix.helpers.TestHelper.clickSnackbarButton +import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.browserScreen import org.mozilla.fenix.ui.robots.collectionRobot import org.mozilla.fenix.ui.robots.composeTabDrawer @@ -29,9 +25,7 @@ import org.mozilla.fenix.ui.robots.navigationToolbar * */ -class ComposeCollectionTest { - private lateinit var mDevice: UiDevice - private lateinit var mockWebServer: MockWebServer +class ComposeCollectionTest : TestSetup() { private val firstCollectionName = "testcollection_1" private val secondCollectionName = "testcollection_2" private val collectionName = "First Collection" @@ -51,20 +45,6 @@ class ComposeCollectionTest { ), ) { it.activity } - @Before - fun setUp() { - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/353823 @SmokeTest @Test diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeContextMenusTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeContextMenusTest.kt index 5e554b49d..0261c72d8 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeContextMenusTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeContextMenusTest.kt @@ -6,15 +6,8 @@ package org.mozilla.fenix.ui import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.core.net.toUri -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AppAndSystemHelper.assertExternalAppOpens import org.mozilla.fenix.helpers.Constants import org.mozilla.fenix.helpers.HomeActivityIntentTestRule @@ -23,7 +16,9 @@ import org.mozilla.fenix.helpers.MatcherHelper.itemWithText import org.mozilla.fenix.helpers.RetryTestRule import org.mozilla.fenix.helpers.TestAssetHelper import org.mozilla.fenix.helpers.TestHelper.clickSnackbarButton +import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.clickContextMenuItem import org.mozilla.fenix.ui.robots.clickPageObject import org.mozilla.fenix.ui.robots.downloadRobot @@ -45,15 +40,14 @@ import org.mozilla.fenix.ui.robots.shareOverlay * */ -class ComposeContextMenusTest { - private lateinit var mDevice: UiDevice - private lateinit var mockWebServer: MockWebServer +class ComposeContextMenusTest : TestSetup() { @get:Rule(order = 0) val composeTestRule = AndroidComposeTestRule( - HomeActivityIntentTestRule.withDefaultSettingsOverrides( + HomeActivityIntentTestRule( tabsTrayRewriteEnabled = true, + isJumpBackInCFREnabled = false, ), ) { it.activity } @@ -61,21 +55,6 @@ class ComposeContextMenusTest { @JvmField val retryTestRule = RetryTestRule(3) - @Before - fun setUp() { - composeTestRule.activity.applicationContext.settings().shouldShowJumpBackInCFR = false - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/243837 @Test fun verifyOpenLinkNewTabContextMenuOptionTest() { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeHistoryTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeHistoryTest.kt index 138ffa7c3..d3b17f57d 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeHistoryTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeHistoryTest.kt @@ -4,31 +4,23 @@ package org.mozilla.fenix.ui -import android.content.Context import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu import androidx.test.espresso.Espresso.pressBack -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice -import kotlinx.coroutines.runBlocking -import mozilla.components.browser.storage.sync.PlacesHistoryStorage -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.R import org.mozilla.fenix.customannotations.SmokeTest -import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AppAndSystemHelper.registerAndCleanupIdlingResources import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.RecyclerViewIdlingResource import org.mozilla.fenix.helpers.TestAssetHelper import org.mozilla.fenix.helpers.TestHelper.exitMenu import org.mozilla.fenix.helpers.TestHelper.longTapSelectItem +import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.browserScreen import org.mozilla.fenix.ui.robots.historyMenu import org.mozilla.fenix.ui.robots.homeScreen @@ -39,42 +31,16 @@ import org.mozilla.fenix.ui.robots.navigationToolbar * Tests for verifying basic functionality of history * */ -class ComposeHistoryTest { - private lateinit var mockWebServer: MockWebServer - private lateinit var mDevice: UiDevice - +class ComposeHistoryTest : TestSetup() { @get:Rule val activityTestRule = AndroidComposeTestRule( - HomeActivityIntentTestRule.withDefaultSettingsOverrides( + HomeActivityIntentTestRule( tabsTrayRewriteEnabled = true, + isJumpBackInCFREnabled = false, ), ) { it.activity } - @Before - fun setUp() { - InstrumentationRegistry.getInstrumentation().targetContext.settings() - .shouldShowJumpBackInCFR = false - - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - // Clearing all history data after each test to avoid overlapping data - val applicationContext: Context = activityTestRule.activity.applicationContext - val historyStorage = PlacesHistoryStorage(applicationContext) - - runBlocking { - historyStorage.deleteEverything() - } - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/243285 @Test fun verifyEmptyHistoryMenuTest() { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeHomeScreenTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeHomeScreenTest.kt index e6930672a..38ed0da77 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeHomeScreenTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeHomeScreenTest.kt @@ -5,19 +5,14 @@ package org.mozilla.fenix.ui import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice -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.customannotations.SmokeTest -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.helpers.RetryTestRule import org.mozilla.fenix.helpers.TestAssetHelper +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.navigationToolbar @@ -28,10 +23,7 @@ import org.mozilla.fenix.ui.robots.navigationToolbar * */ -class ComposeHomeScreenTest { - private lateinit var mDevice: UiDevice - private lateinit var mockWebServer: MockWebServer - +class ComposeHomeScreenTest : TestSetup() { @get:Rule(order = 0) val activityTestRule = AndroidComposeTestRule( @@ -44,21 +36,6 @@ class ComposeHomeScreenTest { @JvmField val retryTestRule = RetryTestRule(3) - @Before - fun setUp() { - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/235396 @Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1844580") @Test diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeMediaNotificationTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeMediaNotificationTest.kt index 63c0ad07b..c673cbe3d 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeMediaNotificationTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeMediaNotificationTest.kt @@ -5,23 +5,17 @@ package org.mozilla.fenix.ui import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice -import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.mediasession.MediaSession -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.fenix.customannotations.SmokeTest -import org.mozilla.fenix.ext.components -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.helpers.MatcherHelper import org.mozilla.fenix.helpers.RetryTestRule import org.mozilla.fenix.helpers.TestAssetHelper +import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.browserScreen import org.mozilla.fenix.ui.robots.clickPageObject import org.mozilla.fenix.ui.robots.homeScreen @@ -34,11 +28,7 @@ import org.mozilla.fenix.ui.robots.notificationShade * - a media notification icon is displayed on the homescreen for the tab playing media content * Note: this test only verifies media notifications, not media itself */ -class ComposeMediaNotificationTest { - private lateinit var mockWebServer: MockWebServer - private lateinit var mDevice: UiDevice - private lateinit var browserStore: BrowserStore - +class ComposeMediaNotificationTest : TestSetup() { @get:Rule(order = 0) val composeTestRule = AndroidComposeTestRule( @@ -51,24 +41,6 @@ class ComposeMediaNotificationTest { @JvmField val retryTestRule = RetryTestRule(3) - @Before - fun setUp() { - // Initializing this as part of class construction, below the rule would throw a NPE - // So we are initializing this here instead of in all tests. - browserStore = composeTestRule.activity.components.core.store - - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1347033 @SmokeTest @Test diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeNavigationToolbarTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeNavigationToolbarTest.kt index ee4964197..2ff866316 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeNavigationToolbarTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeNavigationToolbarTest.kt @@ -5,19 +5,13 @@ package org.mozilla.fenix.ui import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.fenix.customannotations.SmokeTest -import org.mozilla.fenix.helpers.AndroidAssetDispatcher -import org.mozilla.fenix.helpers.AppAndSystemHelper.resetSystemLocaleToEnUS import org.mozilla.fenix.helpers.AppAndSystemHelper.runWithSystemLocaleChanged import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.helpers.TestAssetHelper +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.navigationToolbar import java.util.Locale @@ -31,10 +25,7 @@ import java.util.Locale * - Find in page */ -class ComposeNavigationToolbarTest { - private lateinit var mDevice: UiDevice - private lateinit var mockWebServer: MockWebServer - +class ComposeNavigationToolbarTest : TestSetup() { @get:Rule val composeTestRule = AndroidComposeTestRule( @@ -43,21 +34,6 @@ class ComposeNavigationToolbarTest { ), ) { it.activity } - @Before - fun setUp() { - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - resetSystemLocaleToEnUS() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/987326 // Swipes the nav bar left/right to switch between tabs @SmokeTest diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeSearchTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeSearchTest.kt index 27f46d84f..eaa76b148 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeSearchTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeSearchTest.kt @@ -32,6 +32,7 @@ import org.mozilla.fenix.helpers.TestAssetHelper import org.mozilla.fenix.helpers.TestHelper import org.mozilla.fenix.helpers.TestHelper.exitMenu import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.clickContextMenuItem import org.mozilla.fenix.ui.robots.clickPageObject import org.mozilla.fenix.ui.robots.homeScreen @@ -50,8 +51,8 @@ import org.mozilla.fenix.ui.robots.searchScreen * */ -class ComposeSearchTest { - lateinit var searchMockServer: MockWebServer +class ComposeSearchTest : TestSetup() { + private lateinit var searchMockServer: MockWebServer private val queryString: String = "firefox" private val generalEnginesList = listOf("DuckDuckGo", "Google", "Bing") private val topicEnginesList = listOf("Amazon.com", "Wikipedia", "eBay") @@ -70,7 +71,8 @@ class ComposeSearchTest { ) { it.activity } @Before - fun setUp() { + override fun setUp() { + super.setUp() searchMockServer = MockWebServer().apply { dispatcher = SearchDispatcher() start() @@ -78,7 +80,8 @@ class ComposeSearchTest { } @After - fun tearDown() { + override fun tearDown() { + super.tearDown() searchMockServer.shutdown() } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeSettingsDeleteBrowsingDataOnQuitTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeSettingsDeleteBrowsingDataOnQuitTest.kt index 6994cd450..21521871b 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeSettingsDeleteBrowsingDataOnQuitTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeSettingsDeleteBrowsingDataOnQuitTest.kt @@ -9,15 +9,10 @@ import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.core.net.toUri import androidx.test.espresso.Espresso.pressBack import androidx.test.rule.GrantPermissionRule -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.fenix.R import org.mozilla.fenix.customannotations.SmokeTest -import org.mozilla.fenix.helpers.AndroidAssetDispatcher -import org.mozilla.fenix.helpers.AppAndSystemHelper.clearDownloadsFolder import org.mozilla.fenix.helpers.AppAndSystemHelper.setNetworkEnabled import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource import org.mozilla.fenix.helpers.HomeActivityIntentTestRule @@ -27,6 +22,7 @@ import org.mozilla.fenix.helpers.TestAssetHelper.getStorageTestAsset import org.mozilla.fenix.helpers.TestHelper.exitMenu import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.restartApp +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.clickPageObject import org.mozilla.fenix.ui.robots.downloadRobot import org.mozilla.fenix.ui.robots.homeScreen @@ -37,9 +33,7 @@ import org.mozilla.fenix.ui.robots.navigationToolbar * Delete Browsing Data on quit * */ -class ComposeSettingsDeleteBrowsingDataOnQuitTest { - private lateinit var mockWebServer: MockWebServer - +class ComposeSettingsDeleteBrowsingDataOnQuitTest : TestSetup() { @get:Rule(order = 0) val composeTestRule = AndroidComposeTestRule( @@ -55,22 +49,6 @@ class ComposeSettingsDeleteBrowsingDataOnQuitTest { Manifest.permission.RECORD_AUDIO, ) - @Before - fun setUp() { - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - - // Check and clear the downloads folder - clearDownloadsFolder() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/416048 @Test fun deleteBrowsingDataOnQuitSettingTest() { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeSettingsDeleteBrowsingDataTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeSettingsDeleteBrowsingDataTest.kt index 49c809a7c..7b1fc996b 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeSettingsDeleteBrowsingDataTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeSettingsDeleteBrowsingDataTest.kt @@ -5,14 +5,10 @@ package org.mozilla.fenix.ui import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.fenix.R import org.mozilla.fenix.customannotations.SmokeTest -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AppAndSystemHelper.setNetworkEnabled import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource import org.mozilla.fenix.helpers.HomeActivityIntentTestRule @@ -22,6 +18,7 @@ import org.mozilla.fenix.helpers.TestAssetHelper.getStorageTestAsset import org.mozilla.fenix.helpers.TestHelper.exitMenu import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.restartApp +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.browserScreen import org.mozilla.fenix.ui.robots.clickPageObject import org.mozilla.fenix.ui.robots.homeScreen @@ -33,9 +30,7 @@ import org.mozilla.fenix.ui.robots.settingsScreen * Delete Browsing Data */ -class ComposeSettingsDeleteBrowsingDataTest { - private lateinit var mockWebServer: MockWebServer - +class ComposeSettingsDeleteBrowsingDataTest : TestSetup() { @get:Rule val composeTestRule = AndroidComposeTestRule( @@ -45,19 +40,6 @@ class ComposeSettingsDeleteBrowsingDataTest { ), ) { it.activity } - @Before - fun setUp() { - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/937561 @Test fun deleteBrowsingDataOptionStatesTest() { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeTabbedBrowsingTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeTabbedBrowsingTest.kt index 013760049..7ef151353 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeTabbedBrowsingTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeTabbedBrowsingTest.kt @@ -5,27 +5,21 @@ package org.mozilla.fenix.ui import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice import com.google.android.material.bottomsheet.BottomSheetBehavior -import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.mediasession.MediaSession -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.fenix.customannotations.SmokeTest -import org.mozilla.fenix.ext.components -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.MatcherHelper import org.mozilla.fenix.helpers.RetryTestRule import org.mozilla.fenix.helpers.TestAssetHelper import org.mozilla.fenix.helpers.TestHelper.clickSnackbarButton import org.mozilla.fenix.helpers.TestHelper.closeApp +import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.restartApp import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.browserScreen import org.mozilla.fenix.ui.robots.clickPageObject import org.mozilla.fenix.ui.robots.homeScreen @@ -49,11 +43,7 @@ import org.mozilla.fenix.ui.robots.notificationShade * - Shortcut context menu navigation */ -class ComposeTabbedBrowsingTest { - private lateinit var mDevice: UiDevice - private lateinit var mockWebServer: MockWebServer - private lateinit var browserStore: BrowserStore - +class ComposeTabbedBrowsingTest : TestSetup() { @get:Rule(order = 0) val composeTestRule = AndroidComposeTestRule( @@ -67,24 +57,6 @@ class ComposeTabbedBrowsingTest { @JvmField val retryTestRule = RetryTestRule(3) - @Before - fun setUp() { - // Initializing this as part of class construction, below the rule would throw a NPE - // So we are initializing this here instead of in all related tests. - browserStore = composeTestRule.activity.components.core.store - - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/903599 @Test fun closeAllTabsTest() { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeTopSitesTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeTopSitesTest.kt index 50bfadce1..16ea4b127 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeTopSitesTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/ComposeTopSitesTest.kt @@ -5,23 +5,19 @@ package org.mozilla.fenix.ui import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.fenix.R import org.mozilla.fenix.customannotations.SmokeTest -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.DataGenerationHelper.generateRandomString import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset import org.mozilla.fenix.helpers.TestHelper.clickSnackbarButton +import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText import org.mozilla.fenix.helpers.TestHelper.waitUntilSnackbarGone +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.browserScreen import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.homeScreenWithComposeTopSites @@ -36,10 +32,7 @@ import org.mozilla.fenix.ui.robots.navigationToolbar * - Verifies existence of default top sites available on the home-screen */ -class ComposeTopSitesTest { - private lateinit var mDevice: UiDevice - private lateinit var mockWebServer: MockWebServer - +class ComposeTopSitesTest : TestSetup() { @get:Rule val composeTestRule = AndroidComposeTestRule( @@ -48,20 +41,6 @@ class ComposeTopSitesTest { ), ) { it.activity } - @Before - fun setUp() { - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/532598 @SmokeTest @Test diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HistoryRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HistoryRobot.kt index fc6ee4d1a..8229bf188 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HistoryRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HistoryRobot.kt @@ -223,7 +223,7 @@ fun historyMenu(interact: HistoryRobot.() -> Unit): HistoryRobot.Transition { return HistoryRobot.Transition() } -private fun testPageTitle() = onView(allOf(withId(R.id.title), withText("Test_Page_1"))) +private fun testPageTitle() = onView(withId(R.id.title)) private fun pageUrl(url: String) = onView(allOf(withId(R.id.url), withText(url))) From 936bb85d6c6ed743b99609a65ffa1a993f53a5e8 Mon Sep 17 00:00:00 2001 From: iorgamgabriel Date: Fri, 23 Feb 2024 17:23:46 +0200 Subject: [PATCH 048/238] Bug 1878921 - Translations CouldNotLoadLanguagesError UI "Try Again" --- .../fenix/translations/TranslationsDialogFragment.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogFragment.kt index b28fcbb8e..c8c6034c1 100644 --- a/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogFragment.kt @@ -29,6 +29,7 @@ import androidx.navigation.fragment.navArgs import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialogFragment import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.concept.engine.translate.TranslationError import mozilla.components.lib.state.ext.observeAsComposableState import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import org.mozilla.fenix.BrowserDirection @@ -212,7 +213,11 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() { onSettingClicked = onSettingClicked, onLearnMoreClicked = { openBrowserAndLoad(learnMoreUrl) }, onPositiveButtonClicked = { - translationsDialogStore.dispatch(TranslationsDialogAction.TranslateAction) + if (state.error is TranslationError.CouldNotLoadLanguagesError) { + translationsDialogStore.dispatch(TranslationsDialogAction.FetchSupportedLanguages) + } else { + translationsDialogStore.dispatch(TranslationsDialogAction.TranslateAction) + } }, onNegativeButtonClicked = { if (state.isTranslated) { From ddb15214df722d42307ca31133c9414df4f97354 Mon Sep 17 00:00:00 2001 From: iorgamgabriel Date: Wed, 14 Feb 2024 17:44:22 +0200 Subject: [PATCH 049/238] Bug 1875817 - Translations Integration Toolbar Showing --- .../mozilla/fenix/browser/BrowserFragment.kt | 72 +++++--- .../fenix/browser/TranslationsBinding.kt | 72 ++++++++ app/src/main/res/values/strings.xml | 4 + .../fenix/browser/TranslationsBindingTest.kt | 168 ++++++++++++++++++ 4 files changed, 294 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/browser/TranslationsBinding.kt create mode 100644 app/src/test/java/org/mozilla/fenix/browser/TranslationsBindingTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt index 0d37c0847..f4faa1c12 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt @@ -25,6 +25,7 @@ import mozilla.components.browser.state.state.TabSessionState import mozilla.components.browser.thumbnails.BrowserThumbnails import mozilla.components.browser.toolbar.BrowserToolbar import mozilla.components.concept.engine.permission.SitePermissions +import mozilla.components.concept.toolbar.Toolbar import mozilla.components.feature.app.links.AppLinksUseCases import mozilla.components.feature.contextmenu.ContextMenuCandidate import mozilla.components.feature.readerview.ReaderViewFeature @@ -67,6 +68,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { private val standardSnackbarErrorBinding = ViewBoundFeatureWrapper() private val reviewQualityCheckFeature = ViewBoundFeatureWrapper() + private val translationsBinding = ViewBoundFeatureWrapper() private var readerModeAvailable = false private var reviewQualityCheckAvailable = false @@ -150,7 +152,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { browserToolbarView.view.addPageAction(readerModeAction) - initTranslationsAction(context) + initTranslationsAction(context, view) initReviewQualityCheck(context, view) thumbnailsFeature.set( @@ -236,32 +238,58 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { } } - private fun initTranslationsAction(context: Context) { + private fun initTranslationsAction(context: Context, view: View) { if (!context.settings().enableTranslations) { return } - val translationsAction = - BrowserToolbar.ToggleButton( - image = AppCompatResources.getDrawable( - context, - R.drawable.mozac_ic_translate_24, - )!!.apply { - setTint(ContextCompat.getColor(context, R.color.fx_mobile_text_color_primary)) - }, - imageSelected = AppCompatResources.getDrawable( - context, - R.drawable.mozac_ic_translate_24, - )!!, - contentDescription = context.getString(R.string.browser_toolbar_translate), - contentDescriptionSelected = "", - visible = { - translationsAvailable || context.settings().enableTranslations - }, - listener = { browserToolbarInteractor.onTranslationsButtonClicked() }, - ) - + val translationsAction = Toolbar.ActionButton( + AppCompatResources.getDrawable( + context, + R.drawable.mozac_ic_translate_24, + )!!.apply { + setTint(ContextCompat.getColor(context, R.color.fx_mobile_text_color_primary)) + }, + contentDescription = context.getString(R.string.browser_toolbar_translate), + visible = { translationsAvailable }, + listener = { + browserToolbarInteractor.onTranslationsButtonClicked() + }, + ) browserToolbarView.view.addPageAction(translationsAction) + + getCurrentTab()?.let { + translationsBinding.set( + feature = TranslationsBinding( + browserStore = context.components.core.store, + sessionId = it.id, + onStateUpdated = { isVisible, isTranslated, fromSelectedLanguage, toSelectedLanguage -> + translationsAvailable = isVisible + + translationsAction.updateView( + tintColorResource = if (isTranslated) { + R.color.fx_mobile_icon_color_accent_violet + } else { + R.color.fx_mobile_text_color_primary + }, + contentDescription = if (isTranslated) { + context.getString( + R.string.browser_toolbar_translated_successfully, + fromSelectedLanguage?.localizedDisplayName, + toSelectedLanguage?.localizedDisplayName, + ) + } else { + context.getString(R.string.browser_toolbar_translate) + }, + ) + + safeInvalidateBrowserToolbarView() + }, + ), + owner = this, + view = view, + ) + } } private fun initReviewQualityCheck(context: Context, view: View) { diff --git a/app/src/main/java/org/mozilla/fenix/browser/TranslationsBinding.kt b/app/src/main/java/org/mozilla/fenix/browser/TranslationsBinding.kt new file mode 100644 index 000000000..433657da3 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/browser/TranslationsBinding.kt @@ -0,0 +1,72 @@ +/* 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/. */ + +package org.mozilla.fenix.browser + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChangedBy +import kotlinx.coroutines.flow.mapNotNull +import mozilla.components.browser.state.selector.findTab +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.TranslationsState +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.concept.engine.translate.Language +import mozilla.components.concept.engine.translate.initialFromLanguage +import mozilla.components.concept.engine.translate.initialToLanguage +import mozilla.components.lib.state.helpers.AbstractBinding + +/** + * A binding for observing [TranslationsState] changes + * from the [BrowserStore] and updating the translations action button. + * + * @param browserStore [BrowserStore] observed for any changes related to [TranslationsState]. + * @param sessionId Current open tab session id. + * @param onStateUpdated Invoked when the translations action button should be updated with the new translations state. + */ +class TranslationsBinding( + private val browserStore: BrowserStore, + private val sessionId: String, + private val onStateUpdated: ( + isVisible: Boolean, + isTranslated: Boolean, + fromSelectedLanguage: Language?, + toSelectedLanguage: Language?, + ) -> Unit, +) : AbstractBinding(browserStore) { + + override suspend fun onState(flow: Flow) { + flow.mapNotNull { state -> state.findTab(sessionId) }.distinctUntilChangedBy { + it.translationsState + }.collect { sessionState -> + val translationsState = sessionState.translationsState + + if (translationsState.isTranslated) { + val fromSelected = translationsState.translationEngineState?.initialFromLanguage( + translationsState.supportedLanguages?.fromLanguages, + ) + val toSelected = translationsState.translationEngineState?.initialToLanguage( + translationsState.supportedLanguages?.toLanguages, + ) + + if (fromSelected != null && toSelected != null) { + onStateUpdated( + true, + true, + fromSelected, + toSelected, + ) + } + } else if (translationsState.isExpectedTranslate) { + onStateUpdated( + true, + false, + null, + null, + ) + } else { + onStateUpdated(false, false, null, null) + } + } + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 194a28d3c..fd77f620b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -242,6 +242,10 @@ Erase browsing history Translate page + + Page translated from %1$s to %2$s. diff --git a/app/src/test/java/org/mozilla/fenix/browser/TranslationsBindingTest.kt b/app/src/test/java/org/mozilla/fenix/browser/TranslationsBindingTest.kt new file mode 100644 index 000000000..b58169f98 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/browser/TranslationsBindingTest.kt @@ -0,0 +1,168 @@ +/* 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/. */ + +package org.mozilla.fenix.browser + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import mozilla.components.browser.state.action.TranslationsAction +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.createTab +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.concept.engine.translate.DetectedLanguages +import mozilla.components.concept.engine.translate.Language +import mozilla.components.concept.engine.translate.TranslationEngineState +import mozilla.components.concept.engine.translate.TranslationOperation +import mozilla.components.concept.engine.translate.TranslationPair +import mozilla.components.concept.engine.translate.TranslationSupport +import mozilla.components.support.test.ext.joinBlocking +import mozilla.components.support.test.rule.MainCoroutineRule +import mozilla.components.support.test.rule.runTestOnMain +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify + +@RunWith(AndroidJUnit4::class) +class TranslationsBindingTest { + @get:Rule + val coroutineRule = MainCoroutineRule() + + lateinit var browserStore: BrowserStore + + private val tabId = "1" + private val tab = createTab(url = tabId, id = tabId) + private val onIconChanged: ( + isVisible: Boolean, + isTranslated: Boolean, + fromSelectedLanguage: Language?, + toSelectedLanguage: Language?, + ) -> Unit = spy() + + @Test + fun `GIVEN translationState WHEN translation status isTranslated THEN invoke onIconChanged callback`() = + runTestOnMain { + val englishLanguage = Language("en", "English") + val spanishLanguage = Language("es", "Spanish") + + browserStore = BrowserStore( + BrowserState( + tabs = listOf(tab), + selectedTabId = tabId, + ), + ) + + val binding = TranslationsBinding( + browserStore = browserStore, + sessionId = tabId, + onStateUpdated = onIconChanged, + ) + binding.start() + + val detectedLanguages = DetectedLanguages( + documentLangTag = englishLanguage.code, + supportedDocumentLang = true, + userPreferredLangTag = spanishLanguage.code, + ) + + val translationEngineState = TranslationEngineState( + detectedLanguages = detectedLanguages, + error = null, + isEngineReady = true, + requestedTranslationPair = TranslationPair( + fromLanguage = englishLanguage.code, + toLanguage = spanishLanguage.code, + ), + ) + + val supportLanguages = TranslationSupport( + fromLanguages = listOf(englishLanguage), + toLanguages = listOf(spanishLanguage), + ) + + browserStore.dispatch( + TranslationsAction.SetSupportedLanguagesAction( + tabId = tab.id, + supportedLanguages = supportLanguages, + ), + ).joinBlocking() + + browserStore.dispatch( + TranslationsAction.TranslateStateChangeAction( + tabId = tabId, + translationEngineState = translationEngineState, + ), + ).joinBlocking() + + browserStore.dispatch( + TranslationsAction.TranslateSuccessAction( + tabId = tab.id, + operation = TranslationOperation.TRANSLATE, + ), + ).joinBlocking() + + verify(onIconChanged).invoke( + true, + true, + englishLanguage, + spanishLanguage, + ) + } + + @Test + fun `GIVEN translationState WHEN translation status isExpectedTranslate THEN invoke onIconChanged callback`() = + runTestOnMain { + browserStore = BrowserStore( + BrowserState( + tabs = listOf(tab), + selectedTabId = tabId, + ), + ) + + val binding = TranslationsBinding( + browserStore = browserStore, + sessionId = tabId, + onStateUpdated = onIconChanged, + ) + binding.start() + + browserStore.dispatch( + TranslationsAction.TranslateExpectedAction( + tabId = tabId, + ), + ).joinBlocking() + + verify(onIconChanged).invoke( + true, + false, + null, + null, + ) + } + + @Test + fun `GIVEN translationState WHEN translation status is not isExpectedTranslate or isTranslated THEN invoke onIconChanged callback`() = + runTestOnMain { + browserStore = BrowserStore( + BrowserState( + tabs = listOf(tab), + selectedTabId = tabId, + ), + ) + + val binding = TranslationsBinding( + browserStore = browserStore, + sessionId = tabId, + onStateUpdated = onIconChanged, + ) + binding.start() + + verify(onIconChanged).invoke( + false, + false, + null, + null, + ) + } +} From 7802427e22fa02187f1776bfd4c70966cc46fc02 Mon Sep 17 00:00:00 2001 From: "oana.horvath" Date: Mon, 26 Feb 2024 12:54:14 +0200 Subject: [PATCH 050/238] Bug 1882035 - Disable non-sponsored suggestions UI tests in FirefoxSuggestTest --- .../java/org/mozilla/fenix/ui/FirefoxSuggestTest.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/FirefoxSuggestTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/FirefoxSuggestTest.kt index 1f8b929fc..ae384d063 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/FirefoxSuggestTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/FirefoxSuggestTest.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix.ui import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.mozilla.fenix.customannotations.SmokeTest @@ -192,6 +193,7 @@ class FirefoxSuggestTest { // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348374 // Known bug that might affect this UI test: https://bugzilla.mozilla.org/show_bug.cgi?id=1813587 + @Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1882035") @SmokeTest @Test fun verifyFirefoxSuggestNonSponsoredSearchResultsTest() { @@ -219,6 +221,7 @@ class FirefoxSuggestTest { // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348375 // Known bug that might affect this UI test: https://bugzilla.mozilla.org/show_bug.cgi?id=1813587 + @Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1882035") @Test fun verifyFirefoxSuggestNonSponsoredSearchResultsWithPartialKeywordTest() { runWithCondition(TestHelper.appContext.settings().enableFxSuggest) { @@ -239,6 +242,7 @@ class FirefoxSuggestTest { // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348376 // Known bug that might affect this UI test: https://bugzilla.mozilla.org/show_bug.cgi?id=1813587 + @Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1882035") @Test fun openFirefoxSuggestNonSponsoredSearchResultsTest() { runWithCondition(TestHelper.appContext.settings().enableFxSuggest) { From dea7509173c73d6f87647404f0a5784ce1bb8e34 Mon Sep 17 00:00:00 2001 From: James Hugman Date: Fri, 26 Jan 2024 22:12:38 +0000 Subject: [PATCH 051/238] =?UTF-8?q?Bug=201880483=20=E2=80=94=C2=A0Add=20in?= =?UTF-8?q?verse=20triggers=20to=20messaging=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a breaking change. This changes the `trigger` key to `trigger-if-all` to document that all triggers in this list must evaluate to true. It also adds `exclude-if-any`, a list of triggers which, if any are true will exclude the message. This will remove the need for triggers like `I_AM_NOT_BROWSER_DEFAULT`. --- app/messaging-evergreen-messages.fml.yaml | 10 ++++++---- .../mozilla/fenix/ui/NimbusMessagingHomescreenTest.kt | 2 +- .../java/org/mozilla/fenix/components/AppStoreTest.kt | 1 + .../fenix/messaging/DefaultMessageControllerTest.kt | 3 ++- .../fenix/messaging/state/MessagingMiddlewareTest.kt | 3 ++- .../fenix/messaging/state/MessagingReducerTest.kt | 2 +- 6 files changed, 13 insertions(+), 8 deletions(-) diff --git a/app/messaging-evergreen-messages.fml.yaml b/app/messaging-evergreen-messages.fml.yaml index 36e7d2b15..3c63e3ed9 100644 --- a/app/messaging-evergreen-messages.fml.yaml +++ b/app/messaging-evergreen-messages.fml.yaml @@ -29,9 +29,10 @@ import: text: default_browser_experiment_card_text surface: homescreen action: "MAKE_DEFAULT_BROWSER" - trigger: - - I_AM_NOT_DEFAULT_BROWSER + trigger-if-all: - USER_ESTABLISHED_INSTALL + exclude-if-any: + - I_AM_DEFAULT_BROWSER style: PERSISTENT button-label: preferences_set_as_default_browser @@ -47,7 +48,8 @@ import: text: nimbus_notification_default_browser_text surface: notification style: NOTIFICATION - trigger: - - I_AM_NOT_DEFAULT_BROWSER + trigger-if-all: - DAY_3_AFTER_INSTALL + exclude-if-any: + - I_AM_DEFAULT_BROWSER action: MAKE_DEFAULT_BROWSER diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingHomescreenTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingHomescreenTest.kt index 3fcdeaf4e..f71784acc 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingHomescreenTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingHomescreenTest.kt @@ -66,7 +66,7 @@ class NimbusMessagingHomescreenTest { buttonLabel = Res.string(messageButtonLabel), text = Res.string(messageText), title = Res.string(messageTitle), - trigger = listOf("ALWAYS"), + triggerIfAll = listOf("ALWAYS"), ), ), styles = mapOf( diff --git a/app/src/test/java/org/mozilla/fenix/components/AppStoreTest.kt b/app/src/test/java/org/mozilla/fenix/components/AppStoreTest.kt index 78d0fabae..cfd2e2876 100644 --- a/app/src/test/java/org/mozilla/fenix/components/AppStoreTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/AppStoreTest.kt @@ -113,6 +113,7 @@ class AppStoreTest { "action", mockk(), emptyList(), + emptyList(), mockk(), ) appStore.dispatch(UpdateMessageToShow(message)).join() diff --git a/app/src/test/java/org/mozilla/fenix/messaging/DefaultMessageControllerTest.kt b/app/src/test/java/org/mozilla/fenix/messaging/DefaultMessageControllerTest.kt index 35d5d8b48..d94c96dfc 100644 --- a/app/src/test/java/org/mozilla/fenix/messaging/DefaultMessageControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/messaging/DefaultMessageControllerTest.kt @@ -69,7 +69,8 @@ class DefaultMessageControllerTest { data = data, style = mockk(relaxed = true), action = "action", - triggers = emptyList(), + triggerIfAll = emptyList(), + excludeIfAny = emptyList(), metadata = Message.Metadata( id = "id", displayCount = 0, diff --git a/app/src/test/java/org/mozilla/fenix/messaging/state/MessagingMiddlewareTest.kt b/app/src/test/java/org/mozilla/fenix/messaging/state/MessagingMiddlewareTest.kt index b4f72cd14..88b916158 100644 --- a/app/src/test/java/org/mozilla/fenix/messaging/state/MessagingMiddlewareTest.kt +++ b/app/src/test/java/org/mozilla/fenix/messaging/state/MessagingMiddlewareTest.kt @@ -355,7 +355,8 @@ private fun createMessage( action: String = "action", styleData: StyleData = StyleData(), triggers: List = listOf("triggers"), -) = Message(messageId, data, action, styleData, triggers, metadata) + except: List = listOf(), +) = Message(messageId, data, action, styleData, triggers, except, metadata) private fun createMetadata( displayCount: Int = 0, diff --git a/app/src/test/java/org/mozilla/fenix/messaging/state/MessagingReducerTest.kt b/app/src/test/java/org/mozilla/fenix/messaging/state/MessagingReducerTest.kt index dbb111eb4..43d1f44e9 100644 --- a/app/src/test/java/org/mozilla/fenix/messaging/state/MessagingReducerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/messaging/state/MessagingReducerTest.kt @@ -52,7 +52,7 @@ class MessagingReducerTest { data = MessageData(surface = surface), action = action, style = StyleData(), - triggers = listOf(), + triggerIfAll = listOf(), metadata = Message.Metadata(id = id), ) From cc83d5c4726881d00791392538b2a43aa6815c79 Mon Sep 17 00:00:00 2001 From: William Durand Date: Thu, 15 Feb 2024 10:49:00 +0100 Subject: [PATCH 052/238] Bug 1870339 - Make list of add-ons more accessible --- .../SettingsSubMenuAddonsManagerRobot.kt | 21 ++++++++++++---- .../fenix/addons/AddonsManagementFragment.kt | 24 +++++++++++++++++++ 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuAddonsManagerRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuAddonsManagerRobot.kt index 95ea5f4c4..96d23ec27 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuAddonsManagerRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuAddonsManagerRobot.kt @@ -36,6 +36,7 @@ import org.mozilla.fenix.helpers.Constants.RETRY_COUNT import org.mozilla.fenix.helpers.Constants.TAG import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists +import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdContainingText import org.mozilla.fenix.helpers.MatcherHelper.itemWithText import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeLong @@ -43,7 +44,6 @@ import org.mozilla.fenix.helpers.TestHelper.appName import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.packageName import org.mozilla.fenix.helpers.TestHelper.restartApp -import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText import org.mozilla.fenix.helpers.click import org.mozilla.fenix.helpers.ext.waitNotNull @@ -120,7 +120,7 @@ class SettingsSubMenuAddonsManagerRobot { homeScreen { }.openThreeDotMenu { }.openAddonsManagerMenu { - scrollToElementByText(addonName) + scrollToAddon(addonName) clickInstallAddon(addonName) verifyAddonPermissionPrompt(addonName) acceptPermissionToInstallAddon() @@ -152,7 +152,7 @@ class SettingsSubMenuAddonsManagerRobot { } fun verifyAddonIsInstalled(addonName: String) { - scrollToElementByText(addonName) + scrollToAddon(addonName) Log.i(TAG, "verifyAddonIsInstalled: Trying to verify that the $addonName add-on was installed") onView( allOf( @@ -199,7 +199,7 @@ class SettingsSubMenuAddonsManagerRobot { Log.i(TAG, "verifyAddonsItems: Verified that all uBlock Origin items are completely displayed") } fun verifyAddonCanBeInstalled(addonName: String) { - scrollToElementByText(addonName) + scrollToAddon(addonName) mDevice.waitNotNull(Until.findObject(By.text(addonName)), waitingTime) Log.i(TAG, "verifyAddonCanBeInstalled: Trying to verify that the install $addonName button is visible") onView( @@ -250,7 +250,7 @@ class SettingsSubMenuAddonsManagerRobot { addonName: String, interact: SettingsSubMenuAddonsManagerAddonDetailedMenuRobot.() -> Unit, ): SettingsSubMenuAddonsManagerAddonDetailedMenuRobot.Transition { - scrollToElementByText(addonName) + scrollToAddon(addonName) Log.i(TAG, "openDetailedMenuForAddon: Trying to verify that the $addonName add-on is visible") addonItem(addonName).check(matches(withEffectiveVisibility(Visibility.VISIBLE))) Log.i(TAG, "openDetailedMenuForAddon: Verified that the $addonName add-on is visible") @@ -297,6 +297,17 @@ fun addonsMenu(interact: SettingsSubMenuAddonsManagerRobot.() -> Unit): Settings return SettingsSubMenuAddonsManagerRobot.Transition() } +private fun scrollToAddon(addonName: String) { + Log.i(TAG, "scrollToAddon: Trying to scroll into view add-on: $addonName") + addonsList().scrollIntoView( + itemWithResIdContainingText( + resourceId = "$packageName:id/add_on_name", + text = addonName, + ), + ) + Log.i(TAG, "scrollToAddon: Scrolled into view add-on: $addonName") +} + private fun addonItem(addonName: String) = onView( allOf( diff --git a/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt b/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt index e7c739433..1103b008a 100644 --- a/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt @@ -11,6 +11,7 @@ import android.os.Build import android.os.Bundle import android.view.View import android.view.accessibility.AccessibilityEvent +import android.view.accessibility.AccessibilityNodeInfo import androidx.annotation.VisibleForTesting import androidx.core.view.isVisible import androidx.fragment.app.Fragment @@ -115,6 +116,29 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management) binding?.addOnsEmptyMessage?.isVisible = false recyclerView?.adapter = adapter + recyclerView?.accessibilityDelegate = object : View.AccessibilityDelegate() { + override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) { + super.onInitializeAccessibilityNodeInfo(host, info) + + adapter?.let { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + info.collectionInfo = AccessibilityNodeInfo.CollectionInfo( + it.itemCount, + 1, + false, + ) + } else { + @Suppress("DEPRECATION") + info.collectionInfo = AccessibilityNodeInfo.CollectionInfo.obtain( + it.itemCount, + 1, + false, + ) + } + } + } + } + if (shouldRefresh) { adapter?.updateAddons(addons) } From e4af8643e10411b1cdfa556073d5bb8c24d6ad5f Mon Sep 17 00:00:00 2001 From: Issam Mani Date: Tue, 13 Feb 2024 02:38:45 +0100 Subject: [PATCH 053/238] Bug 1880075 - Use single name field for addresses --- .../mozilla/fenix/ui/AddressAutofillTest.kt | 52 +++------ .../ui/robots/SettingsSubMenuAutofillRobot.kt | 45 +++----- .../fenix/settings/address/ext/Address.kt | 9 -- .../address/view/AddressEditorView.kt | 12 +-- .../settings/address/view/AddressList.kt | 7 +- .../res/layout/fragment_address_editor.xml | 102 ++---------------- app/src/main/res/values/strings.xml | 8 +- .../settings/address/AddressEditorViewTest.kt | 16 +-- .../DefaultAddressEditorControllerTest.kt | 4 +- .../fenix/settings/address/ext/AddressTest.kt | 47 +------- .../DefaultAddressEditorInteractorTest.kt | 4 +- 11 files changed, 56 insertions(+), 250 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/AddressAutofillTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/AddressAutofillTest.kt index b8b675778..61a3c0b7f 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/AddressAutofillTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/AddressAutofillTest.kt @@ -24,9 +24,7 @@ class AddressAutofillTest : TestSetup() { var navigateToAutofillSettings = true var isAddressAutofillEnabled = true var userHasSavedAddress = false - var firstName = "Mozilla" - var middleName = "Fenix" - var lastName = "Firefox" + var name = "Mozilla Fenix Firefox" var streetAddress = "Harrison Street" var city = "San Francisco" var state = "Alaska" @@ -38,9 +36,7 @@ class AddressAutofillTest : TestSetup() { object SecondAddressAutofillDetails { var navigateToAutofillSettings = false - var firstName = "Android" - var middleName = "Test" - var lastName = "Name" + var name = "Android Test Name" var streetAddress = "Fort Street" var city = "San Jose" var state = "Arizona" @@ -65,9 +61,7 @@ class AddressAutofillTest : TestSetup() { navigateToAutofillSettings = FirstAddressAutofillDetails.navigateToAutofillSettings, isAddressAutofillEnabled = FirstAddressAutofillDetails.isAddressAutofillEnabled, userHasSavedAddress = FirstAddressAutofillDetails.userHasSavedAddress, - firstName = FirstAddressAutofillDetails.firstName, - middleName = FirstAddressAutofillDetails.middleName, - lastName = FirstAddressAutofillDetails.lastName, + name = FirstAddressAutofillDetails.name, streetAddress = FirstAddressAutofillDetails.streetAddress, city = FirstAddressAutofillDetails.city, state = FirstAddressAutofillDetails.state, @@ -102,9 +96,7 @@ class AddressAutofillTest : TestSetup() { navigateToAutofillSettings = FirstAddressAutofillDetails.navigateToAutofillSettings, isAddressAutofillEnabled = FirstAddressAutofillDetails.isAddressAutofillEnabled, userHasSavedAddress = FirstAddressAutofillDetails.userHasSavedAddress, - firstName = FirstAddressAutofillDetails.firstName, - middleName = FirstAddressAutofillDetails.middleName, - lastName = FirstAddressAutofillDetails.lastName, + name = FirstAddressAutofillDetails.name, streetAddress = FirstAddressAutofillDetails.streetAddress, city = FirstAddressAutofillDetails.city, state = FirstAddressAutofillDetails.state, @@ -145,9 +137,7 @@ class AddressAutofillTest : TestSetup() { navigateToAutofillSettings = FirstAddressAutofillDetails.navigateToAutofillSettings, isAddressAutofillEnabled = FirstAddressAutofillDetails.isAddressAutofillEnabled, userHasSavedAddress = FirstAddressAutofillDetails.userHasSavedAddress, - firstName = FirstAddressAutofillDetails.firstName, - middleName = FirstAddressAutofillDetails.middleName, - lastName = FirstAddressAutofillDetails.lastName, + name = FirstAddressAutofillDetails.name, streetAddress = FirstAddressAutofillDetails.streetAddress, city = FirstAddressAutofillDetails.city, state = FirstAddressAutofillDetails.state, @@ -173,9 +163,7 @@ class AddressAutofillTest : TestSetup() { navigateToAutofillSettings = FirstAddressAutofillDetails.navigateToAutofillSettings, isAddressAutofillEnabled = FirstAddressAutofillDetails.isAddressAutofillEnabled, userHasSavedAddress = FirstAddressAutofillDetails.userHasSavedAddress, - firstName = FirstAddressAutofillDetails.firstName, - middleName = FirstAddressAutofillDetails.middleName, - lastName = FirstAddressAutofillDetails.lastName, + name = FirstAddressAutofillDetails.name, streetAddress = FirstAddressAutofillDetails.streetAddress, city = FirstAddressAutofillDetails.city, state = FirstAddressAutofillDetails.state, @@ -219,9 +207,7 @@ class AddressAutofillTest : TestSetup() { navigateToAutofillSettings = FirstAddressAutofillDetails.navigateToAutofillSettings, isAddressAutofillEnabled = FirstAddressAutofillDetails.isAddressAutofillEnabled, userHasSavedAddress = FirstAddressAutofillDetails.userHasSavedAddress, - firstName = FirstAddressAutofillDetails.firstName, - middleName = FirstAddressAutofillDetails.middleName, - lastName = FirstAddressAutofillDetails.lastName, + name = FirstAddressAutofillDetails.name, streetAddress = FirstAddressAutofillDetails.streetAddress, city = FirstAddressAutofillDetails.city, state = FirstAddressAutofillDetails.state, @@ -256,9 +242,7 @@ class AddressAutofillTest : TestSetup() { navigateToAutofillSettings = FirstAddressAutofillDetails.navigateToAutofillSettings, isAddressAutofillEnabled = FirstAddressAutofillDetails.isAddressAutofillEnabled, userHasSavedAddress = FirstAddressAutofillDetails.userHasSavedAddress, - firstName = FirstAddressAutofillDetails.firstName, - middleName = FirstAddressAutofillDetails.middleName, - lastName = FirstAddressAutofillDetails.lastName, + name = FirstAddressAutofillDetails.name, streetAddress = FirstAddressAutofillDetails.streetAddress, city = FirstAddressAutofillDetails.city, state = FirstAddressAutofillDetails.state, @@ -271,9 +255,7 @@ class AddressAutofillTest : TestSetup() { clickAddAddressButton() fillAndSaveAddress( navigateToAutofillSettings = SecondAddressAutofillDetails.navigateToAutofillSettings, - firstName = SecondAddressAutofillDetails.firstName, - middleName = SecondAddressAutofillDetails.middleName, - lastName = SecondAddressAutofillDetails.lastName, + name = SecondAddressAutofillDetails.name, streetAddress = SecondAddressAutofillDetails.streetAddress, city = SecondAddressAutofillDetails.city, state = SecondAddressAutofillDetails.state, @@ -319,9 +301,7 @@ class AddressAutofillTest : TestSetup() { navigateToAutofillSettings = FirstAddressAutofillDetails.navigateToAutofillSettings, isAddressAutofillEnabled = FirstAddressAutofillDetails.isAddressAutofillEnabled, userHasSavedAddress = FirstAddressAutofillDetails.userHasSavedAddress, - firstName = FirstAddressAutofillDetails.firstName, - middleName = FirstAddressAutofillDetails.middleName, - lastName = FirstAddressAutofillDetails.lastName, + name = FirstAddressAutofillDetails.name, streetAddress = FirstAddressAutofillDetails.streetAddress, city = FirstAddressAutofillDetails.city, state = FirstAddressAutofillDetails.state, @@ -334,9 +314,7 @@ class AddressAutofillTest : TestSetup() { clickSavedAddress("Mozilla") fillAndSaveAddress( navigateToAutofillSettings = SecondAddressAutofillDetails.navigateToAutofillSettings, - firstName = SecondAddressAutofillDetails.firstName, - middleName = SecondAddressAutofillDetails.middleName, - lastName = SecondAddressAutofillDetails.lastName, + name = SecondAddressAutofillDetails.name, streetAddress = SecondAddressAutofillDetails.streetAddress, city = SecondAddressAutofillDetails.city, state = SecondAddressAutofillDetails.state, @@ -377,9 +355,7 @@ class AddressAutofillTest : TestSetup() { navigateToAutofillSettings = FirstAddressAutofillDetails.navigateToAutofillSettings, isAddressAutofillEnabled = FirstAddressAutofillDetails.isAddressAutofillEnabled, userHasSavedAddress = FirstAddressAutofillDetails.userHasSavedAddress, - firstName = FirstAddressAutofillDetails.firstName, - middleName = FirstAddressAutofillDetails.middleName, - lastName = FirstAddressAutofillDetails.lastName, + name = FirstAddressAutofillDetails.name, streetAddress = FirstAddressAutofillDetails.streetAddress, city = FirstAddressAutofillDetails.city, state = FirstAddressAutofillDetails.state, @@ -416,9 +392,7 @@ class AddressAutofillTest : TestSetup() { navigateToAutofillSettings = FirstAddressAutofillDetails.navigateToAutofillSettings, isAddressAutofillEnabled = FirstAddressAutofillDetails.isAddressAutofillEnabled, userHasSavedAddress = FirstAddressAutofillDetails.userHasSavedAddress, - firstName = FirstAddressAutofillDetails.firstName, - middleName = FirstAddressAutofillDetails.middleName, - lastName = FirstAddressAutofillDetails.lastName, + name = FirstAddressAutofillDetails.name, streetAddress = FirstAddressAutofillDetails.streetAddress, city = FirstAddressAutofillDetails.city, state = FirstAddressAutofillDetails.state, diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuAutofillRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuAutofillRobot.kt index c9af2fdc6..d73d99f95 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuAutofillRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuAutofillRobot.kt @@ -153,12 +153,10 @@ class SettingsSubMenuAutofillRobot { addAddressToolbarTitle(), navigateBackButton(), toolbarCheckmarkButton(), - firstNameTextInput(), - middleNameTextInput(), + nameTextInput(), ) scrollToElementByText(getStringResource(R.string.addresses_street_address)) assertUIObjectExists( - lastNameTextInput(), streetAddressTextInput(), ) scrollToElementByText(getStringResource(R.string.addresses_country)) @@ -215,12 +213,10 @@ class SettingsSubMenuAutofillRobot { navigateBackButton(), toolbarDeleteAddressButton(), toolbarCheckmarkButton(), - firstNameTextInput(), - middleNameTextInput(), + nameTextInput(), ) scrollToElementByText(getStringResource(R.string.addresses_street_address)) assertUIObjectExists( - lastNameTextInput(), streetAddressTextInput(), ) scrollToElementByText(getStringResource(R.string.addresses_country)) @@ -257,10 +253,10 @@ class SettingsSubMenuAutofillRobot { manageAddressesButton().click() Log.i(TAG, "clickManageAddressesButton: Clicked the \"Manage addresses\" button") } - fun clickSavedAddress(firstName: String) { - Log.i(TAG, "clickSavedAddress: Trying to click the $firstName saved address and and wait for $waitingTime ms for a new window") - savedAddress(firstName).clickAndWaitForNewWindow(waitingTime) - Log.i(TAG, "clickSavedAddress: Clicked the $firstName saved address and and waited for $waitingTime ms for a new window") + fun clickSavedAddress(name: String) { + Log.i(TAG, "clickSavedAddress: Trying to click the $name saved address and and wait for $waitingTime ms for a new window") + savedAddress(name).clickAndWaitForNewWindow(waitingTime) + Log.i(TAG, "clickSavedAddress: Clicked the $name saved address and and waited for $waitingTime ms for a new window") } fun clickDeleteAddressButton() { Log.i(TAG, "clickDeleteAddressButton: Waiting for $waitingTime ms for the delete address toolbar button to exist") @@ -307,9 +303,7 @@ class SettingsSubMenuAutofillRobot { navigateToAutofillSettings: Boolean, isAddressAutofillEnabled: Boolean = true, userHasSavedAddress: Boolean = false, - firstName: String, - middleName: String, - lastName: String, + name: String, streetAddress: String, city: String, state: String, @@ -327,21 +321,15 @@ class SettingsSubMenuAutofillRobot { clickAddAddressButton() } } - Log.i(TAG, "fillAndSaveAddress: Waiting for $waitingTime ms for \"First Name\" text field to exist") - firstNameTextInput().waitForExists(waitingTime) - Log.i(TAG, "fillAndSaveAddress: Waited for $waitingTime ms for \"First Name\" text field to exist") + Log.i(TAG, "fillAndSaveAddress: Waiting for $waitingTime ms for \"Name\" text field to exist") + nameTextInput().waitForExists(waitingTime) + Log.i(TAG, "fillAndSaveAddress: Waited for $waitingTime ms for \"Name\" text field to exist") Log.i(TAG, "fillAndSaveAddress: Trying to click device back button to dismiss keyboard using device back button") mDevice.pressBack() Log.i(TAG, "fillAndSaveAddress: Clicked device back button to dismiss keyboard using device back button") - Log.i(TAG, "fillAndSaveAddress: Trying to set \"First Name\" to $firstName") - firstNameTextInput().setText(firstName) - Log.i(TAG, "fillAndSaveAddress: \"First Name\" was set to $firstName") - Log.i(TAG, "fillAndSaveAddress: Trying to set \"Middle Name\" to $middleName") - middleNameTextInput().setText(middleName) - Log.i(TAG, "fillAndSaveAddress: \"Middle Name\" was set to $middleName") - Log.i(TAG, "fillAndSaveAddress: Trying to set \"Last Name\" to $lastName") - lastNameTextInput().setText(lastName) - Log.i(TAG, "fillAndSaveAddress: \"Last Name\" was set to $lastName") + Log.i(TAG, "fillAndSaveAddress: Trying to set \"Name\" to $name") + nameTextInput().setText(name) + Log.i(TAG, "fillAndSaveAddress: \"Name\" was set to $name") Log.i(TAG, "fillAndSaveAddress: Trying to set \"Street Address\" to $streetAddress") streetAddressTextInput().setText(streetAddress) Log.i(TAG, "fillAndSaveAddress: \"Street Address\" was set to $streetAddress") @@ -616,13 +604,12 @@ private fun manageAddressesButton() = .resourceId("android:id/title") .text(getStringResource(R.string.preferences_addresses_manage_addresses)), ) + private fun addAddressToolbarTitle() = itemContainingText(getStringResource(R.string.addresses_add_address)) private fun editAddressToolbarTitle() = itemContainingText(getStringResource(R.string.addresses_edit_address)) private fun toolbarCheckmarkButton() = itemWithResId("$packageName:id/save_address_button") private fun navigateBackButton() = itemWithDescription(getStringResource(R.string.action_bar_up_description)) -private fun firstNameTextInput() = itemWithResId("$packageName:id/first_name_input") -private fun middleNameTextInput() = itemWithResId("$packageName:id/middle_name_input") -private fun lastNameTextInput() = itemWithResId("$packageName:id/last_name_input") +private fun nameTextInput() = itemWithResId("$packageName:id/name_input") private fun streetAddressTextInput() = itemWithResId("$packageName:id/street_address_input") private fun cityTextInput() = itemWithResId("$packageName:id/city_input") private fun subRegionDropDown() = itemWithResId("$packageName:id/subregion_drop_down") @@ -657,7 +644,7 @@ private fun confirmDeleteCreditCardButton() = onView(withId(android.R.id.button1 private fun cancelDeleteCreditCardButton() = onView(withId(android.R.id.button2)).inRoot(RootMatchers.isDialog()) private fun securedCreditCardsLaterButton() = onView(withId(android.R.id.button2)).inRoot(RootMatchers.isDialog()) -private fun savedAddress(firstName: String) = mDevice.findObject(UiSelector().textContains(firstName)) +private fun savedAddress(name: String) = mDevice.findObject(UiSelector().textContains(name)) private fun subRegionOption(subRegion: String) = mDevice.findObject(UiSelector().textContains(subRegion)) private fun countryOption(country: String) = mDevice.findObject(UiSelector().textContains(country)) diff --git a/app/src/main/java/org/mozilla/fenix/settings/address/ext/Address.kt b/app/src/main/java/org/mozilla/fenix/settings/address/ext/Address.kt index 49cf591e8..95a2fa638 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/address/ext/Address.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/address/ext/Address.kt @@ -7,15 +7,6 @@ package org.mozilla.fenix.settings.address.ext import androidx.annotation.VisibleForTesting import mozilla.components.concept.storage.Address -/** - * Generate a label item text for an [Address]. The combination of names is based on desktop code - * found here: - * https://searchfox.org/mozilla-central/rev/d989c65584ded72c2de85cb40bede7ac2f176387/toolkit/components/formautofill/FormAutofillNameUtils.jsm#400 - */ -fun Address.getFullName(): String = listOf(givenName, additionalName, familyName) - .filter { it.isNotEmpty() } - .joinToString(" ") - /** * Generate a description item text for an [Address]. The element ordering is based on the * priorities defined by the desktop code found here: diff --git a/app/src/main/java/org/mozilla/fenix/settings/address/view/AddressEditorView.kt b/app/src/main/java/org/mozilla/fenix/settings/address/view/AddressEditorView.kt index 18858e0ce..e57ad0d87 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/address/view/AddressEditorView.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/address/view/AddressEditorView.kt @@ -47,7 +47,7 @@ class AddressEditorView( * Binds the view in the [AddressEditorFragment], using the current [Address] if available. */ fun bind() { - binding.firstNameInput.apply { + binding.nameInput.apply { requestFocus() placeCursorAtEnd() showKeyboard() @@ -64,11 +64,7 @@ class AddressEditorView( address?.let { address -> binding.emailInput.setText(address.email) binding.phoneInput.setText(address.tel) - - binding.firstNameInput.setText(address.givenName) - binding.middleNameInput.setText(address.additionalName) - binding.lastNameInput.setText(address.familyName) - + binding.nameInput.setText(address.name) binding.streetAddressInput.setText(address.streetAddress) binding.cityInput.setText(address.addressLevel2) binding.zipInput.setText(address.postalCode) @@ -88,9 +84,7 @@ class AddressEditorView( binding.root.hideKeyboard() val addressFields = UpdatableAddressFields( - givenName = binding.firstNameInput.text.toString(), - additionalName = binding.middleNameInput.text.toString(), - familyName = binding.lastNameInput.text.toString(), + name = binding.nameInput.text.toString(), organization = "", streetAddress = binding.streetAddressInput.text.toString(), addressLevel3 = "", diff --git a/app/src/main/java/org/mozilla/fenix/settings/address/view/AddressList.kt b/app/src/main/java/org/mozilla/fenix/settings/address/view/AddressList.kt index a24d8d837..c00010605 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/address/view/AddressList.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/address/view/AddressList.kt @@ -20,7 +20,6 @@ import org.mozilla.fenix.R import org.mozilla.fenix.compose.list.IconListItem import org.mozilla.fenix.compose.list.TextListItem import org.mozilla.fenix.settings.address.ext.getAddressLabel -import org.mozilla.fenix.settings.address.ext.getFullName import org.mozilla.fenix.theme.FirefoxTheme /** @@ -39,7 +38,7 @@ fun AddressList( LazyColumn { items(addresses) { address -> TextListItem( - label = address.getFullName(), + label = address.name, modifier = Modifier.padding(start = 56.dp), description = address.getAddressLabel(), maxDescriptionLines = 2, @@ -66,9 +65,7 @@ private fun AddressListPreview() { addresses = listOf( Address( guid = "1", - givenName = "Banana", - additionalName = "", - familyName = "Apple", + name = "Banana Apple", organization = "Mozilla", streetAddress = "123 Sesame Street", addressLevel3 = "", diff --git a/app/src/main/res/layout/fragment_address_editor.xml b/app/src/main/res/layout/fragment_address_editor.xml index 82d559b65..4f6e4e14d 100644 --- a/app/src/main/res/layout/fragment_address_editor.xml +++ b/app/src/main/res/layout/fragment_address_editor.xml @@ -14,24 +14,24 @@ android:layout_height="wrap_content" android:layout_margin="16dp"> - + + app:layout_constraintTop_toBottomOf="@+id/name_title"> - - - - - - - - - - - - - - - - - - + app:layout_constraintTop_toBottomOf="@+id/name_layout" /> Manage addresses - First Name + First Name - Middle Name + Middle Name - Last Name + Last Name + + Name Street Address diff --git a/app/src/test/java/org/mozilla/fenix/settings/address/AddressEditorViewTest.kt b/app/src/test/java/org/mozilla/fenix/settings/address/AddressEditorViewTest.kt index 550cc1c65..1705d773b 100644 --- a/app/src/test/java/org/mozilla/fenix/settings/address/AddressEditorViewTest.kt +++ b/app/src/test/java/org/mozilla/fenix/settings/address/AddressEditorViewTest.kt @@ -68,9 +68,7 @@ class AddressEditorViewTest { addressEditorView.saveAddress() val expected = UpdatableAddressFields( - givenName = address.givenName, - additionalName = address.additionalName, - familyName = address.familyName, + name = address.name, organization = "", streetAddress = address.streetAddress, addressLevel3 = "", @@ -95,9 +93,7 @@ class AddressEditorViewTest { addressEditorView.saveAddress() val expected = UpdatableAddressFields( - givenName = "", - additionalName = "", - familyName = "", + name = "", organization = "", streetAddress = "", addressLevel3 = "", @@ -137,9 +133,7 @@ class AddressEditorViewTest { assertEquals(address.addressLevel1, binding.subregionDropDown.selectedItem.toString()) assertEquals("City", binding.cityInput.text.toString()) assertEquals("Street", binding.streetAddressInput.text.toString()) - assertEquals("Family", binding.lastNameInput.text.toString()) - assertEquals("Given", binding.firstNameInput.text.toString()) - assertEquals("Additional", binding.middleNameInput.text.toString()) + assertEquals("Name", binding.nameInput.text.toString()) assertEquals("email@mozilla.com", binding.emailInput.text.toString()) assertEquals("Telephone", binding.phoneInput.text.toString()) } @@ -334,9 +328,7 @@ class AddressEditorViewTest { private fun generateAddress(country: String = "US", addressLevel1: String = "Oregon") = Address( guid = "123", - givenName = "Given", - additionalName = "Additional", - familyName = "Family", + name = "Name", organization = "Organization", streetAddress = "Street", addressLevel3 = "Suburb", diff --git a/app/src/test/java/org/mozilla/fenix/settings/address/controller/DefaultAddressEditorControllerTest.kt b/app/src/test/java/org/mozilla/fenix/settings/address/controller/DefaultAddressEditorControllerTest.kt index 3046b3a39..887dfad34 100644 --- a/app/src/test/java/org/mozilla/fenix/settings/address/controller/DefaultAddressEditorControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/settings/address/controller/DefaultAddressEditorControllerTest.kt @@ -53,9 +53,7 @@ class DefaultAddressEditorControllerTest { @Test fun `GIVEN a new address record WHEN save address is called THEN save the new address record to storage`() = runTestOnMain { val addressFields = UpdatableAddressFields( - givenName = "John", - additionalName = "", - familyName = "Smith", + name = "John Smith", organization = "Mozilla", streetAddress = "123 Sesame Street", addressLevel3 = "", diff --git a/app/src/test/java/org/mozilla/fenix/settings/address/ext/AddressTest.kt b/app/src/test/java/org/mozilla/fenix/settings/address/ext/AddressTest.kt index 367238643..a41d0c707 100644 --- a/app/src/test/java/org/mozilla/fenix/settings/address/ext/AddressTest.kt +++ b/app/src/test/java/org/mozilla/fenix/settings/address/ext/AddressTest.kt @@ -9,41 +9,6 @@ import org.junit.Assert.assertEquals import org.junit.Test class AddressTest { - @Test - fun `WHEN all names are populated THEN label includes all names`() { - val addr = generateAddress() - - val label = addr.getFullName() - - assertEquals("${addr.givenName} ${addr.additionalName} ${addr.familyName}", label) - } - - @Test - fun `WHEN middle name is missing THEN label is given and family combined`() { - val addr = generateAddress(additionalName = "") - - val label = addr.getFullName() - - assertEquals("${addr.givenName} ${addr.familyName}", label) - } - - @Test - fun `WHEN only family and middle name are available THEN label is middle and family combined`() { - val addr = generateAddress(givenName = "") - - val label = addr.getFullName() - - assertEquals("${addr.additionalName} ${addr.familyName}", label) - } - - @Test - fun `WHEN only family name is available THEN label is family name`() { - val addr = generateAddress(givenName = "", additionalName = "") - - val label = addr.getFullName() - - assertEquals(addr.familyName, label) - } @Test fun `WHEN all properties are present THEN all properties present in description`() { @@ -76,9 +41,7 @@ class AddressTest { @Test fun `WHEN everything is missing THEN description is empty`() { val addr = generateAddress( - givenName = "", - additionalName = "", - familyName = "", + name = "", organization = "", streetAddress = "", addressLevel3 = "", @@ -109,9 +72,7 @@ class AddressTest { } private fun generateAddress( - givenName: String = "Firefox", - additionalName: String = "The", - familyName: String = "Browser", + name: String = "Firefox The Browser", organization: String = "Mozilla", streetAddress: String = "street", addressLevel3: String = "3", @@ -123,9 +84,7 @@ class AddressTest { email: String = "email", ) = Address( guid = "", - givenName = givenName, - additionalName = additionalName, - familyName = familyName, + name = name, organization = organization, streetAddress = streetAddress, addressLevel3 = addressLevel3, diff --git a/app/src/test/java/org/mozilla/fenix/settings/address/interactor/DefaultAddressEditorInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/settings/address/interactor/DefaultAddressEditorInteractorTest.kt index ff5ba1239..515d97d3c 100644 --- a/app/src/test/java/org/mozilla/fenix/settings/address/interactor/DefaultAddressEditorInteractorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/settings/address/interactor/DefaultAddressEditorInteractorTest.kt @@ -31,9 +31,7 @@ class DefaultAddressEditorInteractorTest { @Test fun `WHEN save button is clicked THEN forward to controller handler`() { val addressFields = UpdatableAddressFields( - givenName = "John", - additionalName = "", - familyName = "Smith", + name = "John Smith", organization = "Mozilla", streetAddress = "123 Sesame Street", addressLevel3 = "", From 314f2deed896fe28bb17cbcbb7ed63c15fa6fddd Mon Sep 17 00:00:00 2001 From: Jackie Johnson <107960801+jjSDET@users.noreply.github.com> Date: Mon, 26 Feb 2024 15:12:25 -0600 Subject: [PATCH 054/238] Bug 1882170 - Disable Auto-Fill View Tests After Address Fields Were Consolidated --- .../java/org/mozilla/fenix/ui/AddressAutofillTest.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/AddressAutofillTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/AddressAutofillTest.kt index 61a3c0b7f..7cfcdafd6 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/AddressAutofillTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/AddressAutofillTest.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.ui +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.mozilla.fenix.customannotations.SmokeTest @@ -116,6 +117,7 @@ class AddressAutofillTest : TestSetup() { } // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1836840 + @Ignore("Disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1882170") @Test fun verifyAddAddressViewTest() { homeScreen { @@ -130,6 +132,7 @@ class AddressAutofillTest : TestSetup() { } // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1836841 + @Ignore("Disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1882170") @Test fun verifyEditAddressViewTest() { autofillScreen { From 99b3abbcc9be7ee3203c13afd07d303160093a0a Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 27 Feb 2024 00:03:55 +0000 Subject: [PATCH 055/238] Import translations from android-l10n --- app/src/main/res/values-es/strings.xml | 13 ++ app/src/main/res/values-nb-rNO/strings.xml | 203 ++++++++++++++++++++- 2 files changed, 215 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 5f00a5e88..e9888675a 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -2106,8 +2106,12 @@ Introduce una contraseña Se requiere nombre de usuario + + Introduce un nombre de usuario Se requiere nombre de servidor + + Introduce una dirección web Búsqueda por voz @@ -2203,6 +2207,9 @@ Buscar con %s + + + Cambia tu navegador predeterminado Configura enlaces de sitios web, correos electrónicos y mensajes para que se abran automáticamente en Firefox. @@ -2460,6 +2467,8 @@ Traducción en curso + + Selecciona un idioma Ha surgido un problema al traducir. Por favor inténtalo de nuevo. @@ -2480,6 +2489,10 @@ No traducir nunca %1$s No traducir nunca este sitio + + Anula todas las demás configuraciones + + Anula las ofertas de traducción Ajustes de traducción diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index aef3dd0fc..bd4b73655 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -1769,6 +1769,8 @@ Sikre dine lagrede passord Konfigurer en PIN-kode, et passord eller et låsemønster for å forhindre at andre mennesker får tilgang de lagrede innloggingene og passordene dine, hvis de har adgang til din enhet. + + Konfigurer en PIN-kode, et passord eller et låsemønster for å beskytte dine lagrede passord om noen andre skulle få tak i enheten din. Senere @@ -1790,6 +1792,9 @@ Sorter innlogginger-meny + + Sorter passord-menyen + Autofyll @@ -1797,10 +1802,16 @@ Adresser Betalingskort + + Betalingsmåter Lagre og fyll ut kort automatisk + + Lagre og fyll inn betalingsmåter Data er kryptert + + %s krypterer alle betalingsmåter du lagrer Synkroniser kort på tvers av enheter @@ -1808,17 +1819,26 @@ Legg til betalingskort + + Legg til kort Behandle lagrede kort + + Behandle kort Legg til adresse Behandle adresser Lagre og autoutfyll adresser + + Lagre og fyll ut adresser Inkluderer informasjon som telefonnummer, e-post og leveringsadresser + + Inkluderer telefonnumre og e-postadresser + Legg til kort @@ -1839,6 +1859,8 @@ Slett kort Er du sikker på at du vil slette dette bankkortet? + + Slett kort? Slett @@ -1853,14 +1875,22 @@ Oppgi et gyldig betalingskortnummer + + Skriv inn et gyldig kortnummer Fyll ut dette feltet + + Legg til et navn Lås opp for å se dine lagrede betalingskort Sikre dine betalingskort + + Sikre dine lagrede betalingsmåter Konfigurer en PIN-kode, et passord eller et låsemønster for å beskytte de lagrede betalingskortene dine om noen andre skulle få tak i enheten din. + + Konfigurer en PIN-kode, et passord eller et låsemønster for å beskytte dine lagrede betalingsmåter om noen andre skulle få tak i enheten din. Konfigurer nå @@ -1871,6 +1901,8 @@ Lås opp for å bruke lagret betalingskortinformasjon + + Lås opp for å bruke lagrede betalingsmåter Legg til adresse @@ -1908,6 +1940,8 @@ Er du sikker på at du vil slette denne adressen? + + Slette denne adressen? Slett @@ -2008,30 +2042,52 @@ Rediger Er du sikker på at du ønsker å slette denne innloggingen? + + Er du sikker på at du ønsker å slette dette passordet? Slett Avbryt Innloggingsalternativer + + Passordalternativer Det redigerbare tekstfeltet for innloggingens nettadresse. + + Det redigerbare tekstfeltet for nettstedsadressen. Det redigerbare tekstfeltet for innloggingens brukernavn. + + Det redigerbare tekstfeltet for brukernavnet. Det redigerbare tekstfeltet for innloggingens passord. + + Det redigerbare tekstfeltet for passordet. Lagre endringer for innlogging. + + Lagre endringer. Rediger + + Rediger passord Legg til ny innlogging + + Legg til passord Passord kreves + + Skriv inn et passord Brukernavn påkrevd + + Skriv inn et brukernavn Servernavn påkrevd + + Skriv inn en nettadresse Stemmesøk @@ -2126,6 +2182,9 @@ %s-søk + + + Endre standard nettleser Angi at lenker fra nettsteder, e-postmeldinger og meldinger skal åpnes automatisk i Firefox. @@ -2352,8 +2411,86 @@ %s, Overskrift + + Lenker + + Lenker tilgjengelig + + + + Oversett denne siden? + + Prøv private oversettelser i %1$s + + Av hensyn til personvernet ditt forlater aldri oversettelser enheten din. Nye språk og forbedringer kommer snart! %1$s + + Les mer + + Oversett fra + + Oversett til + + Ikke nå + + Ferdig + + Oversett + + Prøv igjen + + Oversetter + + Oversettelse pågår + + Velg et språk + + Det oppstod et problem med å oversette. Prøv på nytt. + + Kunne ikke laste inn språk. Sjekk Internett-tilkoblingen din og prøv igjen. + + Beklager, vi støtter ikke %1$s ennå. + + Les mer + + + + Oversettelsesinnstillinger + + Tilby alltid å oversette + + Oversett alltid %1$s + + Oversett aldri %1$s + + Oversett aldri dette nettstedet + + Overstyrer alle andre innstillinger + + Overstyrer tilbud om å oversette + + Oversettelsesinnstillinger + + Om oversettelser i %1$s + + + + Oversettelser + + Tilby å oversette når det er mulig + + + Last alltid ned språk i datasparingsmodus + + Innstillinger for oversetting + + Automatisk oversettelse + + Oversett aldri disse nettstedene + + Last ned språk + Automatisk oversettelse @@ -2402,6 +2539,10 @@ Tilgjengelige språk nødvendig + + %1$s (%2$s) Last ned språk @@ -2415,4 +2556,64 @@ Valgt - + + Slette %1$s (%2$s)? + + Hvis du sletter dette språket, vil %1$s laste ned deler av språk til hurtigbufferen din mens du oversetter. + + Slette alle språk (%1$s)? + + Hvis du sletter alle språk, vil %1$s laste ned deler av språk til hurtigbufferen din mens du oversetter. + + Slett + + Avbryt + + + Laste ned mens du er i datasparingsmodus (%1$s)? + + Vi laster ned delvise språk til hurtigbufferen din for å holde oversettelser private. + + Last alltid ned i datasparingsmodus + + Last ned + + Last ned og oversett + + Avbryt + + + + Feilsøkingsverktøy + + Naviger tilbake + + Faneverktøy + + Antall faner + + Aktiv + + Inaktiv + + Privat + + Totalt + + Verktøy for å lage faner + + Antall faner som skal opprettes + + Legg til aktive faner + + Legg til inaktive faner + + Legg til private faner + From 398071e988f93c3c4a0a467f2465e86f14f47318 Mon Sep 17 00:00:00 2001 From: Roger Yang Date: Mon, 26 Feb 2024 12:35:48 -0500 Subject: [PATCH 056/238] Bug 1880284 - Show keyboard after voice input --- .../main/java/org/mozilla/fenix/search/SearchDialogFragment.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt index c41d5ea4c..80f0e310a 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt @@ -69,6 +69,7 @@ import mozilla.components.support.ktx.android.content.res.getSpanned import mozilla.components.support.ktx.android.net.isHttpOrHttps import mozilla.components.support.ktx.android.view.findViewInHierarchy import mozilla.components.support.ktx.android.view.hideKeyboard +import mozilla.components.support.ktx.android.view.showKeyboard import mozilla.components.support.ktx.kotlin.toNormalizedUrl import mozilla.components.ui.autocomplete.InlineAutocompleteEditText import mozilla.components.ui.widgets.withCenterAlignedButtons @@ -166,6 +167,8 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { val updatedUrl = toolbarView.view.edit.updateUrl(url = it, shouldHighlight = false, shouldAppend = true) interactor.onTextChanged(updatedUrl) toolbarView.view.edit.focus() + // When using voice input, show keyboard after for user convenience. + toolbarView.view.showKeyboard() } } } From 8a32c516df092dfd829e26f3fa8ba86c081b0cc3 Mon Sep 17 00:00:00 2001 From: rahulsainani Date: Fri, 19 Jan 2024 14:01:37 +0100 Subject: [PATCH 057/238] Bug 1875465 - Part 1: Set toolbar position if tablet --- .../fenix/browser/BaseBrowserFragment.kt | 18 +++++++++--------- .../org/mozilla/fenix/browser/TabPreview.kt | 5 +++-- .../java/org/mozilla/fenix/utils/Settings.kt | 11 ++++++++++- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt index ae27629bb..9ba7df57d 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -134,6 +134,7 @@ import org.mozilla.fenix.components.toolbar.BrowserToolbarView import org.mozilla.fenix.components.toolbar.DefaultBrowserToolbarController import org.mozilla.fenix.components.toolbar.DefaultBrowserToolbarMenuController import org.mozilla.fenix.components.toolbar.ToolbarIntegration +import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.components.toolbar.interactor.BrowserToolbarInteractor import org.mozilla.fenix.components.toolbar.interactor.DefaultBrowserToolbarInteractor import org.mozilla.fenix.crashes.CrashContentIntegration @@ -463,7 +464,7 @@ abstract class BaseBrowserFragment : toolbarInfo = FindInPageIntegration.ToolbarInfo( browserToolbarView.view, !context.settings().shouldUseFixedTopToolbar && context.settings().isDynamicToolbarEnabled, - !context.settings().shouldUseBottomToolbar, + context.settings().toolbarPosition == ToolbarPosition.TOP, ), ), owner = this, @@ -820,7 +821,7 @@ abstract class BaseBrowserFragment : browserStore = requireComponents.core.store, appStore = requireComponents.appStore, toolbar = browserToolbarView.view, - isToolbarPlacedAtTop = !context.settings().shouldUseBottomToolbar, + isToolbarPlacedAtTop = context.settings().toolbarPosition == ToolbarPosition.TOP, crashReporterView = binding.crashReporterView, components = requireComponents, settings = context.settings(), @@ -1155,10 +1156,9 @@ abstract class BaseBrowserFragment : if (!context.settings().shouldUseFixedTopToolbar && context.settings().isDynamicToolbarEnabled) { getEngineView().setDynamicToolbarMaxHeight(toolbarHeight) - val toolbarPosition = if (context.settings().shouldUseBottomToolbar) { - MozacToolbarPosition.BOTTOM - } else { - MozacToolbarPosition.TOP + val toolbarPosition = when (context.settings().toolbarPosition) { + ToolbarPosition.BOTTOM -> MozacToolbarPosition.BOTTOM + ToolbarPosition.TOP -> MozacToolbarPosition.TOP } (getSwipeRefreshLayout().layoutParams as CoordinatorLayout.LayoutParams).behavior = EngineViewClippingBehavior( @@ -1175,10 +1175,10 @@ abstract class BaseBrowserFragment : // Effectively place the engineView on top/below of the toolbar if that is not dynamic. val swipeRefreshParams = getSwipeRefreshLayout().layoutParams as CoordinatorLayout.LayoutParams - if (context.settings().shouldUseBottomToolbar) { - swipeRefreshParams.bottomMargin = toolbarHeight - } else { + if (context.settings().toolbarPosition == ToolbarPosition.TOP) { swipeRefreshParams.topMargin = toolbarHeight + } else { + swipeRefreshParams.bottomMargin = toolbarHeight } } } diff --git a/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt b/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt index facb796e5..d04089dd6 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt @@ -18,6 +18,7 @@ import mozilla.components.browser.state.selector.selectedTab import mozilla.components.browser.thumbnails.loader.ThumbnailLoader import mozilla.components.concept.base.images.ImageLoadRequest import org.mozilla.fenix.R +import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.databinding.TabPreviewBinding import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.settings @@ -34,7 +35,7 @@ class TabPreview @JvmOverloads constructor( private val thumbnailLoader = ThumbnailLoader(context.components.core.thumbnailStorage) init { - if (!context.settings().shouldUseBottomToolbar) { + if (context.settings().toolbarPosition == ToolbarPosition.TOP) { binding.fakeToolbar.updateLayoutParams { gravity = Gravity.TOP } @@ -59,7 +60,7 @@ class TabPreview @JvmOverloads constructor( binding.tabButton.setCount(count) } - binding.previewThumbnail.translationY = if (!context.settings().shouldUseBottomToolbar) { + binding.previewThumbnail.translationY = if (context.settings().toolbarPosition == ToolbarPosition.TOP) { binding.fakeToolbar.height.toFloat() } else { 0f diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index 1808eb3a3..957741fab 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -854,6 +854,9 @@ class Settings(private val appContext: Context) : PreferencesHolder { return touchExplorationIsEnabled || switchServiceIsEnabled } + private val isTablet: Boolean + get() = appContext.resources.getBoolean(R.bool.tablet) + var lastKnownMode: BrowsingMode = BrowsingMode.Normal get() { val lastKnownModeWasPrivate = preferences.getBoolean( @@ -922,7 +925,13 @@ class Settings(private val appContext: Context) : PreferencesHolder { ) val toolbarPosition: ToolbarPosition - get() = if (shouldUseBottomToolbar) ToolbarPosition.BOTTOM else ToolbarPosition.TOP + get() = if (isTablet) { + ToolbarPosition.TOP + } else if (shouldUseBottomToolbar) { + ToolbarPosition.BOTTOM + } else { + ToolbarPosition.TOP + } /** * Check each active accessibility service to see if it can perform gestures, if any can, From c91378ceb7a8096cf01a64861351f0fd7070e85a Mon Sep 17 00:00:00 2001 From: rahulsainani Date: Fri, 19 Jan 2024 14:03:06 +0100 Subject: [PATCH 058/238] Bug 1875465 - Part 2: Update layout and positioning logic --- .../org/mozilla/fenix/home/ToolbarView.kt | 30 ++++++++++++++----- .../fenix/search/toolbar/ToolbarView.kt | 8 +++++ app/src/main/res/layout/fragment_browser.xml | 10 ++++++- app/src/main/res/layout/fragment_home.xml | 8 +++++ app/src/main/res/values-sw600dp/dimens.xml | 8 +++++ app/src/main/res/values/dimens.xml | 1 + 6 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 app/src/main/res/values-sw600dp/dimens.xml diff --git a/app/src/main/java/org/mozilla/fenix/home/ToolbarView.kt b/app/src/main/java/org/mozilla/fenix/home/ToolbarView.kt index c37ae5e22..b36403040 100644 --- a/app/src/main/java/org/mozilla/fenix/home/ToolbarView.kt +++ b/app/src/main/java/org/mozilla/fenix/home/ToolbarView.kt @@ -66,16 +66,27 @@ class ToolbarView( gravity = Gravity.TOP } + val isTabletAndTabStripEnabled = context.resources.getBoolean(R.bool.tablet) ConstraintSet().apply { clone(binding.toolbarLayout) clear(binding.bottomBar.id, ConstraintSet.BOTTOM) clear(binding.bottomBarShadow.id, ConstraintSet.BOTTOM) - connect( - binding.bottomBar.id, - ConstraintSet.TOP, - ConstraintSet.PARENT_ID, - ConstraintSet.TOP, - ) + + if (isTabletAndTabStripEnabled) { + connect( + binding.bottomBar.id, + ConstraintSet.TOP, + binding.tabStripView.id, + ConstraintSet.BOTTOM, + ) + } else { + connect( + binding.bottomBar.id, + ConstraintSet.TOP, + ConstraintSet.PARENT_ID, + ConstraintSet.TOP, + ) + } connect( binding.bottomBarShadow.id, ConstraintSet.TOP, @@ -98,7 +109,12 @@ class ToolbarView( binding.homeAppBar.updateLayoutParams { topMargin = - context.resources.getDimensionPixelSize(R.dimen.home_fragment_top_toolbar_header_margin) + context.resources.getDimensionPixelSize(R.dimen.home_fragment_top_toolbar_header_margin) + + if (isTabletAndTabStripEnabled) { + context.resources.getDimensionPixelSize(R.dimen.tab_strip_height) + } else { + 0 + } } } diff --git a/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt b/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt index fc186e8da..5c362119f 100644 --- a/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt +++ b/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt @@ -4,9 +4,11 @@ package org.mozilla.fenix.search.toolbar +import android.view.ViewGroup import androidx.annotation.VisibleForTesting import androidx.appcompat.content.res.AppCompatResources import androidx.core.content.ContextCompat +import androidx.core.view.updateMargins import mozilla.components.browser.toolbar.BrowserToolbar import mozilla.components.concept.toolbar.Toolbar import mozilla.components.feature.toolbar.ToolbarAutocompleteFeature @@ -123,6 +125,12 @@ class ToolbarView( } }, ) + + if (settings.isTabletAndTabStripEnabled) { + (layoutParams as ViewGroup.MarginLayoutParams).updateMargins( + top = context.resources.getDimensionPixelSize(R.dimen.tab_strip_height), + ) + } } } diff --git a/app/src/main/res/layout/fragment_browser.xml b/app/src/main/res/layout/fragment_browser.xml index b61d82c1b..34a879205 100644 --- a/app/src/main/res/layout/fragment_browser.xml +++ b/app/src/main/res/layout/fragment_browser.xml @@ -13,6 +13,14 @@ android:id="@+id/browserWindow" android:layout_width="match_parent" android:layout_height="match_parent"> + + + + + + + + 48dp + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index c48be70a5..8e990dae9 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -70,6 +70,7 @@ 56dp + 0dp 48dp From 54d7785896447e779adcc0cd57930c30e683e25b Mon Sep 17 00:00:00 2001 From: rahulsainani Date: Fri, 19 Jan 2024 14:03:33 +0100 Subject: [PATCH 059/238] Bug 1875465 - Part 3: Add tab strip ui for tablets --- .../mozilla/fenix/browser/BrowserFragment.kt | 37 ++ .../fenix/browser/tabstrip/TabStrip.kt | 414 ++++++++++++++++++ .../fenix/browser/tabstrip/TabStripCard.kt | 71 +++ .../fenix/browser/tabstrip/TabStripState.kt | 63 +++ .../org/mozilla/fenix/home/HomeFragment.kt | 29 ++ .../browser/tabstrip/TabStripStateTest.kt | 244 +++++++++++ 6 files changed, 858 insertions(+) create mode 100644 app/src/main/java/org/mozilla/fenix/browser/tabstrip/TabStrip.kt create mode 100644 app/src/main/java/org/mozilla/fenix/browser/tabstrip/TabStripCard.kt create mode 100644 app/src/main/java/org/mozilla/fenix/browser/tabstrip/TabStripState.kt create mode 100644 app/src/test/java/org/mozilla/fenix/browser/tabstrip/TabStripStateTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt index f4faa1c12..15872282a 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt @@ -10,7 +10,9 @@ import android.view.View import android.view.ViewGroup import androidx.annotation.VisibleForTesting import androidx.appcompat.content.res.AppCompatResources +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import androidx.fragment.app.setFragmentResultListener import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope @@ -37,7 +39,9 @@ import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import org.mozilla.fenix.GleanMetrics.ReaderMode import org.mozilla.fenix.GleanMetrics.Shopping import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.NavGraphDirections import org.mozilla.fenix.R +import org.mozilla.fenix.browser.tabstrip.TabStrip import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.TabCollectionStorage import org.mozilla.fenix.components.appstate.AppAction @@ -48,11 +52,13 @@ import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.runIfFragmentIsAttached import org.mozilla.fenix.ext.settings +import org.mozilla.fenix.home.HomeFragment import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.settings.quicksettings.protections.cookiebanners.getCookieBannerUIMode import org.mozilla.fenix.shopping.DefaultShoppingExperienceFeature import org.mozilla.fenix.shopping.ReviewQualityCheckFeature import org.mozilla.fenix.shortcut.PwaOnboardingObserver +import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.translations.TranslationsDialogFragment.Companion.SESSION_ID import org.mozilla.fenix.translations.TranslationsDialogFragment.Companion.TRANSLATION_IN_PROGRESS @@ -87,6 +93,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { val context = requireContext() val components = context.components + initTabStrip() if (context.settings().isSwipeToolbarToSwitchTabsEnabled) { binding.gestureLayout.addGestureListener( @@ -238,6 +245,36 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { } } + private fun initTabStrip() { + if (!resources.getBoolean(R.bool.tablet)) { + return + } + + binding.tabStripView.isVisible = true + binding.tabStripView.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + FirefoxTheme { + TabStrip( + onAddTabClick = { + findNavController().navigate( + NavGraphDirections.actionGlobalHome( + focusOnAddressBar = true, + ), + ) + }, + onLastTabClose = { + findNavController().navigate( + BrowserFragmentDirections.actionGlobalHome(), + ) + }, + onSelectedTabClick = {}, + ) + } + } + } + } + private fun initTranslationsAction(context: Context, view: View) { if (!context.settings().enableTranslations) { return diff --git a/app/src/main/java/org/mozilla/fenix/browser/tabstrip/TabStrip.kt b/app/src/main/java/org/mozilla/fenix/browser/tabstrip/TabStrip.kt new file mode 100644 index 000000000..b7d808fe8 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/browser/tabstrip/TabStrip.kt @@ -0,0 +1,414 @@ +/* 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/. */ + +package org.mozilla.fenix.browser.tabstrip + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyListItemInfo +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.systemGestureExclusion +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.coerceIn +import androidx.compose.ui.unit.dp +import mozilla.components.browser.state.action.TabListAction +import mozilla.components.browser.state.state.createTab +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.feature.tabs.TabsUseCases +import mozilla.components.lib.state.ext.observeAsState +import org.mozilla.fenix.R +import org.mozilla.fenix.components.AppStore +import org.mozilla.fenix.components.components +import org.mozilla.fenix.compose.Favicon +import org.mozilla.fenix.theme.FirefoxTheme +import org.mozilla.fenix.theme.Theme + +private val minTabStripItemWidth = 160.dp +private val maxTabStripItemWidth = 280.dp +private val tabStripIconSize = 24.dp +private val spaceBetweenTabs = 4.dp +private val tabStripStartPadding = 8.dp +private val addTabIconSize = 20.dp + +/** + * Top level composable for the tabs strip. + * + * @param onHome Whether or not the tabs strip is in the home screen. + * @param browserStore The [BrowserStore] instance used to observe tabs state. + * @param appStore The [AppStore] instance used to observe browsing mode. + * @param tabsUseCases The [TabsUseCases] instance to perform tab actions. + * @param onAddTabClick Invoked when the add tab button is clicked. + * @param onLastTabClose Invoked when the last remaining open tab is closed. + * @param onSelectedTabClick Invoked when a tab is selected. + */ +@Composable +fun TabStrip( + onHome: Boolean = false, + browserStore: BrowserStore = components.core.store, + appStore: AppStore = components.appStore, + tabsUseCases: TabsUseCases = components.useCases.tabsUseCases, + onAddTabClick: () -> Unit, + onLastTabClose: () -> Unit, + onSelectedTabClick: () -> Unit, +) { + val isPrivateMode by appStore.observeAsState(false) { it.mode.isPrivate } + val state by browserStore.observeAsState(TabStripState.initial) { + it.toTabStripState(isSelectDisabled = onHome, isPrivateMode = isPrivateMode) + } + + TabStripContent( + state = state, + onAddTabClick = onAddTabClick, + onCloseTabClick = { + if (state.tabs.size == 1) { + onLastTabClose() + } + tabsUseCases.removeTab(it) + }, + onSelectedTabClick = { + tabsUseCases.selectTab(it) + onSelectedTabClick() + }, + ) +} + +@Composable +private fun TabStripContent( + state: TabStripState, + onAddTabClick: () -> Unit, + onCloseTabClick: (id: String) -> Unit, + onSelectedTabClick: (id: String) -> Unit, +) { + Row( + modifier = Modifier + .fillMaxSize() + .background(FirefoxTheme.colors.layer1) + .systemGestureExclusion(), + verticalAlignment = Alignment.CenterVertically, + ) { + TabsList( + state = state, + modifier = Modifier.weight(1f, fill = false), + onCloseTabClick = onCloseTabClick, + onSelectedTabClick = onSelectedTabClick, + ) + + IconButton(onClick = onAddTabClick) { + Icon( + painter = painterResource(R.drawable.mozac_ic_plus_24), + modifier = Modifier.size(addTabIconSize), + tint = FirefoxTheme.colors.iconPrimary, + contentDescription = stringResource(R.string.add_tab), + ) + } + } +} + +@Composable +@OptIn(ExperimentalFoundationApi::class) +private fun TabsList( + state: TabStripState, + modifier: Modifier = Modifier, + onCloseTabClick: (id: String) -> Unit, + onSelectedTabClick: (id: String) -> Unit, +) { + BoxWithConstraints(modifier = modifier) { + val listState = rememberLazyListState() + // Calculate the width of each tab item based on available width and the number of tabs and + // taking into account the space between tabs. + val availableWidth = maxWidth - tabStripStartPadding + val tabWidth = (availableWidth / state.tabs.size) - spaceBetweenTabs + + LazyRow( + modifier = Modifier, + state = listState, + contentPadding = PaddingValues(start = tabStripStartPadding), + ) { + items( + items = state.tabs, + key = { it.id }, + ) { itemState -> + TabItem( + state = itemState, + onCloseTabClick = onCloseTabClick, + onSelectedTabClick = onSelectedTabClick, + modifier = Modifier + .padding(end = spaceBetweenTabs) + .animateItemPlacement() + .width( + tabWidth.coerceIn( + minimumValue = minTabStripItemWidth, + maximumValue = maxTabStripItemWidth, + ), + ), + ) + } + } + + if (state.tabs.isNotEmpty()) { + // When a new tab is added, scroll to the end of the list. This is done here instead of + // in onCloseTabClick so this acts on state change which can occur from any other + // place e.g. tabs tray. + LaunchedEffect(state.tabs.last().id) { + listState.scrollToItem(state.tabs.size) + } + + // When a tab is selected, scroll to the selected tab. This is done here instead of + // in onSelectedTabClick so this acts on state change which can occur from any other + // place e.g. tabs tray. + val selectedTab = state.tabs.firstOrNull { it.isSelected } + LaunchedEffect(selectedTab?.id) { + if (selectedTab != null) { + val selectedItemInfo = + listState.layoutInfo.visibleItemsInfo.firstOrNull { it.key == selectedTab.id } + + if (listState.isItemPartiallyVisible(selectedItemInfo) || selectedItemInfo == null) { + listState.animateScrollToItem(state.tabs.indexOf(selectedTab)) + } + } + } + } + } +} + +private fun LazyListState.isItemPartiallyVisible(itemInfo: LazyListItemInfo?) = + itemInfo != null && + (itemInfo.offset + itemInfo.size > layoutInfo.viewportEndOffset || itemInfo.offset < 0) + +@Composable +private fun TabItem( + state: TabStripItem, + modifier: Modifier = Modifier, + onCloseTabClick: (id: String) -> Unit, + onSelectedTabClick: (id: String) -> Unit, +) { + TabStripCard( + modifier = modifier.fillMaxSize(), + backgroundColor = + if (state.isPrivate) { + if (state.isSelected) { + FirefoxTheme.colors.layer3 + } else { + FirefoxTheme.colors.layer2 + } + } else { + if (state.isSelected) { + FirefoxTheme.colors.layer2 + } else { + FirefoxTheme.colors.layer3 + } + }, + elevation = if (state.isSelected) { + selectedTabStripCardElevation + } else { + defaultTabStripCardElevation + }, + ) { + Row( + modifier = Modifier + .fillMaxSize() + .clickable { onSelectedTabClick(state.id) }, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Row( + modifier = Modifier.weight(1f, fill = false), + verticalAlignment = Alignment.CenterVertically, + ) { + Spacer(modifier = Modifier.size(8.dp)) + + TabStripIcon(state.url) + + Spacer(modifier = Modifier.size(8.dp)) + + Text( + text = state.title, + color = FirefoxTheme.colors.textPrimary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = FirefoxTheme.typography.subtitle2, + ) + } + + IconButton(onClick = { onCloseTabClick(state.id) }) { + Icon( + painter = painterResource(R.drawable.mozac_ic_cross_20), + tint = FirefoxTheme.colors.iconPrimary, + contentDescription = stringResource(R.string.close_tab), + ) + } + } + } +} + +@Composable +private fun TabStripIcon(url: String) { + Box( + modifier = Modifier + .size(tabStripIconSize) + .clip(CircleShape), + contentAlignment = Alignment.Center, + ) { + Favicon( + url = url, + size = tabStripIconSize, + ) + } +} + +private class TabUIStateParameterProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + TabStripState( + listOf( + TabStripItem( + id = "1", + title = "Tab 1", + url = "https://www.mozilla.org", + isPrivate = false, + isSelected = false, + ), + TabStripItem( + id = "2", + title = "Tab 2 with a very long title that should be truncated", + url = "https://www.mozilla.org", + isPrivate = false, + isSelected = false, + ), + TabStripItem( + id = "3", + title = "Selected tab", + url = "https://www.mozilla.org", + isPrivate = false, + isSelected = true, + ), + TabStripItem( + id = "p1", + title = "Private tab 1", + url = "https://www.mozilla.org", + isPrivate = true, + isSelected = false, + ), + TabStripItem( + id = "p2", + title = "Private selected tab", + url = "https://www.mozilla.org", + isPrivate = true, + isSelected = true, + ), + ), + ), + ) +} + +@Preview(device = Devices.TABLET) +@Composable +private fun TabStripPreview( + @PreviewParameter(TabUIStateParameterProvider::class) tabStripState: TabStripState, +) { + FirefoxTheme { + TabStripContentPreview(tabStripState.tabs.filter { !it.isPrivate }) + } +} + +@Preview(device = Devices.TABLET) +@Composable +private fun TabStripPreviewDarkMode( + @PreviewParameter(TabUIStateParameterProvider::class) tabStripState: TabStripState, +) { + FirefoxTheme(theme = Theme.Dark) { + TabStripContentPreview(tabStripState.tabs.filter { !it.isPrivate }) + } +} + +@Preview(device = Devices.TABLET) +@Composable +private fun TabStripPreviewPrivateMode( + @PreviewParameter(TabUIStateParameterProvider::class) tabStripState: TabStripState, +) { + FirefoxTheme(theme = Theme.Private) { + TabStripContentPreview(tabStripState.tabs.filter { it.isPrivate }) + } +} + +@Composable +private fun TabStripContentPreview(tabs: List) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(dimensionResource(id = R.dimen.tab_strip_height)), + contentAlignment = Alignment.Center, + ) { + TabStripContent( + state = TabStripState( + tabs = tabs, + ), + onAddTabClick = {}, + onCloseTabClick = {}, + onSelectedTabClick = {}, + ) + } +} + +@Preview(device = Devices.TABLET) +@Composable +private fun TabStripPreview() { + val browserStore = BrowserStore() + + FirefoxTheme { + Box( + modifier = Modifier + .fillMaxWidth() + .height(dimensionResource(id = R.dimen.tab_strip_height)), + contentAlignment = Alignment.Center, + ) { + TabStrip( + appStore = AppStore(), + browserStore = browserStore, + tabsUseCases = TabsUseCases(browserStore), + onAddTabClick = { + val tab = createTab( + url = "www.example.com", + ) + browserStore.dispatch(TabListAction.AddTabAction(tab)) + }, + onLastTabClose = {}, + onSelectedTabClick = {}, + ) + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/browser/tabstrip/TabStripCard.kt b/app/src/main/java/org/mozilla/fenix/browser/tabstrip/TabStripCard.kt new file mode 100644 index 000000000..57f9c7b3a --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/browser/tabstrip/TabStripCard.kt @@ -0,0 +1,71 @@ +/* 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/. */ + +package org.mozilla.fenix.browser.tabstrip + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Card +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import org.mozilla.fenix.compose.annotation.LightDarkPreview +import org.mozilla.fenix.theme.FirefoxTheme + +private val cardShape = RoundedCornerShape(8.dp) +internal val defaultTabStripCardElevation = 0.dp +internal val selectedTabStripCardElevation = 1.dp + +/** + * Card composable used in Tab Strip items. + * + * @param modifier The modifier to be applied to the card. + * @param backgroundColor The background color of the card. + * @param elevation The elevation of the card. + * @param content The content of the card. + */ +@Composable +fun TabStripCard( + modifier: Modifier = Modifier, + backgroundColor: Color = FirefoxTheme.colors.layer3, + elevation: Dp = defaultTabStripCardElevation, + content: @Composable () -> Unit, +) { + Card( + shape = cardShape, + backgroundColor = backgroundColor, + elevation = elevation, + modifier = modifier, + content = content, + ) +} + +@LightDarkPreview +@Composable +private fun TabStripCardPreview() { + FirefoxTheme { + TabStripCard { + Box( + modifier = Modifier + .height(56.dp) + .width(200.dp) + .padding(8.dp), + contentAlignment = Alignment.Center, + ) { + Text( + text = "Tab Strip Card", + color = FirefoxTheme.colors.textPrimary, + style = FirefoxTheme.typography.subtitle1, + ) + } + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/browser/tabstrip/TabStripState.kt b/app/src/main/java/org/mozilla/fenix/browser/tabstrip/TabStripState.kt new file mode 100644 index 000000000..5dacf2b4a --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/browser/tabstrip/TabStripState.kt @@ -0,0 +1,63 @@ +/* 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/. */ + +package org.mozilla.fenix.browser.tabstrip + +import mozilla.components.browser.state.selector.getNormalOrPrivateTabs +import mozilla.components.browser.state.state.BrowserState + +/** + * The ui state of the tabs strip. + * + * @property tabs The list of [TabStripItem]. + */ +data class TabStripState( + val tabs: List, +) { + companion object { + val initial = TabStripState(tabs = emptyList()) + } +} + +/** + * The ui state of a tab. + * + * @property id The id of the tab. + * @property title The title of the tab. + * @property url The url of the tab. + * @property isPrivate Whether or not the tab is private. + * @property isSelected Whether or not the tab is selected. + */ +data class TabStripItem( + val id: String, + val title: String, + val url: String, + val isPrivate: Boolean, + val isSelected: Boolean, +) + +/** + * Converts [BrowserState] to [TabStripState] that contains the information needed to render the + * tabs strip. + * + * @param isSelectDisabled When true, the tabs will show as selected. + * @param isPrivateMode Whether or not the browser is in private mode. + */ +internal fun BrowserState.toTabStripState( + isSelectDisabled: Boolean, + isPrivateMode: Boolean, +): TabStripState { + return TabStripState( + tabs = getNormalOrPrivateTabs(isPrivateMode) + .map { + TabStripItem( + id = it.id, + title = it.content.title.ifBlank { it.content.url }, + url = it.content.url, + isPrivate = it.content.private, + isSelected = !isSelectDisabled && it.id == selectedTabId, + ) + }, + ) +} diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index fccae3ff9..5f61e7cd7 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -23,6 +23,7 @@ import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTag import androidx.compose.ui.semantics.testTagsAsResourceId @@ -77,6 +78,7 @@ import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.service.glean.private.NoExtras import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import mozilla.components.ui.colors.PhotonColors +import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.GleanMetrics.HomeScreen import org.mozilla.fenix.GleanMetrics.Homepage import org.mozilla.fenix.GleanMetrics.PrivateBrowsingShortcutCfr @@ -84,6 +86,7 @@ import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.addons.showSnackBar import org.mozilla.fenix.browser.browsingmode.BrowsingMode +import org.mozilla.fenix.browser.tabstrip.TabStrip import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.PrivateShortcutCreateManager import org.mozilla.fenix.components.TabCollectionStorage @@ -576,6 +579,7 @@ class HomeFragment : Fragment() { ) toolbarView?.build() + initTabStrip() PrivateBrowsingButtonView(binding.privateBrowsingButton, browsingModeManager) { newMode -> sessionControlInteractor.onPrivateModeButtonClicked(newMode) @@ -653,6 +657,31 @@ class HomeFragment : Fragment() { ) } + private fun initTabStrip() { + if (!resources.getBoolean(R.bool.tablet)) { + return + } + + binding.tabStripView.isVisible = true + binding.tabStripView.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + FirefoxTheme { + TabStrip( + onHome = true, + onAddTabClick = { + sessionControlInteractor.onNavigateSearch() + }, + onSelectedTabClick = { + (requireActivity() as HomeActivity).openToBrowser(BrowserDirection.FromHome) + }, + onLastTabClose = {}, + ) + } + } + } + } + /** * Method used to listen to search engine name changes and trigger a top sites update accordingly */ diff --git a/app/src/test/java/org/mozilla/fenix/browser/tabstrip/TabStripStateTest.kt b/app/src/test/java/org/mozilla/fenix/browser/tabstrip/TabStripStateTest.kt new file mode 100644 index 000000000..e6c98b0ef --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/browser/tabstrip/TabStripStateTest.kt @@ -0,0 +1,244 @@ +package org.mozilla.fenix.browser.tabstrip + +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.createTab +import org.junit.Assert.assertEquals +import org.junit.Test + +class TabStripStateTest { + + @Test + fun `WHEN browser state tabs is empty THEN tabs strip state tabs is empty`() { + val browserState = BrowserState(tabs = emptyList()) + val actual = browserState.toTabStripState(isSelectDisabled = false, isPrivateMode = false) + + val expected = TabStripState(tabs = emptyList()) + + assertEquals(expected, actual) + } + + @Test + fun `WHEN private mode is off THEN tabs strip state tabs should include only non private tabs`() { + val browserState = BrowserState( + tabs = listOf( + createTab( + url = "https://example.com", + title = "Example 1", + private = false, + id = "1", + ), + createTab( + url = "https://example2.com", + title = "Example 2", + private = true, + id = "2", + ), + createTab( + url = "https://example3.com", + title = "Example 3", + private = false, + id = "3", + ), + ), + ) + val actual = browserState.toTabStripState(isSelectDisabled = false, isPrivateMode = false) + + val expected = TabStripState( + tabs = listOf( + TabStripItem( + id = "1", + title = "Example 1", + url = "https://example.com", + isSelected = false, + isPrivate = false, + ), + TabStripItem( + id = "3", + title = "Example 3", + url = "https://example3.com", + isSelected = false, + isPrivate = false, + ), + ), + ) + + assertEquals(expected, actual) + } + + @Test + fun `WHEN private mode is on THEN tabs strip state tabs should include only private tabs`() { + val browserState = BrowserState( + tabs = listOf( + createTab( + url = "https://example.com", + title = "Example", + private = false, + id = "1", + ), + createTab( + url = "https://example2.com", + title = "Private Example", + private = true, + id = "2", + ), + createTab( + url = "https://example3.com", + title = "Example 3", + private = true, + id = "3", + ), + ), + ) + val actual = browserState.toTabStripState(isSelectDisabled = false, isPrivateMode = true) + + val expected = TabStripState( + tabs = listOf( + TabStripItem( + id = "2", + title = "Private Example", + url = "https://example2.com", + isSelected = false, + isPrivate = true, + ), + TabStripItem( + id = "3", + title = "Example 3", + url = "https://example3.com", + isSelected = false, + isPrivate = true, + ), + ), + ) + + assertEquals(expected, actual) + } + + @Test + fun `WHEN isSelectDisabled is false THEN tabs strip state tabs should have a selected tab`() { + val browserState = BrowserState( + tabs = listOf( + createTab( + url = "https://example.com", + title = "Example 1", + private = false, + id = "1", + ), + createTab( + url = "https://example2.com", + title = "Example 2", + private = false, + id = "2", + ), + ), + selectedTabId = "2", + ) + val actual = browserState.toTabStripState(isSelectDisabled = false, isPrivateMode = false) + + val expected = TabStripState( + tabs = listOf( + TabStripItem( + id = "1", + title = "Example 1", + url = "https://example.com", + isSelected = false, + isPrivate = false, + ), + TabStripItem( + id = "2", + title = "Example 2", + url = "https://example2.com", + isSelected = true, + isPrivate = false, + ), + ), + ) + + assertEquals(expected, actual) + } + + @Test + fun `WHEN isSelectDisabled is true THEN tabs strip state tabs should not have a selected tab`() { + val browserState = BrowserState( + tabs = listOf( + createTab( + url = "https://example.com", + title = "Example 1", + private = false, + id = "1", + ), + createTab( + url = "https://example2.com", + title = "Example 2", + private = false, + id = "2", + ), + ), + selectedTabId = "2", + ) + val actual = browserState.toTabStripState(isSelectDisabled = true, isPrivateMode = false) + + val expected = TabStripState( + tabs = listOf( + TabStripItem( + id = "1", + title = "Example 1", + url = "https://example.com", + isSelected = false, + isPrivate = false, + ), + TabStripItem( + id = "2", + title = "Example 2", + url = "https://example2.com", + isSelected = false, + isPrivate = false, + ), + ), + ) + + assertEquals(expected, actual) + } + + @Test + fun `WHEN a tab does not have a title THEN tabs strip should display the url`() { + val browserState = BrowserState( + tabs = listOf( + createTab( + url = "https://example.com", + title = "Example 1", + private = false, + id = "1", + ), + createTab( + url = "https://example2.com", + title = "", + private = false, + id = "2", + ), + ), + selectedTabId = "2", + ) + val actual = browserState.toTabStripState(isSelectDisabled = false, isPrivateMode = false) + + val expected = TabStripState( + tabs = listOf( + TabStripItem( + id = "1", + title = "Example 1", + url = "https://example.com", + isSelected = false, + isPrivate = false, + ), + TabStripItem( + id = "2", + title = "https://example2.com", + url = "https://example2.com", + isSelected = true, + isPrivate = false, + ), + ), + ) + + assertEquals(expected, actual) + } +} From 5ea4da3abeed74926a10a95342b31d2637dcacc1 Mon Sep 17 00:00:00 2001 From: rahulsainani Date: Sat, 17 Feb 2024 00:07:47 +0100 Subject: [PATCH 060/238] Bug 1875465 - Part 4: Add secret setting and remove toolbar customization --- .../mozilla/fenix/browser/BrowserFragment.kt | 11 +++++----- .../org/mozilla/fenix/home/HomeFragment.kt | 8 +++----- .../org/mozilla/fenix/home/ToolbarView.kt | 2 +- .../fenix/settings/CustomizationFragment.kt | 20 ++++++++++++++++--- .../fenix/settings/SecretSettingsFragment.kt | 10 ++++++++++ .../java/org/mozilla/fenix/utils/Settings.kt | 13 +++++++++++- app/src/main/res/values/preference_keys.xml | 3 +++ app/src/main/res/values/static_strings.xml | 2 ++ .../res/xml/customization_preferences.xml | 4 +++- .../res/xml/secret_settings_preferences.xml | 5 +++++ .../fenix/search/toolbar/ToolbarViewTest.kt | 4 ++++ 11 files changed, 65 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt index 15872282a..115a8a85e 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt @@ -93,9 +93,12 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { val context = requireContext() val components = context.components - initTabStrip() + val isTabletAndTabStripEnabled = context.settings().isTabletAndTabStripEnabled + if (isTabletAndTabStripEnabled) { + initTabStrip() + } - if (context.settings().isSwipeToolbarToSwitchTabsEnabled) { + if (!isTabletAndTabStripEnabled && context.settings().isSwipeToolbarToSwitchTabsEnabled) { binding.gestureLayout.addGestureListener( ToolbarGestureHandler( activity = requireActivity(), @@ -246,10 +249,6 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { } private fun initTabStrip() { - if (!resources.getBoolean(R.bool.tablet)) { - return - } - binding.tabStripView.isVisible = true binding.tabStripView.apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 5f61e7cd7..5d9270121 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -579,7 +579,9 @@ class HomeFragment : Fragment() { ) toolbarView?.build() - initTabStrip() + if (requireContext().settings().isTabletAndTabStripEnabled) { + initTabStrip() + } PrivateBrowsingButtonView(binding.privateBrowsingButton, browsingModeManager) { newMode -> sessionControlInteractor.onPrivateModeButtonClicked(newMode) @@ -658,10 +660,6 @@ class HomeFragment : Fragment() { } private fun initTabStrip() { - if (!resources.getBoolean(R.bool.tablet)) { - return - } - binding.tabStripView.isVisible = true binding.tabStripView.apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) diff --git a/app/src/main/java/org/mozilla/fenix/home/ToolbarView.kt b/app/src/main/java/org/mozilla/fenix/home/ToolbarView.kt index b36403040..3c556f7b4 100644 --- a/app/src/main/java/org/mozilla/fenix/home/ToolbarView.kt +++ b/app/src/main/java/org/mozilla/fenix/home/ToolbarView.kt @@ -66,7 +66,7 @@ class ToolbarView( gravity = Gravity.TOP } - val isTabletAndTabStripEnabled = context.resources.getBoolean(R.bool.tablet) + val isTabletAndTabStripEnabled = context.settings().isTabletAndTabStripEnabled ConstraintSet().apply { clone(binding.toolbarLayout) clear(binding.bottomBar.id, ConstraintSet.BOTTOM) diff --git a/app/src/main/java/org/mozilla/fenix/settings/CustomizationFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/CustomizationFragment.kt index fb1d26a6d..9f5d6b6d0 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/CustomizationFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/CustomizationFragment.kt @@ -10,7 +10,9 @@ import android.os.Build.VERSION.SDK_INT import android.os.Bundle import androidx.appcompat.app.AppCompatDelegate import androidx.preference.Preference +import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat +import androidx.preference.PreferenceScreen import androidx.preference.SwitchPreference import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.GleanMetrics.AppTheme @@ -51,8 +53,19 @@ class CustomizationFragment : PreferenceFragmentCompat() { bindLightTheme() bindAutoBatteryTheme() setupRadioGroups() - setupToolbarCategory() - setupGesturesCategory() + val tabletAndTabStripEnabled = requireContext().settings().isTabletAndTabStripEnabled + if (tabletAndTabStripEnabled) { + val preferenceScreen: PreferenceScreen = + requirePreference(R.string.pref_key_customization_preference_screen) + val toolbarPrefCategory: PreferenceCategory = + requirePreference(R.string.pref_key_customization_category_toolbar) + preferenceScreen.removePreference(toolbarPrefCategory) + } else { + setupToolbarCategory() + } + // if tab strip is enabled, swipe toolbar to switch tabs should not be enabled so the + // preference is not shown + setupGesturesCategory(isSwipeToolbarToSwitchTabsVisible = !tabletAndTabStripEnabled) } private fun setupRadioGroups() { @@ -140,7 +153,7 @@ class CustomizationFragment : PreferenceFragmentCompat() { addToRadioGroup(topPreference, bottomPreference) } - private fun setupGesturesCategory() { + private fun setupGesturesCategory(isSwipeToolbarToSwitchTabsVisible: Boolean) { requirePreference(R.string.pref_key_website_pull_to_refresh).apply { isVisible = FeatureFlags.pullToRefreshEnabled isChecked = context.settings().isPullToRefreshEnabledInBrowser @@ -152,6 +165,7 @@ class CustomizationFragment : PreferenceFragmentCompat() { } requirePreference(R.string.pref_key_swipe_toolbar_switch_tabs).apply { isChecked = context.settings().isSwipeToolbarToSwitchTabsEnabled + isVisible = isSwipeToolbarToSwitchTabsVisible onPreferenceChangeListener = SharedPreferenceUpdater() } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/SecretSettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SecretSettingsFragment.kt index 2d91285fe..7d7d4108c 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SecretSettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SecretSettingsFragment.kt @@ -122,6 +122,8 @@ class SecretSettingsFragment : PreferenceFragmentCompat() { } } + setupTabStripPreference() + // for performance reasons, this is only available in Nightly or Debug builds requirePreference(R.string.pref_key_custom_glean_server_url).apply { isVisible = Config.channel.isNightlyOrDebug && BuildConfig.GLEAN_CUSTOM_URL.isNullOrEmpty() @@ -138,6 +140,14 @@ class SecretSettingsFragment : PreferenceFragmentCompat() { } } + private fun setupTabStripPreference() { + requirePreference(R.string.pref_key_enable_tab_strip).apply { + isVisible = Config.channel.isNightlyOrDebug && context.resources.getBoolean(R.bool.tablet) + isChecked = context.settings().isTabStripEnabled + onPreferenceChangeListener = SharedPreferenceUpdater() + } + } + override fun onPreferenceTreeClick(preference: Preference): Boolean { when (preference.key) { getString(R.string.pref_key_custom_sponsored_stories_parameters) -> diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index 957741fab..b978ae339 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -857,6 +857,17 @@ class Settings(private val appContext: Context) : PreferencesHolder { private val isTablet: Boolean get() = appContext.resources.getBoolean(R.bool.tablet) + /** + * Indicates if the user has enabled the tab strip feature. + */ + val isTabStripEnabled by booleanPreference( + key = appContext.getPreferenceKey(R.string.pref_key_enable_tab_strip), + default = false, + ) + + val isTabletAndTabStripEnabled: Boolean + get() = isTablet && isTabStripEnabled + var lastKnownMode: BrowsingMode = BrowsingMode.Normal get() { val lastKnownModeWasPrivate = preferences.getBoolean( @@ -925,7 +936,7 @@ class Settings(private val appContext: Context) : PreferencesHolder { ) val toolbarPosition: ToolbarPosition - get() = if (isTablet) { + get() = if (isTabletAndTabStripEnabled) { ToolbarPosition.TOP } else if (shouldUseBottomToolbar) { ToolbarPosition.BOTTOM diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index 6c1615556..0db2c3d73 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -78,6 +78,7 @@ pref_key_suggest_strong_password_enabled pref_key_enable_debug_drawer pref_key_show_first_time_translation + pref_key_enable_tab_strip pref_key_telemetry @@ -155,12 +156,14 @@ pref_key_follow_device_theme + pref_key_customization_preference_screen pref_key_website_pull_to_refresh pref_key_dynamic_toolbar pref_key_swipe_toolbar_switch_tabs pref_key_swipe_toolbar_show_tabs pref_key_recent_tabs pref_key_recent_bookmarks + pref_key_customization_category_toolbar pref_key_https_only_settings diff --git a/app/src/main/res/values/static_strings.xml b/app/src/main/res/values/static_strings.xml index 6432c5bed..649bfb5ae 100644 --- a/app/src/main/res/values/static_strings.xml +++ b/app/src/main/res/values/static_strings.xml @@ -78,6 +78,8 @@ Enable Felt Privacy Enable Debug Drawer + + Enable Tab Strip Make inactive diff --git a/app/src/main/res/xml/customization_preferences.xml b/app/src/main/res/xml/customization_preferences.xml index cfd13f3aa..f1e1ea05e 100644 --- a/app/src/main/res/xml/customization_preferences.xml +++ b/app/src/main/res/xml/customization_preferences.xml @@ -3,7 +3,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/. --> + xmlns:app="http://schemas.android.com/apk/res-auto" + android:key="@string/pref_key_customization_preference_screen" > + Date: Mon, 19 Feb 2024 11:09:39 +0100 Subject: [PATCH 061/238] Bug 1875465 - Part 5: Hide tab strip when full screen --- .../java/org/mozilla/fenix/browser/BaseBrowserFragment.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt index 9ba7df57d..e2bc0dc37 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -1560,6 +1560,7 @@ abstract class BaseBrowserFragment : (view as? SwipeGestureLayout)?.isSwipeEnabled = false browserToolbarView.collapse() browserToolbarView.view.isVisible = false + binding.tabStripView.isVisible = false val browserEngine = binding.swipeRefresh.layoutParams as CoordinatorLayout.LayoutParams browserEngine.bottomMargin = 0 browserEngine.topMargin = 0 @@ -1585,6 +1586,9 @@ abstract class BaseBrowserFragment : initializeEngineView(toolbarHeight) browserToolbarView.expand() } + if (requireContext().settings().isTabletAndTabStripEnabled) { + binding.tabStripView.isVisible = true + } } binding.swipeRefresh.isEnabled = shouldPullToRefreshBeEnabled(inFullScreen) From edc60d77fd2a4d65845de5127c48f0663ff7f2d7 Mon Sep 17 00:00:00 2001 From: "oana.horvath" Date: Mon, 26 Feb 2024 13:02:03 +0200 Subject: [PATCH 062/238] Bug 1880087 - Create TestSetup helper: Test classes D-L --- .../java/org/mozilla/fenix/ui/DeepLinkTest.kt | 25 +---------- .../mozilla/fenix/ui/DownloadFileTypesTest.kt | 11 +---- .../java/org/mozilla/fenix/ui/DownloadTest.kt | 38 +--------------- .../ui/EnhancedTrackingProtectionTest.kt | 22 +--------- .../mozilla/fenix/ui/FirefoxSuggestTest.kt | 3 +- .../fenix/ui/GlobalPrivacyControlTest.kt | 20 ++------- .../java/org/mozilla/fenix/ui/HistoryTest.kt | 43 ++----------------- .../org/mozilla/fenix/ui/HomeScreenTest.kt | 27 +----------- .../java/org/mozilla/fenix/ui/LoginsTest.kt | 21 ++------- 9 files changed, 24 insertions(+), 186 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/DeepLinkTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/DeepLinkTest.kt index 258adb4cb..1a3cf129f 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/DeepLinkTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/DeepLinkTest.kt @@ -5,16 +5,12 @@ package org.mozilla.fenix.ui import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice -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.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.TestAssetHelper +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.DeepLinkRobot /** @@ -31,29 +27,12 @@ import org.mozilla.fenix.ui.robots.DeepLinkRobot **/ @Ignore("All tests perma-failing, see: https://github.com/mozilla-mobile/fenix/issues/13491") -class DeepLinkTest { - private lateinit var mDevice: UiDevice - private lateinit var mockWebServer: MockWebServer - +class DeepLinkTest : TestSetup() { private val robot = DeepLinkRobot() @get:Rule val activityIntentTestRule = HomeActivityIntentTestRule() - @Before - fun setUp() { - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - @Test fun openHomeScreen() { robot.openHomeScreen { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/DownloadFileTypesTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/DownloadFileTypesTest.kt index ff0a744ad..1ff999f42 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/DownloadFileTypesTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/DownloadFileTypesTest.kt @@ -5,14 +5,13 @@ package org.mozilla.fenix.ui import androidx.core.net.toUri -import org.junit.After import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized import org.mozilla.fenix.customannotations.SmokeTest -import org.mozilla.fenix.helpers.AppAndSystemHelper.clearDownloadsFolder import org.mozilla.fenix.helpers.HomeActivityIntentTestRule +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.downloadRobot /** @@ -22,7 +21,7 @@ import org.mozilla.fenix.ui.robots.downloadRobot * - Verifies downloading of varying file types and the appearance inside the Downloads listing. **/ @RunWith(Parameterized::class) -class DownloadFileTypesTest(fileName: String) { +class DownloadFileTypesTest(fileName: String) : TestSetup() { /* Remote test page managed by Mozilla Mobile QA team at https://github.com/mozilla-mobile/testapp */ private val downloadTestPage = "https://storage.googleapis.com/mobile_test_assets/test_app/downloads.html" private var downloadFile: String = fileName @@ -30,12 +29,6 @@ class DownloadFileTypesTest(fileName: String) { @get:Rule val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides() - @After - fun tearDown() { - // Check and clear the downloads folder - clearDownloadsFolder() - } - companion object { // Creating test data. The test will take each file name as a parameter and run it individually. @JvmStatic diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/DownloadTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/DownloadTest.kt index 987660636..23c8e2ac3 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/DownloadTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/DownloadTest.kt @@ -5,16 +5,11 @@ package org.mozilla.fenix.ui import androidx.core.net.toUri -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.customannotations.SmokeTest -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AppAndSystemHelper.assertExternalAppOpens -import org.mozilla.fenix.helpers.AppAndSystemHelper.clearDownloadsFolder import org.mozilla.fenix.helpers.AppAndSystemHelper.deleteDownloadedFileOnStorage import org.mozilla.fenix.helpers.AppAndSystemHelper.setNetworkEnabled import org.mozilla.fenix.helpers.Constants.PackageName.GOOGLE_APPS_PHOTOS @@ -25,6 +20,7 @@ import org.mozilla.fenix.helpers.TestAssetHelper import org.mozilla.fenix.helpers.TestHelper.clickSnackbarButton import org.mozilla.fenix.helpers.TestHelper.exitMenu import org.mozilla.fenix.helpers.TestHelper.mDevice +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.browserScreen import org.mozilla.fenix.ui.robots.clickPageObject import org.mozilla.fenix.ui.robots.downloadRobot @@ -40,9 +36,7 @@ import org.mozilla.fenix.ui.robots.notificationShade * - Verifies download notification and actions * - Verifies managing downloads inside the Downloads listing. **/ -class DownloadTest { - private lateinit var mockWebServer: MockWebServer - +class DownloadTest : TestSetup() { /* Remote test page managed by Mozilla Mobile QA team at https://github.com/mozilla-mobile/testapp */ private val downloadTestPage = "https://storage.googleapis.com/mobile_test_assets/test_app/downloads.html" private var downloadFile: String = "" @@ -50,34 +44,6 @@ class DownloadTest { @get:Rule val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides() - @Before - fun setUp() { - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - - // clear all existing notifications - notificationShade { - mDevice.openNotification() - clearNotifications() - } - } - - @After - fun tearDown() { - notificationShade { - cancelAllShownNotifications() - } - - mockWebServer.shutdown() - - setNetworkEnabled(enabled = true) - - // Check and clear the downloads folder - clearDownloadsFolder() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/243844 @Test fun verifyTheDownloadPromptsTest() { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/EnhancedTrackingProtectionTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/EnhancedTrackingProtectionTest.kt index eabf00b6e..5909ebc9c 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/EnhancedTrackingProtectionTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/EnhancedTrackingProtectionTest.kt @@ -6,14 +6,10 @@ package org.mozilla.fenix.ui import androidx.core.net.toUri import androidx.test.espresso.Espresso.pressBack -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.fenix.customannotations.SmokeTest import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.TestAssetHelper.getEnhancedTrackingProtectionAsset import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset @@ -22,6 +18,7 @@ import org.mozilla.fenix.helpers.TestHelper.exitMenu import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.restartApp import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.browserScreen import org.mozilla.fenix.ui.robots.enhancedTrackingProtection import org.mozilla.fenix.ui.robots.homeScreen @@ -40,9 +37,7 @@ import org.mozilla.fenix.ui.robots.navigationToolbar * - Verifying Enhanced Tracking Protection site exceptions */ -class EnhancedTrackingProtectionTest { - private lateinit var mockWebServer: MockWebServer - +class EnhancedTrackingProtectionTest : TestSetup() { @get:Rule val activityTestRule = HomeActivityIntentTestRule( isJumpBackInCFREnabled = false, @@ -50,19 +45,6 @@ class EnhancedTrackingProtectionTest { isWallpaperOnboardingEnabled = false, ) - @Before - fun setUp() { - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/416046 @Test fun testETPSettingsItemsAndSubMenus() { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/FirefoxSuggestTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/FirefoxSuggestTest.kt index ae384d063..151ee41ae 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/FirefoxSuggestTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/FirefoxSuggestTest.kt @@ -14,6 +14,7 @@ import org.mozilla.fenix.helpers.AppAndSystemHelper.runWithCondition import org.mozilla.fenix.helpers.DataGenerationHelper.getSponsoredFxSuggestPlaceHolder import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.helpers.TestHelper +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.navigationToolbar /** @@ -21,7 +22,7 @@ import org.mozilla.fenix.ui.robots.navigationToolbar * */ -class FirefoxSuggestTest { +class FirefoxSuggestTest : TestSetup() { @get:Rule val activityTestRule = AndroidComposeTestRule( diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/GlobalPrivacyControlTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/GlobalPrivacyControlTest.kt index 25a333b50..02e4cc224 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/GlobalPrivacyControlTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/GlobalPrivacyControlTest.kt @@ -4,15 +4,13 @@ package org.mozilla.fenix.ui -import okhttp3.mockwebserver.MockWebServer -import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.TestAssetHelper.TestAsset import org.mozilla.fenix.helpers.TestAssetHelper.getGPCTestAsset +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.navigationToolbar @@ -20,8 +18,7 @@ import org.mozilla.fenix.ui.robots.navigationToolbar * Tests for Global Privacy Control setting. */ -class GlobalPrivacyControlTest { - private lateinit var mockWebServer: MockWebServer +class GlobalPrivacyControlTest : TestSetup() { private lateinit var gpcPage: TestAsset @get:Rule @@ -33,20 +30,11 @@ class GlobalPrivacyControlTest { ) @Before - fun setUp() { - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - + override fun setUp() { + super.setUp() gpcPage = getGPCTestAsset(mockWebServer) } - @After - fun tearDown() { - mockWebServer.shutdown() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2429327 @Test fun testGPCinNormalBrowsing() { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/HistoryTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/HistoryTest.kt index f29372c63..10ca3d150 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/HistoryTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/HistoryTest.kt @@ -4,24 +4,14 @@ package org.mozilla.fenix.ui -import android.content.Context import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu import androidx.test.espresso.Espresso.pressBack -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice -import kotlinx.coroutines.runBlocking -import mozilla.components.browser.storage.sync.PlacesHistoryStorage -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.R import org.mozilla.fenix.customannotations.SmokeTest -import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AppAndSystemHelper.registerAndCleanupIdlingResources import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.MockBrowserDataHelper @@ -29,7 +19,9 @@ import org.mozilla.fenix.helpers.RecyclerViewIdlingResource import org.mozilla.fenix.helpers.TestAssetHelper import org.mozilla.fenix.helpers.TestHelper.exitMenu import org.mozilla.fenix.helpers.TestHelper.longTapSelectItem +import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.browserScreen import org.mozilla.fenix.ui.robots.historyMenu import org.mozilla.fenix.ui.robots.homeScreen @@ -40,40 +32,13 @@ import org.mozilla.fenix.ui.robots.navigationToolbar * Tests for verifying basic functionality of history * */ -class HistoryTest { - private lateinit var mockWebServer: MockWebServer - private lateinit var mDevice: UiDevice - +class HistoryTest : TestSetup() { @get:Rule val activityTestRule = AndroidComposeTestRule( - HomeActivityIntentTestRule.withDefaultSettingsOverrides(), + HomeActivityIntentTestRule(isJumpBackInCFREnabled = false), ) { it.activity } - @Before - fun setUp() { - InstrumentationRegistry.getInstrumentation().targetContext.settings() - .shouldShowJumpBackInCFR = false - - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - // Clearing all history data after each test to avoid overlapping data - val applicationContext: Context = activityTestRule.activity.applicationContext - val historyStorage = PlacesHistoryStorage(applicationContext) - - runBlocking { - historyStorage.deleteEverything() - } - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/243285 @Test fun verifyEmptyHistoryMenuTest() { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/HomeScreenTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/HomeScreenTest.kt index 3f3ba4d31..f1cff8c78 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/HomeScreenTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/HomeScreenTest.kt @@ -5,19 +5,14 @@ package org.mozilla.fenix.ui import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.fenix.customannotations.SmokeTest -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.helpers.RetryTestRule import org.mozilla.fenix.helpers.TestAssetHelper import org.mozilla.fenix.helpers.TestHelper +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.navigationToolbar import org.mozilla.fenix.ui.robots.searchScreen @@ -29,10 +24,7 @@ import org.mozilla.fenix.ui.robots.searchScreen * */ -class HomeScreenTest { - private lateinit var mDevice: UiDevice - private lateinit var mockWebServer: MockWebServer - +class HomeScreenTest : TestSetup() { @get:Rule(order = 0) val activityTestRule = AndroidComposeTestRule(HomeActivityTestRule.withDefaultSettingsOverrides()) { it.activity } @@ -41,21 +33,6 @@ class HomeScreenTest { @JvmField val retryTestRule = RetryTestRule(3) - @Before - fun setUp() { - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/235396 @Test fun homeScreenItemsTest() { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/LoginsTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/LoginsTest.kt index 394c449eb..e91723e27 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/LoginsTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/LoginsTest.kt @@ -7,14 +7,11 @@ package org.mozilla.fenix.ui import android.os.Build import android.view.autofill.AutofillManager import androidx.core.net.toUri -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.customannotations.SmokeTest -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.MatcherHelper import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId @@ -29,6 +26,7 @@ import org.mozilla.fenix.helpers.TestHelper.restartApp import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText import org.mozilla.fenix.helpers.TestHelper.waitForAppWindowToBeUpdated +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.browserScreen import org.mozilla.fenix.ui.robots.clearTextFieldItem import org.mozilla.fenix.ui.robots.clickPageObject @@ -42,20 +40,14 @@ import org.mozilla.fenix.ui.robots.setPageObjectText * - save login prompts. * - saving logins based on the user's preferences. */ -class LoginsTest { - private lateinit var mockWebServer: MockWebServer - +class LoginsTest : TestSetup() { @get:Rule val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true) @Before - fun setUp() { - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - + override fun setUp() { + super.setUp() if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { val autofillManager: AutofillManager = TestHelper.appContext.getSystemService(AutofillManager::class.java) @@ -63,11 +55,6 @@ class LoginsTest { } } - @After - fun tearDown() { - mockWebServer.shutdown() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2092713 // Tests the Logins and passwords menu items and default values @Test From b51accb66f87d87d5cf85b4d5817193ad18e8af8 Mon Sep 17 00:00:00 2001 From: "oana.horvath" Date: Thu, 15 Feb 2024 11:55:22 +0200 Subject: [PATCH 063/238] Bug 1880497 - Create TestSetup helper: Test classes M-N --- .../java/org/mozilla/fenix/ui/MainMenuTest.kt | 27 ++------------- .../mozilla/fenix/ui/MediaNotificationTest.kt | 34 ++----------------- .../mozilla/fenix/ui/NavigationToolbarTest.kt | 29 ++-------------- .../org/mozilla/fenix/ui/NimbusEventTest.kt | 26 ++------------ .../fenix/ui/NimbusMessagingHomescreenTest.kt | 25 +++----------- .../fenix/ui/NimbusMessagingMessageTest.kt | 11 +++--- .../ui/NimbusMessagingNotificationTest.kt | 12 +++---- .../fenix/ui/NimbusMessagingTriggerTest.kt | 11 +++--- .../fenix/ui/NoNetworkAccessStartupTests.kt | 10 ++---- 9 files changed, 30 insertions(+), 155 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/MainMenuTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/MainMenuTest.kt index 24a877737..7302bc9b0 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/MainMenuTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/MainMenuTest.kt @@ -5,17 +5,11 @@ package org.mozilla.fenix.ui import androidx.core.net.toUri -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice import mozilla.components.concept.engine.utils.EngineReleaseChannel -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.fenix.customannotations.SmokeTest import org.mozilla.fenix.ext.components -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AppAndSystemHelper.assertNativeAppOpens import org.mozilla.fenix.helpers.AppAndSystemHelper.assertYoutubeAppOpens import org.mozilla.fenix.helpers.AppAndSystemHelper.runWithCondition @@ -25,6 +19,8 @@ import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.MatcherHelper import org.mozilla.fenix.helpers.TestAssetHelper import org.mozilla.fenix.helpers.TestHelper +import org.mozilla.fenix.helpers.TestHelper.mDevice +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.browserScreen import org.mozilla.fenix.ui.robots.clickContextMenuItem import org.mozilla.fenix.ui.robots.clickPageObject @@ -32,28 +28,11 @@ import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.longClickPageObject import org.mozilla.fenix.ui.robots.navigationToolbar -class MainMenuTest { - private lateinit var mDevice: UiDevice - private lateinit var mockWebServer: MockWebServer - +class MainMenuTest : TestSetup() { @get:Rule val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(translationsEnabled = true) - @Before - fun setUp() { - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/233849 @Test fun verifyTabMainMenuItemsTest() { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/MediaNotificationTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/MediaNotificationTest.kt index eba6aad2e..33304899c 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/MediaNotificationTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/MediaNotificationTest.kt @@ -4,23 +4,17 @@ package org.mozilla.fenix.ui -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice -import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.mediasession.MediaSession -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.fenix.customannotations.SmokeTest -import org.mozilla.fenix.ext.components -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.helpers.MatcherHelper.itemWithText import org.mozilla.fenix.helpers.RetryTestRule import org.mozilla.fenix.helpers.TestAssetHelper +import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.browserScreen import org.mozilla.fenix.ui.robots.clickPageObject import org.mozilla.fenix.ui.robots.homeScreen @@ -33,36 +27,14 @@ import org.mozilla.fenix.ui.robots.notificationShade * - a media notification icon is displayed on the homescreen for the tab playing media content * Note: this test only verifies media notifications, not media itself */ -class MediaNotificationTest { - private lateinit var mockWebServer: MockWebServer - private lateinit var mDevice: UiDevice - +class MediaNotificationTest : TestSetup() { @get:Rule val activityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides() - private lateinit var browserStore: BrowserStore @Rule @JvmField val retryTestRule = RetryTestRule(3) - @Before - fun setUp() { - // Initializing this as part of class construction, below the rule would throw a NPE - // So we are initializing this here instead of in all tests. - browserStore = activityTestRule.activity.components.core.store - - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1347033 @SmokeTest @Test diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/NavigationToolbarTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/NavigationToolbarTest.kt index 13c8812c5..a486a4e67 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/NavigationToolbarTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/NavigationToolbarTest.kt @@ -5,19 +5,14 @@ package org.mozilla.fenix.ui import androidx.core.net.toUri -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.fenix.customannotations.SmokeTest -import org.mozilla.fenix.helpers.AndroidAssetDispatcher -import org.mozilla.fenix.helpers.AppAndSystemHelper import org.mozilla.fenix.helpers.AppAndSystemHelper.runWithSystemLocaleChanged import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.helpers.TestAssetHelper +import org.mozilla.fenix.helpers.TestHelper.mDevice +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.navigationToolbar import java.util.Locale @@ -32,28 +27,10 @@ import java.util.Locale * - Find in page */ -class NavigationToolbarTest { - private lateinit var mDevice: UiDevice - private lateinit var mockWebServer: MockWebServer - +class NavigationToolbarTest : TestSetup() { @get:Rule val activityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides() - @Before - fun setUp() { - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - AppAndSystemHelper.resetSystemLocaleToEnUS() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/987326 // Swipes the nav bar left/right to switch between tabs @SmokeTest diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusEventTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusEventTest.kt index 0d63197d9..943e0624c 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusEventTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusEventTest.kt @@ -5,28 +5,20 @@ package org.mozilla.fenix.ui import android.content.Intent -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice import io.mockk.mockk import mozilla.components.concept.sync.AuthType import mozilla.components.service.fxa.FirefoxAccount -import okhttp3.mockwebserver.MockWebServer -import org.junit.After import org.junit.Assert.assertTrue -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.fenix.components.TelemetryAccountObserver -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.Experimentation import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.RetryTestRule import org.mozilla.fenix.helpers.TestHelper.appContext +import org.mozilla.fenix.helpers.TestSetup -class NimbusEventTest { - private lateinit var mDevice: UiDevice - private lateinit var mockWebServer: MockWebServer - +class NimbusEventTest : TestSetup() { @get:Rule val homeActivityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides() .withIntent( @@ -39,20 +31,6 @@ class NimbusEventTest { @JvmField val retryTestRule = RetryTestRule(3) - @Before - fun setUp() { - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - @Test fun homeScreenNimbusEventsTest() { Experimentation.withHelper { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingHomescreenTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingHomescreenTest.kt index f71784acc..22f4dd412 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingHomescreenTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingHomescreenTest.kt @@ -5,22 +5,18 @@ package org.mozilla.fenix.ui import android.content.Intent -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice import mozilla.components.service.nimbus.messaging.FxNimbusMessaging import mozilla.components.service.nimbus.messaging.MessageData import mozilla.components.service.nimbus.messaging.Messaging import mozilla.components.service.nimbus.messaging.StyleData -import okhttp3.mockwebserver.MockWebServer -import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.experiments.nimbus.Res import org.mozilla.fenix.FenixApplication -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.RetryTestRule +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.nimbus.HomeScreenSection import org.mozilla.fenix.nimbus.Homescreen @@ -32,10 +28,7 @@ import org.mozilla.fenix.ui.robots.homeScreen * Verifies a message can be displayed with all of the correct components **/ -class NimbusMessagingHomescreenTest { - private lateinit var mDevice: UiDevice - private lateinit var mockWebServer: MockWebServer - +class NimbusMessagingHomescreenTest : TestSetup() { private var messageButtonLabel = "CLICK ME" private var messageText = "Some Nimbus Messaging text" private var messageTitle = "A Nimbus title" @@ -54,7 +47,8 @@ class NimbusMessagingHomescreenTest { val retryTestRule = RetryTestRule(3) @Before - fun setUp() { + override fun setUp() { + super.setUp() // Set up nimbus message FxNimbusMessaging.features.messaging.withInitializer { _, _ -> // FML generated objects. @@ -95,22 +89,11 @@ class NimbusMessagingHomescreenTest { ), ) } - - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } // refresh message store val application = (homeActivityTestRule.activity.application as FenixApplication) application.restoreMessaging() } - @After - fun tearDown() { - mockWebServer.shutdown() - } - @Test fun testNimbusMessageIsDisplayed() { // Checks the home screen card message is displayed correctly diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingMessageTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingMessageTest.kt index 4cbd0735c..0ca94358e 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingMessageTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingMessageTest.kt @@ -5,8 +5,6 @@ package org.mozilla.fenix.ui import android.content.Context -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice import kotlinx.coroutines.test.runTest import mozilla.components.service.nimbus.messaging.FxNimbusMessaging import mozilla.components.service.nimbus.messaging.Messaging @@ -20,6 +18,7 @@ import org.junit.Test import org.mozilla.fenix.ext.components import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.TestHelper +import org.mozilla.fenix.helpers.TestSetup /** * This test is to test the integrity of messages hardcoded in the FML. @@ -27,10 +26,8 @@ import org.mozilla.fenix.helpers.TestHelper * It tests if the trigger expressions are valid, all the fields are complete * and a simple check if they are localized (don't contain `_`). */ -class NimbusMessagingMessageTest { +class NimbusMessagingMessageTest : TestSetup() { private lateinit var feature: Messaging - private lateinit var mDevice: UiDevice - private lateinit var context: Context private val messaging @@ -41,9 +38,9 @@ class NimbusMessagingMessageTest { HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true) @Before - fun setUp() { + override fun setUp() { + super.setUp() context = TestHelper.appContext - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) feature = FxNimbusMessaging.features.messaging.value() } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingNotificationTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingNotificationTest.kt index 9f3762a21..1348ff007 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingNotificationTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingNotificationTest.kt @@ -6,10 +6,8 @@ package org.mozilla.fenix.ui import android.content.Context import android.os.Build -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.GrantPermissionRule import androidx.test.rule.GrantPermissionRule.grant -import androidx.test.uiautomator.UiDevice import mozilla.components.service.nimbus.messaging.FxNimbusMessaging import org.json.JSONObject import org.junit.Before @@ -18,15 +16,15 @@ import org.junit.Test import org.mozilla.experiments.nimbus.HardcodedNimbusFeatures import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.TestHelper +import org.mozilla.fenix.helpers.TestHelper.mDevice +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.ui.robots.notificationShade /** * A UI test for testing the notification surface for Nimbus Messaging. */ -class NimbusMessagingNotificationTest { - private lateinit var mDevice: UiDevice - +class NimbusMessagingNotificationTest : TestSetup() { private lateinit var context: Context private lateinit var hardcodedNimbus: HardcodedNimbusFeatures @@ -43,9 +41,9 @@ class NimbusMessagingNotificationTest { } @Before - fun setUp() { + override fun setUp() { + super.setUp() context = TestHelper.appContext - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) } @Test diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingTriggerTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingTriggerTest.kt index e061d6845..31be3ec83 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingTriggerTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/NimbusMessagingTriggerTest.kt @@ -4,8 +4,6 @@ package org.mozilla.fenix.ui -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice import mozilla.components.service.nimbus.messaging.FxNimbusMessaging import mozilla.components.service.nimbus.messaging.Messaging import org.junit.Assert @@ -17,6 +15,7 @@ import org.mozilla.experiments.nimbus.internal.NimbusException import org.mozilla.fenix.ext.components import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.TestHelper +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.messaging.CustomAttributeProvider /** @@ -26,9 +25,7 @@ import org.mozilla.fenix.messaging.CustomAttributeProvider * - as much of the custom targeting and trigger attributes are recorded as possible. * - we can run the Rust JEXL evaluator. */ -class NimbusMessagingTriggerTest { - private lateinit var mDevice: UiDevice - +class NimbusMessagingTriggerTest : TestSetup() { private lateinit var feature: Messaging private lateinit var nimbus: NimbusInterface @@ -36,8 +33,8 @@ class NimbusMessagingTriggerTest { val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true) @Before - fun setUp() { - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + override fun setUp() { + super.setUp() nimbus = TestHelper.appContext.components.nimbus.sdk feature = FxNimbusMessaging.features.messaging.value() } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/NoNetworkAccessStartupTests.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/NoNetworkAccessStartupTests.kt index fd30ae0ad..ce59c9e56 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/NoNetworkAccessStartupTests.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/NoNetworkAccessStartupTests.kt @@ -5,7 +5,6 @@ package org.mozilla.fenix.ui import androidx.core.net.toUri -import org.junit.After import org.junit.Rule import org.junit.Test import org.mozilla.fenix.R @@ -14,6 +13,7 @@ import org.mozilla.fenix.helpers.AppAndSystemHelper.setNetworkEnabled import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.helpers.TestHelper.packageName import org.mozilla.fenix.helpers.TestHelper.verifyUrl +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.browserScreen import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.navigationToolbar @@ -23,17 +23,11 @@ import org.mozilla.fenix.ui.robots.navigationToolbar * */ -class NoNetworkAccessStartupTests { +class NoNetworkAccessStartupTests : TestSetup() { @get:Rule val activityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides(launchActivity = false) - @After - fun tearDown() { - // Restoring network connection - setNetworkEnabled(true) - } - // Test running on beta/release builds in CI: // caution when making changes to it, so they don't block the builds // Based on STR from https://github.com/mozilla-mobile/fenix/issues/16886 From b19419224dbe0736de4bd5ab06e72775d8622509 Mon Sep 17 00:00:00 2001 From: "oana.horvath" Date: Mon, 26 Feb 2024 14:51:25 +0200 Subject: [PATCH 064/238] Bug 1880796 - Create TestSetup helper: Test classes O-R --- .../org/mozilla/fenix/ui/OnboardingTest.kt | 3 +- .../org/mozilla/fenix/ui/PDFViewerTest.kt | 29 ++----------------- .../java/org/mozilla/fenix/ui/PocketTest.kt | 11 +++---- .../java/org/mozilla/fenix/ui/PwaTest.kt | 3 +- .../org/mozilla/fenix/ui/ReaderViewTest.kt | 26 ++--------------- .../fenix/ui/RecentlyClosedTabsTest.kt | 29 +++---------------- .../java/org/mozilla/fenix/ui/SearchTest.kt | 10 +++---- 7 files changed, 22 insertions(+), 89 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/OnboardingTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/OnboardingTest.kt index 9913b7769..6b305496e 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/OnboardingTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/OnboardingTest.kt @@ -6,9 +6,10 @@ import org.junit.Test import org.mozilla.fenix.customannotations.SmokeTest import org.mozilla.fenix.helpers.AppAndSystemHelper.runWithLauncherIntent import org.mozilla.fenix.helpers.HomeActivityIntentTestRule +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.homeScreen -class OnboardingTest { +class OnboardingTest : TestSetup() { @get:Rule val activityTestRule = diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/PDFViewerTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/PDFViewerTest.kt index 340cbd08e..913d1c7d2 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/PDFViewerTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/PDFViewerTest.kt @@ -5,17 +5,10 @@ package org.mozilla.fenix.ui import androidx.core.net.toUri -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.fenix.customannotations.SmokeTest -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AppAndSystemHelper.assertExternalAppOpens -import org.mozilla.fenix.helpers.AppAndSystemHelper.clearDownloadsFolder import org.mozilla.fenix.helpers.Constants.PackageName.GOOGLE_DOCS import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.MatcherHelper @@ -23,12 +16,11 @@ 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.getGenericAsset -import org.mozilla.fenix.helpers.TestHelper.mDevice +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.clickPageObject import org.mozilla.fenix.ui.robots.navigationToolbar -class PDFViewerTest { - private lateinit var mockWebServer: MockWebServer +class PDFViewerTest : TestSetup() { private val downloadTestPage = "https://storage.googleapis.com/mobile_test_assets/test_app/downloads.html" private val pdfFileName = "washington.pdf" @@ -38,23 +30,6 @@ class PDFViewerTest { @get:Rule val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides() - @Before - fun setUp() { - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - - // Check and clear the downloads folder - clearDownloadsFolder() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2048140 @SmokeTest @Test diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/PocketTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/PocketTest.kt index 885fa80c0..674924c95 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/PocketTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/PocketTest.kt @@ -1,22 +1,20 @@ package org.mozilla.fenix.ui import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.fenix.helpers.Constants import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.helpers.RetryTestRule +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.homeScreen /** * Tests for verifying the presence of the Pocket section and its elements */ -class PocketTest { - private lateinit var mDevice: UiDevice +class PocketTest : TestSetup() { private lateinit var firstPocketStoryPublisher: String @get:Rule(order = 0) @@ -28,9 +26,8 @@ class PocketTest { val retryTestRule = RetryTestRule(3) @Before - fun setUp() { - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - + override fun setUp() { + super.setUp() // Workaround to make sure the Pocket articles are populated before starting the tests. homeScreen { }.openThreeDotMenu { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/PwaTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/PwaTest.kt index 4ccfa4a97..6ce798c2f 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/PwaTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/PwaTest.kt @@ -11,12 +11,13 @@ import org.mozilla.fenix.customannotations.SmokeTest import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText import org.mozilla.fenix.helpers.TestHelper.mDevice +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.clickPageObject import org.mozilla.fenix.ui.robots.customTabScreen import org.mozilla.fenix.ui.robots.navigationToolbar import org.mozilla.fenix.ui.robots.pwaScreen -class PwaTest { +class PwaTest : TestSetup() { /* Updated externalLinks.html to v2.0, changed the hypertext reference to mozilla-mobile.github.io/testapp/downloads for "External link" */ diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/ReaderViewTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/ReaderViewTest.kt index aa2215fdd..8e138aa8b 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/ReaderViewTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/ReaderViewTest.kt @@ -5,20 +5,16 @@ package org.mozilla.fenix.ui import android.view.View -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.fenix.R import org.mozilla.fenix.customannotations.SmokeTest -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AppAndSystemHelper.registerAndCleanupIdlingResources import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.RetryTestRule import org.mozilla.fenix.helpers.TestAssetHelper +import org.mozilla.fenix.helpers.TestHelper.mDevice +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.helpers.ViewVisibilityIdlingResource import org.mozilla.fenix.ui.robots.browserScreen import org.mozilla.fenix.ui.robots.navigationToolbar @@ -32,9 +28,7 @@ import org.mozilla.fenix.ui.robots.navigationToolbar * */ -class ReaderViewTest { - private lateinit var mockWebServer: MockWebServer - private lateinit var mDevice: UiDevice +class ReaderViewTest : TestSetup() { private val estimatedReadingTime = "1 - 2 minutes" @get:Rule @@ -44,20 +38,6 @@ class ReaderViewTest { @JvmField val retryTestRule = RetryTestRule(3) - @Before - fun setUp() { - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - /** * Verify that Reader View capable pages * diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/RecentlyClosedTabsTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/RecentlyClosedTabsTest.kt index ca4084655..044696b36 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/RecentlyClosedTabsTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/RecentlyClosedTabsTest.kt @@ -6,21 +6,17 @@ package org.mozilla.fenix.ui import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu -import androidx.test.espresso.intent.Intents -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.fenix.R import org.mozilla.fenix.customannotations.SmokeTest -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AppAndSystemHelper.registerAndCleanupIdlingResources -import org.mozilla.fenix.helpers.HomeActivityTestRule +import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.RecyclerViewIdlingResource import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset import org.mozilla.fenix.helpers.TestHelper.longTapSelectItem import org.mozilla.fenix.helpers.TestHelper.mDevice +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.browserScreen import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.navigationToolbar @@ -29,31 +25,14 @@ import org.mozilla.fenix.ui.robots.navigationToolbar * Tests for verifying basic functionality of recently closed tabs history * */ -class RecentlyClosedTabsTest { - private lateinit var mockWebServer: MockWebServer - +class RecentlyClosedTabsTest : TestSetup() { @get:Rule val activityTestRule = AndroidComposeTestRule( - HomeActivityTestRule.withDefaultSettingsOverrides( + HomeActivityIntentTestRule.withDefaultSettingsOverrides( tabsTrayRewriteEnabled = true, ), ) { it.activity } - @Before - fun setUp() { - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - - Intents.init() - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1065414 // Verifies that a recently closed item is properly opened @SmokeTest diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SearchTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SearchTest.kt index d1d0076cd..ede7b06b5 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SearchTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SearchTest.kt @@ -21,7 +21,6 @@ import org.junit.Test import org.mozilla.fenix.customannotations.SmokeTest import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.helpers.AppAndSystemHelper import org.mozilla.fenix.helpers.AppAndSystemHelper.assertNativeAppOpens import org.mozilla.fenix.helpers.AppAndSystemHelper.denyPermission import org.mozilla.fenix.helpers.AppAndSystemHelper.grantSystemPermission @@ -45,6 +44,7 @@ import org.mozilla.fenix.helpers.TestHelper.exitMenu import org.mozilla.fenix.helpers.TestHelper.longTapSelectItem import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.clickContextMenuItem import org.mozilla.fenix.ui.robots.clickPageObject import org.mozilla.fenix.ui.robots.homeScreen @@ -64,7 +64,7 @@ import java.util.Locale * */ -class SearchTest { +class SearchTest : TestSetup() { private lateinit var searchMockServer: MockWebServer private var queryString = "firefox" private val generalEnginesList = listOf("DuckDuckGo", "Google", "Bing") @@ -84,7 +84,8 @@ class SearchTest { ) { it.activity } @Before - fun setUp() { + override fun setUp() { + super.setUp() searchMockServer = MockWebServer().apply { dispatcher = SearchDispatcher() start() @@ -92,9 +93,8 @@ class SearchTest { } @After - fun tearDown() { + override fun tearDown() { searchMockServer.shutdown() - AppAndSystemHelper.resetSystemLocaleToEnUS() } // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2154189 From 0c13a2d516e26c3122e96d967086971b90e27dd8 Mon Sep 17 00:00:00 2001 From: "oana.horvath" Date: Mon, 26 Feb 2024 16:51:30 +0200 Subject: [PATCH 065/238] Bug 1880797 - Create TestSetup helper: Settings Test classes --- .../org/mozilla/fenix/ui/SettingsAboutTest.kt | 26 ++--------------- .../mozilla/fenix/ui/SettingsAddonsTest.kt | 22 ++------------- .../mozilla/fenix/ui/SettingsAdvancedTest.kt | 27 ++---------------- .../mozilla/fenix/ui/SettingsCustomizeTest.kt | 22 ++------------- .../SettingsDeleteBrowsingDataOnQuitTest.kt | 26 ++--------------- .../ui/SettingsDeleteBrowsingDataTest.kt | 22 ++------------- .../mozilla/fenix/ui/SettingsGeneralTest.kt | 24 ++-------------- .../fenix/ui/SettingsHTTPSOnlyModeTest.kt | 3 +- .../mozilla/fenix/ui/SettingsHomepageTest.kt | 22 ++------------- .../mozilla/fenix/ui/SettingsPrivacyTest.kt | 27 ++---------------- .../fenix/ui/SettingsPrivateBrowsingTest.kt | 21 ++------------ .../mozilla/fenix/ui/SettingsSearchTest.kt | 20 +++++-------- .../fenix/ui/SettingsSitePermissionsTest.kt | 28 ++----------------- 13 files changed, 33 insertions(+), 257 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAboutTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAboutTest.kt index bd2debace..3e37fb5e3 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAboutTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAboutTest.kt @@ -4,18 +4,13 @@ package org.mozilla.fenix.ui -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiSelector -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.RetryTestRule import org.mozilla.fenix.helpers.TestHelper.mDevice +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.clickRateButtonGooglePlay import org.mozilla.fenix.ui.robots.homeScreen @@ -24,10 +19,7 @@ import org.mozilla.fenix.ui.robots.homeScreen * */ -class SettingsAboutTest { - private lateinit var mDevice: UiDevice - private lateinit var mockWebServer: MockWebServer - +class SettingsAboutTest : TestSetup() { @get:Rule val activityIntentTestRule = HomeActivityIntentTestRule() @@ -35,20 +27,6 @@ class SettingsAboutTest { @JvmField val retryTestRule = RetryTestRule(3) - @Before - fun setUp() { - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - // Walks through the About settings menu to ensure all items are present // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2092700 @Test diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAddonsTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAddonsTest.kt index 62923e2c0..695d0af3c 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAddonsTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAddonsTest.kt @@ -4,15 +4,11 @@ package org.mozilla.fenix.ui -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.fenix.R import org.mozilla.fenix.customannotations.SmokeTest import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AppAndSystemHelper.registerAndCleanupIdlingResources import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.RecyclerViewIdlingResource @@ -20,6 +16,7 @@ import org.mozilla.fenix.helpers.TestAssetHelper.getEnhancedTrackingProtectionAs import org.mozilla.fenix.helpers.TestHelper import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText import org.mozilla.fenix.helpers.TestHelper.waitUntilSnackbarGone +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.addonsMenu import org.mozilla.fenix.ui.robots.homeScreen @@ -27,25 +24,10 @@ import org.mozilla.fenix.ui.robots.homeScreen * Tests for verifying the functionality of installing or removing addons * */ -class SettingsAddonsTest { - private lateinit var mockWebServer: MockWebServer - +class SettingsAddonsTest : TestSetup() { @get:Rule val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides() - @Before - fun setUp() { - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/875780 // Walks through settings add-ons menu to ensure all items are present @Test diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAdvancedTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAdvancedTest.kt index 86b0b1752..da889b435 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAdvancedTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsAdvancedTest.kt @@ -5,16 +5,10 @@ package org.mozilla.fenix.ui import androidx.core.net.toUri -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.fenix.customannotations.SmokeTest import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AppAndSystemHelper.assertYoutubeAppOpens import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText @@ -22,6 +16,8 @@ import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdAndText import org.mozilla.fenix.helpers.TestAssetHelper import org.mozilla.fenix.helpers.TestHelper import org.mozilla.fenix.helpers.TestHelper.exitMenu +import org.mozilla.fenix.helpers.TestHelper.mDevice +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.clickPageObject import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.navigationToolbar @@ -31,31 +27,14 @@ import org.mozilla.fenix.ui.robots.navigationToolbar * */ -class SettingsAdvancedTest { - private lateinit var mDevice: UiDevice - private lateinit var mockWebServer: MockWebServer +class SettingsAdvancedTest : TestSetup() { private val youTubeSchemaLink = itemContainingText("Youtube schema link") - private val youTubeFullLink = itemContainingText("Youtube full link") private val playStoreLink = itemContainingText("Playstore link") private val playStoreUrl = "play.google.com" @get:Rule val activityIntentTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides() - @Before - fun setUp() { - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2092699 // Walks through settings menu and sub-menus to ensure all items are present @Test diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsCustomizeTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsCustomizeTest.kt index 78b0b4af8..f20ff1418 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsCustomizeTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsCustomizeTest.kt @@ -5,39 +5,21 @@ package org.mozilla.fenix.ui import android.content.res.Configuration -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.TestAssetHelper import org.mozilla.fenix.helpers.TestHelper.exitMenu import org.mozilla.fenix.helpers.TestHelper.verifyDarkThemeApplied import org.mozilla.fenix.helpers.TestHelper.verifyLightThemeApplied +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.navigationToolbar -class SettingsCustomizeTest { - private lateinit var mockWebServer: MockWebServer - +class SettingsCustomizeTest : TestSetup() { @get:Rule val activityIntentTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides() - @Before - fun setUp() { - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - private fun getUiTheme(): Boolean { val mode = activityIntentTestRule.activity.resources?.configuration?.uiMode?.and(Configuration.UI_MODE_NIGHT_MASK) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsDeleteBrowsingDataOnQuitTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsDeleteBrowsingDataOnQuitTest.kt index 40a26ac53..cae712884 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsDeleteBrowsingDataOnQuitTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsDeleteBrowsingDataOnQuitTest.kt @@ -8,15 +8,10 @@ import android.Manifest import androidx.core.net.toUri import androidx.test.espresso.Espresso.pressBack import androidx.test.rule.GrantPermissionRule -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mozilla.fenix.R import org.mozilla.fenix.customannotations.SmokeTest -import org.mozilla.fenix.helpers.AndroidAssetDispatcher -import org.mozilla.fenix.helpers.AppAndSystemHelper import org.mozilla.fenix.helpers.AppAndSystemHelper.setNetworkEnabled import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource import org.mozilla.fenix.helpers.HomeActivityIntentTestRule @@ -26,6 +21,7 @@ import org.mozilla.fenix.helpers.TestAssetHelper.getStorageTestAsset import org.mozilla.fenix.helpers.TestHelper.exitMenu import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.restartApp +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.clickPageObject import org.mozilla.fenix.ui.robots.downloadRobot import org.mozilla.fenix.ui.robots.homeScreen @@ -36,9 +32,7 @@ import org.mozilla.fenix.ui.robots.navigationToolbar * Delete Browsing Data on quit * */ -class SettingsDeleteBrowsingDataOnQuitTest { - private lateinit var mockWebServer: MockWebServer - +class SettingsDeleteBrowsingDataOnQuitTest : TestSetup() { @get:Rule val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true) @@ -48,22 +42,6 @@ class SettingsDeleteBrowsingDataOnQuitTest { Manifest.permission.RECORD_AUDIO, ) - @Before - fun setUp() { - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - - // Check and clear the downloads folder - AppAndSystemHelper.clearDownloadsFolder() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/416048 @Test fun deleteBrowsingDataOnQuitSettingTest() { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsDeleteBrowsingDataTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsDeleteBrowsingDataTest.kt index 14fd12d11..a2dcf1269 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsDeleteBrowsingDataTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsDeleteBrowsingDataTest.kt @@ -4,15 +4,11 @@ package org.mozilla.fenix.ui -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.R import org.mozilla.fenix.customannotations.SmokeTest -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AppAndSystemHelper.setNetworkEnabled import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource import org.mozilla.fenix.helpers.HomeActivityIntentTestRule @@ -22,6 +18,7 @@ import org.mozilla.fenix.helpers.TestAssetHelper.getStorageTestAsset import org.mozilla.fenix.helpers.TestHelper.exitMenu import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.restartApp +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.browserScreen import org.mozilla.fenix.ui.robots.clickPageObject import org.mozilla.fenix.ui.robots.homeScreen @@ -33,25 +30,10 @@ import org.mozilla.fenix.ui.robots.settingsScreen * Delete Browsing Data */ -class SettingsDeleteBrowsingDataTest { - private lateinit var mockWebServer: MockWebServer - +class SettingsDeleteBrowsingDataTest : TestSetup() { @get:Rule val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true) - @Before - fun setUp() { - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/937561 @Test fun deleteBrowsingDataOptionStatesTest() { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsGeneralTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsGeneralTest.kt index d2292b31c..5736fa14c 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsGeneralTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsGeneralTest.kt @@ -4,17 +4,12 @@ package org.mozilla.fenix.ui -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.AppAndSystemHelper import org.mozilla.fenix.helpers.AppAndSystemHelper.registerAndCleanupIdlingResources import org.mozilla.fenix.helpers.AppAndSystemHelper.runWithSystemLocaleChanged import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource @@ -23,6 +18,7 @@ import org.mozilla.fenix.helpers.RecyclerViewIdlingResource import org.mozilla.fenix.helpers.TestAssetHelper.getLoremIpsumAsset import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeLong import org.mozilla.fenix.helpers.TestHelper.mDevice +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.checkTextSizeOnWebsite import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.util.FRENCH_LANGUAGE_HEADER @@ -35,26 +31,10 @@ import java.util.Locale * Tests for verifying the General section of the Settings menu * */ -class SettingsGeneralTest { - private lateinit var mockWebServer: MockWebServer - +class SettingsGeneralTest : TestSetup() { @get:Rule val activityIntentTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides() - @Before - fun setUp() { - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - AppAndSystemHelper.resetSystemLocaleToEnUS() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2092697 @Test fun verifyGeneralSettingsItemsTest() { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsHTTPSOnlyModeTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsHTTPSOnlyModeTest.kt index f8c1bccbb..9636810e3 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsHTTPSOnlyModeTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsHTTPSOnlyModeTest.kt @@ -11,11 +11,12 @@ import org.mozilla.fenix.customannotations.SmokeTest import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText import org.mozilla.fenix.helpers.TestHelper.exitMenu +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.clickPageObject import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.navigationToolbar -class SettingsHTTPSOnlyModeTest { +class SettingsHTTPSOnlyModeTest : TestSetup() { private val httpPageUrl = "http://example.com/" private val httpsPageUrl = "https://example.com/" private val insecureHttpPage = "http.badssl.com" diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsHomepageTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsHomepageTest.kt index bc4a78a9f..8dc31c64e 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsHomepageTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsHomepageTest.kt @@ -4,14 +4,10 @@ package org.mozilla.fenix.ui -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.customannotations.SmokeTest -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AppAndSystemHelper.openAppFromExternalLink import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.RetryTestRule @@ -19,6 +15,7 @@ import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.restartApp import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.browserScreen import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.navigationToolbar @@ -27,9 +24,7 @@ import org.mozilla.fenix.ui.robots.navigationToolbar * Tests for verifying the Homepage settings menu * */ -class SettingsHomepageTest { - private lateinit var mockWebServer: MockWebServer - +class SettingsHomepageTest : TestSetup() { @get:Rule val activityIntentTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true) @@ -37,19 +32,6 @@ class SettingsHomepageTest { @JvmField val retryTestRule = RetryTestRule(3) - @Before - fun setUp() { - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1564843 @Test fun verifyHomepageSettingsTest() { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsPrivacyTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsPrivacyTest.kt index bbfe7cb91..93084fb3e 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsPrivacyTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsPrivacyTest.kt @@ -4,16 +4,12 @@ package org.mozilla.fenix.ui -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.helpers.TestAssetHelper +import org.mozilla.fenix.helpers.TestHelper.mDevice +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.navigationToolbar import org.mozilla.fenix.ui.robots.notificationShade @@ -23,27 +19,10 @@ import org.mozilla.fenix.ui.robots.notificationShade * */ -class SettingsPrivacyTest { - private lateinit var mDevice: UiDevice - private lateinit var mockWebServer: MockWebServer - +class SettingsPrivacyTest : TestSetup() { @get:Rule val activityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides(skipOnboarding = true) - @Before - fun setUp() { - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2092698 @Test fun settingsPrivacyItemsTest() { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsPrivateBrowsingTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsPrivateBrowsingTest.kt index bf52c2748..033addb4c 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsPrivateBrowsingTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsPrivateBrowsingTest.kt @@ -4,43 +4,26 @@ package org.mozilla.fenix.ui -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AppAndSystemHelper.openAppFromExternalLink import org.mozilla.fenix.helpers.DataGenerationHelper.generateRandomString import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.TestAssetHelper import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.restartApp +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.addToHomeScreen import org.mozilla.fenix.ui.robots.browserScreen import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.navigationToolbar -class SettingsPrivateBrowsingTest { - private lateinit var mockWebServer: MockWebServer +class SettingsPrivateBrowsingTest : TestSetup() { private val pageShortcutName = generateRandomString(5) @get:Rule val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true) - @Before - fun setUp() { - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/555822 @Test fun verifyPrivateBrowsingMenuItemsTest() { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsSearchTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsSearchTest.kt index f9aab824f..c6ae4df21 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsSearchTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsSearchTest.kt @@ -13,8 +13,6 @@ import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.mozilla.fenix.customannotations.SmokeTest -import org.mozilla.fenix.helpers.AndroidAssetDispatcher -import org.mozilla.fenix.helpers.AppAndSystemHelper.resetSystemLocaleToEnUS import org.mozilla.fenix.helpers.AppAndSystemHelper.runWithSystemLocaleChanged import org.mozilla.fenix.helpers.AppAndSystemHelper.setSystemLocale import org.mozilla.fenix.helpers.DataGenerationHelper.setTextToClipBoard @@ -28,14 +26,14 @@ import org.mozilla.fenix.helpers.TestHelper.appContext import org.mozilla.fenix.helpers.TestHelper.exitMenu import org.mozilla.fenix.helpers.TestHelper.restartApp import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.EngineShortcut import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.navigationToolbar import org.mozilla.fenix.ui.robots.searchScreen import java.util.Locale -class SettingsSearchTest { - private lateinit var mockWebServer: MockWebServer +class SettingsSearchTest : TestSetup() { private lateinit var searchMockServer: MockWebServer private val defaultSearchEngineList = listOf( @@ -50,12 +48,8 @@ class SettingsSearchTest { ) { it.activity } @Before - fun setUp() { - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - + override fun setUp() { + super.setUp() searchMockServer = MockWebServer().apply { dispatcher = SearchDispatcher() start() @@ -63,9 +57,9 @@ class SettingsSearchTest { } @After - fun tearDown() { - mockWebServer.shutdown() - resetSystemLocaleToEnUS() + override fun tearDown() { + super.tearDown() + searchMockServer.shutdown() } // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2203333 diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsSitePermissionsTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsSitePermissionsTest.kt index 3f6b57929..4b3842fc6 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsSitePermissionsTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsSitePermissionsTest.kt @@ -7,17 +7,11 @@ package org.mozilla.fenix.ui import androidx.core.net.toUri import androidx.test.espresso.Espresso.pressBack import androidx.test.filters.SdkSuppress -import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.mediasession.MediaSession -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.customannotations.SmokeTest -import org.mozilla.fenix.ext.components -import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AppAndSystemHelper.grantSystemPermission import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.helpers.MatcherHelper.itemWithText @@ -25,6 +19,7 @@ import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset import org.mozilla.fenix.helpers.TestAssetHelper.getMutedVideoPageAsset import org.mozilla.fenix.helpers.TestAssetHelper.getVideoPageAsset import org.mozilla.fenix.helpers.TestHelper.exitMenu +import org.mozilla.fenix.helpers.TestSetup import org.mozilla.fenix.ui.robots.browserScreen import org.mozilla.fenix.ui.robots.clickPageObject import org.mozilla.fenix.ui.robots.homeScreen @@ -36,13 +31,11 @@ import org.mozilla.fenix.ui.robots.navigationToolbar * - the settings effects on the app behavior * */ -class SettingsSitePermissionsTest { +class SettingsSitePermissionsTest : TestSetup() { /* Test page created and handled by the Mozilla mobile test-eng team */ private val permissionsTestPage = "https://mozilla-mobile.github.io/testapp/v2.0/permissions" private val permissionsTestPageHost = "https://mozilla-mobile.github.io" private val testPageSubstring = "https://mozilla-mobile.github.io:443" - private lateinit var mockWebServer: MockWebServer - private lateinit var browserStore: BrowserStore @get:Rule val activityTestRule = HomeActivityTestRule( @@ -52,23 +45,6 @@ class SettingsSitePermissionsTest { isDeleteSitePermissionsEnabled = true, ) - @Before - fun setUp() { - // Initializing this as part of class construction, below the rule would throw a NPE - // So we are initializing this here instead of in all tests. - browserStore = activityTestRule.activity.components.core.store - - mockWebServer = MockWebServer().apply { - dispatcher = AndroidAssetDispatcher() - start() - } - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - // TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/246974 @Test fun sitePermissionsItemsTest() { From 065f7e70fc66f51faf5777ed6913540e8459f545 Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Fri, 23 Feb 2024 14:18:38 -0500 Subject: [PATCH 066/238] Bug 1882134 - Use jvmToolchain for setting target JVM version --- app/build.gradle | 5 ----- benchmark/build.gradle | 9 --------- build.gradle | 10 ++-------- mozilla-lint-rules/build.gradle | 3 --- 4 files changed, 2 insertions(+), 25 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 44a84cfff..ea48e5ac4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -219,11 +219,6 @@ android { } } - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - bundle { // Profiler issues require us to temporarily package native code compressed to // match the previous APK packaging. diff --git a/benchmark/build.gradle b/benchmark/build.gradle index 20825ec4e..8f13686ff 100644 --- a/benchmark/build.gradle +++ b/benchmark/build.gradle @@ -13,15 +13,6 @@ android { namespace 'org.mozilla.fenix.benchmark' compileSdk config.compileSdkVersion - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - kotlinOptions { - jvmTarget = "17" - } - defaultConfig { minSdk 23 targetSdk config.targetSdkVersion diff --git a/build.gradle b/build.gradle index bd002d017..53718ef05 100644 --- a/build.gradle +++ b/build.gradle @@ -170,7 +170,6 @@ allprojects { } tasks.withType(KotlinCompile).configureEach { - kotlinOptions.jvmTarget = "17" kotlinOptions.allWarningsAsErrors = true kotlinOptions.freeCompilerArgs += [ "-opt-in=kotlin.RequiresOptIn", "-Xjvm-default=all-compatibility" @@ -180,13 +179,8 @@ allprojects { subprojects { afterEvaluate { - if (it.hasProperty('android')) { - android { - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - } + kotlin { + jvmToolchain(config.jvmTargetCompatibility) } } } diff --git a/mozilla-lint-rules/build.gradle b/mozilla-lint-rules/build.gradle index 00fcdb7d3..70c330727 100644 --- a/mozilla-lint-rules/build.gradle +++ b/mozilla-lint-rules/build.gradle @@ -5,9 +5,6 @@ apply plugin: 'java-library' apply plugin: 'kotlin' -targetCompatibility = JavaVersion.VERSION_17 -sourceCompatibility = JavaVersion.VERSION_17 - repositories { if (project.hasProperty("centralRepo")) { maven { From 77b69aafd7a7c0b078a85e1b1fe03c211efc3935 Mon Sep 17 00:00:00 2001 From: AndiAJ Date: Tue, 27 Feb 2024 12:59:23 +0200 Subject: [PATCH 067/238] Bug 1882264 - Convert private variables to functions so they don't get initialised --- .../SettingsSubMenuOpenLinksInAppsRobot.kt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuOpenLinksInAppsRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuOpenLinksInAppsRobot.kt index e1ce8d7a4..f10d5db94 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuOpenLinksInAppsRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuOpenLinksInAppsRobot.kt @@ -25,7 +25,7 @@ class SettingsSubMenuOpenLinksInAppsRobot { fun verifyOpenLinksInAppsView(selectedOpenLinkInAppsOption: String) { assertUIObjectExists( - goBackButton, + goBackButton(), itemContainingText(getStringResource(R.string.preferences_open_links_in_apps)), itemContainingText(getStringResource(R.string.preferences_open_links_in_apps_always)), itemContainingText(getStringResource(R.string.preferences_open_links_in_apps_ask)), @@ -36,7 +36,7 @@ class SettingsSubMenuOpenLinksInAppsRobot { fun verifyPrivateOpenLinksInAppsView(selectedOpenLinkInAppsOption: String) { assertUIObjectExists( - goBackButton, + goBackButton(), itemContainingText(getStringResource(R.string.preferences_open_links_in_apps)), itemContainingText(getStringResource(R.string.preferences_open_links_in_apps_ask)), itemContainingText(getStringResource(R.string.preferences_open_links_in_apps_never)), @@ -54,26 +54,26 @@ class SettingsSubMenuOpenLinksInAppsRobot { fun clickOpenLinkInAppOption(openLinkInAppsOption: String) { when (openLinkInAppsOption) { - "Always" -> alwaysOption.click() - "Ask before opening" -> askBeforeOpeningOption.click() - "Never" -> neverOption.click() + "Always" -> alwaysOption().click() + "Ask before opening" -> askBeforeOpeningOption().click() + "Never" -> neverOption().click() } } class Transition { fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition { mDevice.waitForIdle() - goBackButton.click() + goBackButton().click() SettingsRobot().interact() return SettingsRobot.Transition() } } } -private val goBackButton = itemWithDescription("Navigate up") -private val alwaysOption = +private fun goBackButton() = itemWithDescription("Navigate up") +private fun alwaysOption() = itemContainingText(getStringResource(R.string.preferences_open_links_in_apps_always)) -private val askBeforeOpeningOption = +private fun askBeforeOpeningOption() = itemContainingText(getStringResource(R.string.preferences_open_links_in_apps_ask)) -private val neverOption = +private fun neverOption() = itemContainingText(getStringResource(R.string.preferences_open_links_in_apps_never)) From 5944b5b8bf322188f1c585d6b4e5495e8a77347a Mon Sep 17 00:00:00 2001 From: AndiAJ Date: Tue, 27 Feb 2024 13:09:43 +0200 Subject: [PATCH 068/238] Bug 1882264 - Add logs to SettingsSubMenuOpenLinksInAppsRobot --- .../robots/SettingsSubMenuOpenLinksInAppsRobot.kt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuOpenLinksInAppsRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuOpenLinksInAppsRobot.kt index f10d5db94..1ee72b2cd 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuOpenLinksInAppsRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuOpenLinksInAppsRobot.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.ui.robots +import android.util.Log import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.hasSibling @@ -11,6 +12,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import org.hamcrest.CoreMatchers.allOf import org.mozilla.fenix.R +import org.mozilla.fenix.helpers.Constants.TAG import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText @@ -44,26 +46,35 @@ class SettingsSubMenuOpenLinksInAppsRobot { verifySelectedOpenLinksInAppOption(selectedOpenLinkInAppsOption) } - fun verifySelectedOpenLinksInAppOption(openLinkInAppsOption: String) = + fun verifySelectedOpenLinksInAppOption(openLinkInAppsOption: String) { + Log.i(TAG, "verifySelectedOpenLinksInAppOption: Trying to verify that the $openLinkInAppsOption option is checked") onView( allOf( withId(R.id.radio_button), hasSibling(withText(openLinkInAppsOption)), ), ).check(matches(isChecked(true))) + Log.i(TAG, "verifySelectedOpenLinksInAppOption: Verified that the $openLinkInAppsOption option is checked") + } fun clickOpenLinkInAppOption(openLinkInAppsOption: String) { + Log.i(TAG, "clickOpenLinkInAppOption: Trying to click the $openLinkInAppsOption option") when (openLinkInAppsOption) { "Always" -> alwaysOption().click() "Ask before opening" -> askBeforeOpeningOption().click() "Never" -> neverOption().click() } + Log.i(TAG, "clickOpenLinkInAppOption: Clicked the $openLinkInAppsOption option") } class Transition { fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition { + Log.i(TAG, "goBack: Waiting for device to be idle") mDevice.waitForIdle() + Log.i(TAG, "goBack: Waited for device to be idle") + Log.i(TAG, "goBack: Trying to click the navigate up button") goBackButton().click() + Log.i(TAG, "goBack: Clicked the navigate up button") SettingsRobot().interact() return SettingsRobot.Transition() From 39f1f91d93f48796336929ea48792621832dad0d Mon Sep 17 00:00:00 2001 From: AndiAJ Date: Tue, 27 Feb 2024 13:32:01 +0200 Subject: [PATCH 069/238] Bug 1882272 - Remove redundant assertion functions from SettingsSubMenuPrivateBrowsingRobot --- .../SettingsSubMenuPrivateBrowsingRobot.kt | 79 +++++++------------ 1 file changed, 27 insertions(+), 52 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuPrivateBrowsingRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuPrivateBrowsingRobot.kt index 2ce205706..d2347f298 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuPrivateBrowsingRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuPrivateBrowsingRobot.kt @@ -9,18 +9,14 @@ import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.Visibility -import androidx.test.espresso.matcher.ViewMatchers.withChild 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.By.text import androidx.test.uiautomator.UiSelector import androidx.test.uiautomator.Until -import org.hamcrest.CoreMatchers import org.junit.Assert.assertTrue -import org.mozilla.fenix.R import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists import org.mozilla.fenix.helpers.MatcherHelper.checkedItemWithResId import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime @@ -35,17 +31,37 @@ import org.mozilla.fenix.helpers.isEnabled class SettingsSubMenuPrivateBrowsingRobot { - fun verifyNavigationToolBarHeader() = assertNavigationToolBarHeader() - - fun verifyOpenLinksInPrivateTab() = assertOpenLinksInPrivateTab() + fun verifyOpenLinksInPrivateTab() { + openLinksInPrivateTabSwitch() + .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + } - fun verifyAddPrivateBrowsingShortcutButton() = assertAddPrivateBrowsingShortcutButton() + fun verifyAddPrivateBrowsingShortcutButton() { + mDevice.wait( + Until.findObject(text("Add private browsing shortcut")), + waitingTime, + ) + addPrivateBrowsingShortcutButton() + .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + } - fun verifyOpenLinksInPrivateTabEnabled() = assertOpenLinksInPrivateTabEnabled() + fun verifyOpenLinksInPrivateTabEnabled() { + openLinksInPrivateTabSwitch().check(matches(isEnabled(true))) + } - fun verifyOpenLinksInPrivateTabOff() = assertOpenLinksInPrivateTabOff() + fun verifyOpenLinksInPrivateTabOff() { + assertUIObjectExists( + checkedItemWithResId("android:id/switch_widget", isChecked = true), + exists = false, + ) + openLinksInPrivateTabSwitch() + .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + } - fun verifyPrivateBrowsingShortcutIcon() = assertPrivateBrowsingShortcutIcon() + fun verifyPrivateBrowsingShortcutIcon() { + mDevice.wait(Until.findObject(text("Private $appName")), waitingTime) + assertTrue(mDevice.hasObject(text("Private $appName"))) + } fun clickPrivateModeScreenshotsSwitch() = screenshotsInPrivateModeSwitch().click() @@ -92,16 +108,6 @@ class SettingsSubMenuPrivateBrowsingRobot { } } -private fun assertNavigationToolBarHeader() { - onView( - CoreMatchers.allOf( - withId(R.id.navigationToolbar), - withChild(withText(R.string.preferences_private_browsing_options)), - ), - ) - .check((matches(withEffectiveVisibility(Visibility.VISIBLE)))) -} - private fun openLinksInPrivateTabSwitch() = onView(withText("Open links in a private tab")) @@ -119,34 +125,3 @@ private fun cancelShortcutAdditionButton() = mDevice.findObject(UiSelector().textContains("CANCEL")) private fun privateBrowsingShortcutIcon() = mDevice.findObject(text("Private $appName")) - -private fun assertAddPrivateBrowsingShortcutButton() { - mDevice.wait( - Until.findObject(text("Add private browsing shortcut")), - waitingTime, - ) - addPrivateBrowsingShortcutButton() - .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) -} - -private fun assertOpenLinksInPrivateTab() { - openLinksInPrivateTabSwitch() - .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) -} - -private fun assertOpenLinksInPrivateTabEnabled() = - openLinksInPrivateTabSwitch().check(matches(isEnabled(true))) - -private fun assertOpenLinksInPrivateTabOff() { - assertUIObjectExists( - checkedItemWithResId("android:id/switch_widget", isChecked = true), - exists = false, - ) - openLinksInPrivateTabSwitch() - .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) -} - -private fun assertPrivateBrowsingShortcutIcon() { - mDevice.wait(Until.findObject(text("Private $appName")), waitingTime) - assertTrue(mDevice.hasObject(text("Private $appName"))) -} From dbf5ec4fffd2ea45476ce52f3dc24dd8fff5036f Mon Sep 17 00:00:00 2001 From: AndiAJ Date: Tue, 27 Feb 2024 13:57:36 +0200 Subject: [PATCH 070/238] Bug 1882272 - Add logs to SettingsSubMenuPrivateBrowsingRobot --- .../SettingsSubMenuPrivateBrowsingRobot.kt | 50 +++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuPrivateBrowsingRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuPrivateBrowsingRobot.kt index d2347f298..62bde104e 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuPrivateBrowsingRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuPrivateBrowsingRobot.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix.ui.robots import android.os.Build +import android.util.Log import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions import androidx.test.espresso.assertion.ViewAssertions.matches @@ -17,6 +18,7 @@ import androidx.test.uiautomator.By.text import androidx.test.uiautomator.UiSelector import androidx.test.uiautomator.Until import org.junit.Assert.assertTrue +import org.mozilla.fenix.helpers.Constants.TAG import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists import org.mozilla.fenix.helpers.MatcherHelper.checkedItemWithResId import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime @@ -32,21 +34,29 @@ import org.mozilla.fenix.helpers.isEnabled class SettingsSubMenuPrivateBrowsingRobot { fun verifyOpenLinksInPrivateTab() { + Log.i(TAG, "verifyOpenLinksInPrivateTab: Trying to verify that the \"Open links in a private tab\" option is visible") openLinksInPrivateTabSwitch() .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + Log.i(TAG, "verifyOpenLinksInPrivateTab: Verified that the \"Open links in a private tab\" option is visible") } fun verifyAddPrivateBrowsingShortcutButton() { + Log.i(TAG, "verifyAddPrivateBrowsingShortcutButton: Waiting for $waitingTime ms until finding the \"Add private browsing shortcut\" button") mDevice.wait( Until.findObject(text("Add private browsing shortcut")), waitingTime, ) + Log.i(TAG, "verifyAddPrivateBrowsingShortcutButton: Waited for $waitingTime ms until the \"Add private browsing shortcut\" button was found") + Log.i(TAG, "verifyAddPrivateBrowsingShortcutButton: Trying to verify that the \"Add private browsing shortcut\" button is visible") addPrivateBrowsingShortcutButton() .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + Log.i(TAG, "verifyAddPrivateBrowsingShortcutButton: Verified that the \"Add private browsing shortcut\" button is visible") } fun verifyOpenLinksInPrivateTabEnabled() { + Log.i(TAG, "verifyOpenLinksInPrivateTabEnabled: Trying to verify that the \"Open links in a private tab\" toggle is enabled") openLinksInPrivateTabSwitch().check(matches(isEnabled(true))) + Log.i(TAG, "verifyOpenLinksInPrivateTabEnabled: Verified that the \"Open links in a private tab\" toggle is enabled") } fun verifyOpenLinksInPrivateTabOff() { @@ -54,53 +64,87 @@ class SettingsSubMenuPrivateBrowsingRobot { checkedItemWithResId("android:id/switch_widget", isChecked = true), exists = false, ) + Log.i(TAG, "verifyOpenLinksInPrivateTabOff: Trying to verify that the \"Open links in a private tab\" toggle is visible") openLinksInPrivateTabSwitch() .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + Log.i(TAG, "verifyOpenLinksInPrivateTabOff: Verified that the \"Open links in a private tab\" toggle is visible") } fun verifyPrivateBrowsingShortcutIcon() { + Log.i(TAG, "verifyPrivateBrowsingShortcutIcon: Waiting for $waitingTime ms until finding the \"Private $appName\" shortcut icon") mDevice.wait(Until.findObject(text("Private $appName")), waitingTime) - assertTrue(mDevice.hasObject(text("Private $appName"))) + Log.i(TAG, "verifyPrivateBrowsingShortcutIcon: Waited for $waitingTime ms until the \"Private $appName\" shortcut icon was found") + Log.i(TAG, "verifyPrivateBrowsingShortcutIcon: Trying to verify the \"Private $appName\" shortcut icon") + assertTrue("\"Private $appName\" shortcut icon wasn't verified", mDevice.hasObject(text("Private $appName"))) + Log.i(TAG, "verifyPrivateBrowsingShortcutIcon: Verified the \"Private $appName\" shortcut icon") } - fun clickPrivateModeScreenshotsSwitch() = screenshotsInPrivateModeSwitch().click() + fun clickPrivateModeScreenshotsSwitch() { + Log.i(TAG, "clickPrivateModeScreenshotsSwitch: Trying to click the \"Allow screenshots in private browsing\" toggle") + screenshotsInPrivateModeSwitch().click() + Log.i(TAG, "clickPrivateModeScreenshotsSwitch: Clicked the \"Allow screenshots in private browsing\" toggle") + } - fun clickOpenLinksInPrivateTabSwitch() = openLinksInPrivateTabSwitch().click() + fun clickOpenLinksInPrivateTabSwitch() { + Log.i(TAG, "clickOpenLinksInPrivateTabSwitch: Trying to click the \"Open links in a private tab\" toggle") + openLinksInPrivateTabSwitch().click() + Log.i(TAG, "clickOpenLinksInPrivateTabSwitch: Clicked the \"Open links in a private tab\" toggle") + } fun cancelPrivateShortcutAddition() { + Log.i(TAG, "cancelPrivateShortcutAddition: Waiting for $waitingTime ms until finding the \"Add private browsing shortcut\" button") mDevice.wait( Until.findObject(text("Add private browsing shortcut")), waitingTime, ) + Log.i(TAG, "cancelPrivateShortcutAddition: Waited for $waitingTime ms until the \"Add private browsing shortcut\" button was found") + Log.i(TAG, "cancelPrivateShortcutAddition: Trying to click the \"Add private browsing shortcut\" button") addPrivateBrowsingShortcutButton().click() + Log.i(TAG, "cancelPrivateShortcutAddition: Clicked the \"Add private browsing shortcut\" button") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Log.i(TAG, "cancelPrivateShortcutAddition: Waiting for $waitingTime ms until finding the \"Cancel\" button") mDevice.wait(Until.findObject(By.textContains("CANCEL")), waitingTime) + Log.i(TAG, "cancelPrivateShortcutAddition: Waited for $waitingTime ms until the \"Cancel\" button was found") + Log.i(TAG, "cancelPrivateShortcutAddition: Trying to click the \"Cancel\" button") cancelShortcutAdditionButton().click() + Log.i(TAG, "cancelPrivateShortcutAddition: Clicked the \"Cancel\" button") } } fun addPrivateShortcutToHomescreen() { + Log.i(TAG, "addPrivateShortcutToHomescreen: Waiting for $waitingTime ms until finding the \"Add private browsing shortcut\" button") mDevice.wait( Until.findObject(text("Add private browsing shortcut")), waitingTime, ) + Log.i(TAG, "addPrivateShortcutToHomescreen: Waited for $waitingTime ms until the \"Add private browsing shortcut\" button was found") + Log.i(TAG, "addPrivateShortcutToHomescreen: Trying to click the \"Add private browsing shortcut\" button") addPrivateBrowsingShortcutButton().click() + Log.i(TAG, "addPrivateShortcutToHomescreen: Clicked the \"Add private browsing shortcut\" button") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Log.i(TAG, "addPrivateShortcutToHomescreen: Waiting for $waitingTime ms until finding the \"Add automatically\" button") mDevice.wait(Until.findObject(By.textContains("add automatically")), waitingTime) + Log.i(TAG, "addPrivateShortcutToHomescreen: Waited for $waitingTime ms until the \"Add automatically\" button was found") + Log.i(TAG, "addPrivateShortcutToHomescreen: Trying to click the \"Add automatically\" button") addAutomaticallyButton().click() + Log.i(TAG, "addPrivateShortcutToHomescreen: Clicked the \"Add automatically\" button") } } class Transition { fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition { + Log.i(TAG, "goBack: Trying to click the navigate up button") goBackButton().perform(ViewActions.click()) + Log.i(TAG, "goBack: Clicked the navigate up button") SettingsRobot().interact() return SettingsRobot.Transition() } fun openPrivateBrowsingShortcut(interact: SearchRobot.() -> Unit): SearchRobot.Transition { + Log.i(TAG, "openPrivateBrowsingShortcut: Trying to click the \"Private $appName\" shortcut icon") privateBrowsingShortcutIcon().click() + Log.i(TAG, "openPrivateBrowsingShortcut: Clicked the \"Private $appName\" shortcut icon") SearchRobot().interact() return SearchRobot.Transition() From 000f5e2623ff6595ec2ec63150cd8e53deea68ed Mon Sep 17 00:00:00 2001 From: t-p-white Date: Mon, 26 Feb 2024 10:18:44 +0000 Subject: [PATCH 071/238] Bug 1879512 - Added experimental onboarding images for Nimbus --- app/lint-baseline.xml | 10 + .../drawable/ic_onboarding_key_features.xml | 423 ++++++++++++++++++ .../ic_onboarding_key_features_icons_only.xml | 307 +++++++++++++ app/src/main/res/raw/keep.xml | 2 +- 4 files changed, 741 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable/ic_onboarding_key_features.xml create mode 100644 app/src/main/res/drawable/ic_onboarding_key_features_icons_only.xml diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml index b4ce2b38e..c9eb994da 100644 --- a/app/lint-baseline.xml +++ b/app/lint-baseline.xml @@ -1101,6 +1101,16 @@ column="1"/> + + + + + + + + diff --git a/app/src/main/res/drawable/ic_onboarding_key_features.xml b/app/src/main/res/drawable/ic_onboarding_key_features.xml new file mode 100644 index 000000000..c5eeaffcf --- /dev/null +++ b/app/src/main/res/drawable/ic_onboarding_key_features.xml @@ -0,0 +1,423 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_onboarding_key_features_icons_only.xml b/app/src/main/res/drawable/ic_onboarding_key_features_icons_only.xml new file mode 100644 index 000000000..820fd340e --- /dev/null +++ b/app/src/main/res/drawable/ic_onboarding_key_features_icons_only.xml @@ -0,0 +1,307 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/raw/keep.xml b/app/src/main/res/raw/keep.xml index c19db331d..9a17666f9 100644 --- a/app/src/main/res/raw/keep.xml +++ b/app/src/main/res/raw/keep.xml @@ -1,3 +1,3 @@ + tools:keep="@drawable/onboarding_ctd_default_browser,@drawable-night/onboarding_ctd_default_browser,@drawable/onboarding_ctd_sync,@drawable/onboarding_ctd_notification,@drawable/ic_onboarding_key_features.xml,@drawable/ic_onboarding_key_features_icons_only.xml" /> From d38cd687f3070fd38aa13f822c9ae21b37e6e6b9 Mon Sep 17 00:00:00 2001 From: Aaron Train Date: Mon, 26 Feb 2024 15:51:38 -0500 Subject: [PATCH 072/238] Bug 1882162 - remove JQuery slim test asset --- app/src/androidTest/assets/pages/jquery-3.4.1.slim.min.js | 2 -- app/src/androidTest/assets/pages/refresh.html | 1 - 2 files changed, 3 deletions(-) delete mode 100644 app/src/androidTest/assets/pages/jquery-3.4.1.slim.min.js diff --git a/app/src/androidTest/assets/pages/jquery-3.4.1.slim.min.js b/app/src/androidTest/assets/pages/jquery-3.4.1.slim.min.js deleted file mode 100644 index af151cfe3..000000000 --- a/app/src/androidTest/assets/pages/jquery-3.4.1.slim.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.4.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/parseXML,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-event/ajax,-effects,-effects/Tween,-effects/animatedSelector | (c) JS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(g,e){"use strict";var t=[],v=g.document,r=Object.getPrototypeOf,s=t.slice,y=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,m=n.hasOwnProperty,a=m.toString,l=a.call(Object),b={},x=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},w=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function C(e,t,n){var r,i,o=(n=n||v).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function T(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/parseXML,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-event/ajax,-effects,-effects/Tween,-effects/animatedSelector",E=function(e,t){return new E.fn.init(e,t)},d=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function p(e){var t=!!e&&"length"in e&&e.length,n=T(e);return!x(e)&&!w(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+R+")"+R+"*"),U=new RegExp(R+"|>"),V=new RegExp(W),X=new RegExp("^"+B+"$"),Q={ID:new RegExp("^#("+B+")"),CLASS:new RegExp("^\\.("+B+")"),TAG:new RegExp("^("+B+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+R+"*(even|odd|(([+-]|)(\\d*)n|)"+R+"*(?:([+-]|)"+R+"*(\\d+)|))"+R+"*\\)|)","i"),bool:new RegExp("^(?:"+I+")$","i"),needsContext:new RegExp("^"+R+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+R+"*((?:-\\d)?\\d*)"+R+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,G=/^(?:input|select|textarea|button)$/i,K=/^h\d$/i,J=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+R+"?|("+R+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){C()},ae=xe(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{O.apply(t=P.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){O={apply:t.length?function(e,t){q.apply(e,P.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,d=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==d&&9!==d&&11!==d)return n;if(!r&&((e?e.ownerDocument||e:m)!==T&&C(e),e=e||T,E)){if(11!==d&&(u=Z.exec(t)))if(i=u[1]){if(9===d){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return O.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&p.getElementsByClassName&&e.getElementsByClassName)return O.apply(n,e.getElementsByClassName(i)),n}if(p.qsa&&!S[t+" "]&&(!v||!v.test(t))&&(1!==d||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===d&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=N),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+be(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return O.apply(n,f.querySelectorAll(c)),n}catch(e){S(t,!0)}finally{s===N&&e.removeAttribute("id")}}}return g(t.replace(F,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>x.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[N]=!0,e}function ce(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)x.attrHandle[n[r]]=t}function de(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function pe(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in p=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},C=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==T&&9===r.nodeType&&r.documentElement&&(a=(T=r).documentElement,E=!i(T),m!==T&&(n=T.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),p.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),p.getElementsByTagName=ce(function(e){return e.appendChild(T.createComment("")),!e.getElementsByTagName("*").length}),p.getElementsByClassName=J.test(T.getElementsByClassName),p.getById=ce(function(e){return a.appendChild(e).id=N,!T.getElementsByName||!T.getElementsByName(N).length}),p.getById?(x.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},x.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(x.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},x.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),x.find.TAG=p.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):p.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},x.find.CLASS=p.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(p.qsa=J.test(T.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+R+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+R+"*(?:value|"+I+")"),e.querySelectorAll("[id~="+N+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+N+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=T.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+R+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(p.matchesSelector=J.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){p.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",W)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=J.test(a.compareDocumentPosition),y=t||J.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!p.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument===m&&y(m,e)?-1:t===T||t.ownerDocument===m&&y(m,t)?1:u?H(u,e)-H(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===T?-1:t===T?1:i?-1:o?1:u?H(u,e)-H(u,t):0;if(i===o)return de(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?de(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),T},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==T&&C(e),p.matchesSelector&&E&&!S[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||p.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){S(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&V.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=d[e+" "];return t||(t=new RegExp("(^|"+R+")"+e+"("+R+"|$)"))&&d(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function L(e,n,r){return x(n)?E.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?E.grep(e,function(e){return e===n!==r}):"string"!=typeof n?E.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(E.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof E?t[0]:t,E.merge(this,E.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:v,!0)),D.test(r[1])&&E.isPlainObject(t))for(r in t)x(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=v.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):x(e)?void 0!==n.ready?n.ready(e):e(E):E.makeArray(e,this)}).prototype=E.fn,j=E(v);var O=/^(?:parents|prev(?:Until|All))/,P={children:!0,contents:!0,next:!0,prev:!0};function H(e,t){while((e=e[t])&&1!==e.nodeType);return e}E.fn.extend({has:function(e){var t=E(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,pe=/^$|^module$|\/(?:java|ecma)script/i,he={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ge(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&S(e,t)?E.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;nx",b.noCloneChecked=!!ye.cloneNode(!0).lastChild.defaultValue;var we=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Te=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function Ne(){return!1}function Ae(e,t){return e===function(){try{return v.activeElement}catch(e){}}()==("focus"===t)}function ke(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)ke(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Ne;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return E().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=E.guid++)),e.each(function(){E.event.add(this,t,i,r,n)})}function Se(e,i,o){o?(G.set(e,i,!1),E.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=G.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(E.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),G.set(this,i,r),t=o(this,i),this[i](),r!==(n=G.get(this,i))||t?G.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(G.set(this,i,{value:E.event.trigger(E.extend(r[0],E.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===G.get(e,i)&&E.event.add(e,i,Ee)}E.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,d,p,h,g,v=G.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&E.find.matchesSelector(ie,i),n.guid||(n.guid=E.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof E&&E.event.triggered!==e.type?E.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(I)||[""]).length;while(l--)p=g=(s=Te.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),p&&(f=E.event.special[p]||{},p=(i?f.delegateType:f.bindType)||p,f=E.event.special[p]||{},c=E.extend({type:p,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&E.expr.match.needsContext.test(i),namespace:h.join(".")},o),(d=u[p])||((d=u[p]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(p,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?d.splice(d.delegateCount++,0,c):d.push(c),E.event.global[p]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,d,p,h,g,v=G.hasData(e)&&G.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(I)||[""]).length;while(l--)if(p=g=(s=Te.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),p){f=E.event.special[p]||{},d=u[p=(r?f.delegateType:f.bindType)||p]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=d.length;while(o--)c=d[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(d.splice(o,1),c.selector&&d.delegateCount--,f.remove&&f.remove.call(e,c));a&&!d.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||E.removeEvent(e,p,v.handle),delete u[p])}else for(p in u)E.event.remove(e,p+t[l],n,r,!0);E.isEmptyObject(u)&&G.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=E.event.fix(e),u=new Array(arguments.length),l=(G.get(this,"events")||{})[s.type]||[],c=E.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,Le=/\s*$/g;function Oe(e,t){return S(e,"table")&&S(11!==t.nodeType?t:t.firstChild,"tr")&&E(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Ie(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(G.hasData(e)&&(o=G.access(e),a=G.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(b.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||E.isXMLDoc(e)))for(a=ge(c),r=0,i=(o=ge(e)).length;r
",2===pt.childNodes.length),E.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(b.createHTMLDocument?((r=(t=v.implementation.createHTMLDocument("")).createElement("base")).href=v.location.href,t.head.appendChild(r)):t=v),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&E(o).remove(),E.merge([],i.childNodes)));var r,i,o},E.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=E.css(e,"position"),c=E(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=E.css(e,"top"),u=E.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),x(t)&&(t=t.call(e,n,E.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},E.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){E.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===E.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===E.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=E(e).offset()).top+=E.css(e,"borderTopWidth",!0),i.left+=E.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-E.css(r,"marginTop",!0),left:t.left-i.left-E.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===E.css(e,"position"))e=e.offsetParent;return e||ie})}}),E.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;E.fn[t]=function(e){return z(this,function(e,t,n){var r;if(w(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),E.each(["top","left"],function(e,n){E.cssHooks[n]=ze(b.pixelPosition,function(e,t){if(t)return t=Fe(e,n),Me.test(t)?E(e).position()[n]+"px":t})}),E.each({Height:"height",Width:"width"},function(a,s){E.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){E.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return z(this,function(e,t,n){var r;return w(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?E.css(e,t,i):E.style(e,t,n,i)},s,n?e:void 0,n)}})}),E.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){E.fn[n]=function(e,t){return 0 -