Bug 1853469 - GeckoView Mock Remote Settings for Automated Testing r=geckoview-reviewers,owlish,calu

This bug mocks the required information that is not available in
automation due to remote settings not being available.

This patch includes a way to mock a translations offer or the conditions
of an expected translation. Additionally, it will send synthetic data
on some endpoints when `browser.translations.geckoview.enableAllTestMocks` is
set and in automation.

Differential Revision: https://phabricator.services.mozilla.com/D192397
This commit is contained in:
Olivia Hall 2023-11-06 15:42:05 +00:00
Родитель c7f4eee33a
Коммит 1886b3e702
8 изменённых файлов: 278 добавлений и 103 удалений

Просмотреть файл

@ -150,6 +150,8 @@ pref("browser.tabs.remote.autostart", true);
// Bug 1809922 to enable translations
#ifdef NIGHTLY_BUILD
pref("browser.translations.enable", true);
// Used for mocking data for GeckoView Translations tests, should use in addition with an automation check.
pref("browser.translations.geckoview.enableAllTestMocks", false);
#endif
// SSL error page behaviour (bug 437372)

Просмотреть файл

@ -77,6 +77,12 @@ const APIS = {
TriggerCookieBannerHandled({ tab }) {
return browser.test.triggerCookieBannerHandled(tab.id);
},
TriggerTranslationsOffer({ tab }) {
return browser.test.triggerTranslationsOffer(tab.id);
},
TriggerLanguageStateChange({ tab, languageState }) {
return browser.test.triggerLanguageStateChange(tab.id, languageState);
},
};
port.onMessage.addListener(async message => {

Просмотреть файл

@ -223,6 +223,27 @@ this.test = class extends ExtensionAPI {
name: "CookieBanner::HandledBanner",
});
},
async triggerTranslationsOffer(tabId) {
const browser = context.extension.tabManager.get(tabId).browser;
const { CustomEvent } = browser.ownerGlobal;
return browser.dispatchEvent(
new CustomEvent("TranslationsParent:OfferTranslation", {
bubbles: true,
})
);
},
async triggerLanguageStateChange(tabId, languageState) {
const browser = context.extension.tabManager.get(tabId).browser;
const { CustomEvent } = browser.ownerGlobal;
return browser.dispatchEvent(
new CustomEvent("TranslationsParent:LanguageState", {
bubbles: true,
detail: languageState,
})
);
},
},
};
}

Просмотреть файл

@ -258,6 +258,38 @@
"name": "tabId"
}
]
},
{
"name": "triggerTranslationsOffer",
"type": "function",
"async": true,
"description": "Simulates offering a translation.",
"parameters": [
{
"type": "number",
"name": "tabId"
}
]
},
{
"name": "triggerLanguageStateChange",
"type": "function",
"async": true,
"description": "Simulates expecting a translation.",
"parameters": [
{
"type": "number",
"name": "tabId"
},
{
"name": "languageState",
"type": "object",
"properties": {},
"additionalProperties": { "type": "any" }
}
]
}
]
}

Просмотреть файл

@ -278,6 +278,11 @@ open class BaseSessionTest(
fun GeckoSession.triggerCookieBannerHandled() =
sessionRule.triggerCookieBannerHandled(this)
fun GeckoSession.triggerTranslationsOffer() =
sessionRule.triggerTranslationsOffer(this)
fun GeckoSession.triggerLanguageStateChange(languageState: JSONObject) =
sessionRule.triggerLanguageStateChange(this, languageState)
var GeckoSession.active: Boolean
get() = sessionRule.getActive(this)
set(value) = setActive(value)

Просмотреть файл

@ -7,9 +7,12 @@ package org.mozilla.geckoview.test
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import junit.framework.TestCase.assertTrue
import org.json.JSONObject
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.TranslationsController
import org.mozilla.geckoview.TranslationsController.Language
@ -29,45 +32,72 @@ class TranslationsTest : BaseSessionTest() {
mapOf(
"browser.translations.enable" to true,
"browser.translations.automaticallyPopup" to true,
"intl.accept_languages" to "fr-CA, it, de",
"intl.accept_languages" to "en",
"browser.translations.geckoview.enableAllTestMocks" to true,
),
)
}
private var expectedLanguages: List<TranslationsController.Language> = listOf(
Language("bg", "Bulgarian"),
Language("nl", "Dutch"),
@After
fun cleanup() {
sessionRule.setPrefsUntilTestEnd(
mapOf(
"browser.translations.automaticallyPopup" to false,
"browser.translations.geckoview.enableAllTestMocks" to false,
),
)
}
private var mockedExpectedLanguages: List<TranslationsController.Language> = listOf(
Language("en", "English"),
Language("fr", "French"),
Language("de", "German"),
Language("it", "Italian"),
Language("pl", "Polish"),
Language("pt", "Portuguese"),
Language("es", "Spanish"),
)
@Test
fun onExpectedTranslateDelegateTest() {
sessionRule.delegateDuringNextWait(object : Delegate {
@AssertCalled(count = 0)
override fun onExpectedTranslate(session: GeckoSession) {
}
})
mainSession.loadTestPath(TRANSLATIONS_ES)
mainSession.waitForPageStop()
// ToDo: bug 1853469 is to research getting automation testing working fully.
val handled = GeckoResult<Void>()
sessionRule.delegateUntilTestEnd(object : Delegate {
@AssertCalled(count = 1)
override fun onExpectedTranslate(session: GeckoSession) {
handled.complete(null)
}
})
var expectedTranslateEvent = JSONObject(
"""
{
"detectedLanguages": {
"userLangTag": "en",
"isDocLangTagSupported": true,
"docLangTag": "es"
},
"requestedTranslationPair": null,
"error": null,
"isEngineReady": false
}
""".trimIndent(),
)
mainSession.triggerLanguageStateChange(expectedTranslateEvent)
sessionRule.waitForResult(handled)
}
@Test
fun onOfferTranslateDelegateTest() {
sessionRule.delegateDuringNextWait(object : Delegate {
@AssertCalled(count = 0)
override fun onOfferTranslate(session: GeckoSession) {
}
})
mainSession.loadTestPath(TRANSLATIONS_ES)
mainSession.waitForPageStop()
// ToDo: bug 1853469 is to research getting onOfferTranslate to work in automation.
val handled = GeckoResult<Void>()
sessionRule.delegateUntilTestEnd(object : Delegate {
@AssertCalled(count = 1)
override fun onOfferTranslate(session: GeckoSession) {
handled.complete(null)
}
})
mainSession.triggerTranslationsOffer()
sessionRule.waitForResult(handled)
}
@Test
@ -78,35 +108,61 @@ class TranslationsTest : BaseSessionTest() {
session: GeckoSession,
translationState: TranslationState?,
) {
assertTrue(
"Translations correctly does not have an engine ready.",
translationState?.isEngineReady == false,
)
assertTrue("Translations correctly does not have a requested pair. ", translationState?.requestedTranslationPair == null)
}
})
mainSession.loadTestPath(TRANSLATIONS_ES)
mainSession.waitForPageStop()
}
// Simpler translation test that doesn't test delegate state.
// Tests en -> es
@Test
fun simpleTranslateTest() {
mainSession.loadTestPath(TRANSLATIONS_EN)
mainSession.waitForPageStop()
val translate = sessionRule.session.sessionTranslation!!.translate("en", "es", null)
try {
sessionRule.waitForResult(translate)
assertTrue("Translate should complete", true)
} catch (e: Exception) {
assertTrue("Should not have an exception while translating.", false)
}
}
// More comprehensive translation test that also tests delegate state.
// Tests es -> en
@Test
fun translateTest() {
var delegateCalled = 0
sessionRule.delegateUntilTestEnd(object : Delegate {
@AssertCalled(count = 1)
@AssertCalled(count = 3)
override fun onTranslationStateChange(
session: GeckoSession,
translationState: TranslationState?,
) {
assertTrue(
"Translations correctly does not have an engine ready. ",
translationState?.isEngineReady == false,
)
assertTrue("Translations correctly does not have a requested pair. ", translationState?.requestedTranslationPair == null)
delegateCalled++
// Before page load
if (delegateCalled == 1) {
assertTrue(
"Translations correctly does not have a requested pair.",
translationState?.requestedTranslationPair == null,
)
}
// Page load
if (delegateCalled == 2) {
assertTrue("Translations correctly has detected a page language. ", translationState?.detectedLanguages?.docLangTag == "es")
}
// Translate
if (delegateCalled == 3) {
assertTrue("Translations correctly has set a translation pair from language. ", translationState?.requestedTranslationPair?.fromLanguage == "es")
assertTrue("Translations correctly has set a translation pair to language. ", translationState?.requestedTranslationPair?.toLanguage == "en")
}
}
})
mainSession.loadTestPath(TRANSLATIONS_ES)
mainSession.waitForPageStop()
val translate = mainSession.sessionTranslation!!.translate("es", "en", null)
val translate = sessionRule.session.sessionTranslation!!.translate("es", "en", null)
try {
sessionRule.waitForResult(translate)
assertTrue("Should be able to translate.", true)
@ -117,23 +173,9 @@ class TranslationsTest : BaseSessionTest() {
@Test
fun restoreOriginalPageLanguageTest() {
sessionRule.delegateUntilTestEnd(object : Delegate {
@AssertCalled(count = 2)
override fun onTranslationStateChange(
session: GeckoSession,
translationState: TranslationState?,
) {
assertTrue(
"Translations correctly does not have an engine ready. ",
translationState?.isEngineReady == false,
)
assertTrue("Translations correctly does not have a requested pair. ", translationState?.requestedTranslationPair == null)
}
})
mainSession.loadTestPath(TRANSLATIONS_ES)
mainSession.waitForPageStop()
val restore = mainSession.sessionTranslation!!.restoreOriginalPage()
val restore = sessionRule.session.sessionTranslation!!.restoreOriginalPage()
try {
sessionRule.waitForResult(restore)
assertTrue("Should be able to restore.", true)
@ -146,7 +188,6 @@ class TranslationsTest : BaseSessionTest() {
fun testTranslationOptions() {
var options = TranslationOptions.Builder().downloadModel(true).build()
assertTrue("TranslationOptions builder options work as expected.", options.downloadModel)
// ToDo: bug 1853055 will develop this further.
}
@Test
@ -157,12 +198,21 @@ class TranslationsTest : BaseSessionTest() {
"The translations engine is correctly reporting as supported.",
sessionRule.waitForResult(isSupportedResult),
)
// ToDo: Bug 1853469
// Setting "browser.translations.simulateUnsupportedEngine" to true did not return false.
}
@Test
fun testIsTranslationsEngineNotSupported() {
sessionRule.setPrefsUntilTestEnd(mapOf("browser.translations.simulateUnsupportedEngine" to true))
val isSupportedResult = TranslationsController.RuntimeTranslation.isTranslationsEngineSupported()
assertTrue(
"The translations engine is correctly reporting as not supported.",
sessionRule.waitForResult(isSupportedResult) == false,
)
}
@Test
fun testGetPreferredLanguage() {
sessionRule.setPrefsUntilTestEnd(mapOf("intl.accept_languages" to "fr-CA, it, de"))
val preferredLanguages = TranslationsController.RuntimeTranslation.preferredLanguages()
sessionRule.waitForResult(preferredLanguages).let { languages ->
assertTrue(
@ -184,7 +234,6 @@ class TranslationsTest : BaseSessionTest() {
@Test
fun testManageLanguageModel() {
// Will revisit during or after Bug 1853469, might also not be advisable to test downloading/deleting full models in automation.
val options = ModelManagementOptions.Builder()
.languageToManage("en")
.operation(TranslationsController.RuntimeTranslation.DOWNLOAD)
@ -196,70 +245,64 @@ class TranslationsTest : BaseSessionTest() {
@Test
fun testListSupportedLanguages() {
val translationDropdowns = TranslationsController.RuntimeTranslation.listSupportedLanguages()
// Bug 1853469 models are not listing in automation, so we do not have information on what models are enabled.
try {
sessionRule.waitForResult(translationDropdowns)
assertTrue("Should not be able to list supported languages.", false)
assertTrue("Should be able to list supported languages.", true)
} catch (e: Exception) {
assertTrue("Should have an exception.", true)
assertTrue("Should not have an exception.", false)
}
// Enable after ToDo: Bug 1853469
// var fromPresent = true
// var toPresent = true
// sessionRule.waitForResult(translationDropdowns).let { dropdowns ->
// // It is okay and expected that additional languages will join these lists over time.
// // Test is checking for minimum options are present.
// for (expected in expectedLanguages) {
// if (!dropdowns.fromLanguages!!.contains(expected)) {
// assertTrue("Language $expected.localizedDisplayName was not in from list", false)
// fromPresent = false
// }
// if (!dropdowns.toLanguages!!.contains(expected)) {
// assertTrue("Language $expected.localizedDisplayName was not in to list", false)
// toPresent = false
// }
// }
// }
// assertTrue(
// "All primary from languages are present.",
// fromPresent,
// )
// assertTrue(
// "All primary to languages are present.",
// toPresent,
// )
var fromPresent = true
var toPresent = true
sessionRule.waitForResult(translationDropdowns).let { dropdowns ->
// Test is checking for minimum options are present based on mocked expectations.
for (expected in mockedExpectedLanguages) {
if (!dropdowns.fromLanguages!!.contains(expected)) {
assertTrue("Language $expected was not in from list.", false)
fromPresent = false
}
if (!dropdowns.toLanguages!!.contains(expected)) {
assertTrue("Language $expected was not in to list.", false)
toPresent = false
}
}
}
assertTrue(
"All primary from languages are present.",
fromPresent,
)
assertTrue(
"All primary to languages are present.",
toPresent,
)
}
@Test
fun testListModelDownloadStates() {
var modelStatesResult = TranslationsController.RuntimeTranslation.listModelDownloadStates()
// ToDo: Bug 1853469 models not listing in automation, so we do not have information on the state.
try {
sessionRule.waitForResult(modelStatesResult)
assertTrue("Should not be able to list models.", false)
assertTrue("Should not be able to list models.", true)
} catch (e: Exception) {
assertTrue("Should have an exception.", true)
assertTrue("Should not have an exception.", false)
}
// Enable after ToDo: Bug 1853469
// sessionRule.waitForResult(modelStatesResult).let { models ->
// assertTrue(
// "Received information on the state of the models.",
// models.size >= expectedLanguages.size,
// )
// assertTrue(
// "Received information on the size in bytes of the first returned model.",
// models[0].size > 0,
// )
// assertTrue(
// "Received information on the language of the first returned model.",
// models[0].language != null,
// )
// assertTrue(
// "Received information on the download state of the first returned model",
// models[0].isDownloaded,
// )
// }
sessionRule.waitForResult(modelStatesResult).let { models ->
assertTrue(
"Received information on the state of the models.",
models.size >= mockedExpectedLanguages.size - 1,
)
assertTrue(
"Received information on the size in bytes of the first returned model.",
models[0].size > 0,
)
assertTrue(
"Received information on the language of the first returned model.",
models[0].language != null,
)
assertTrue(
"Received information on the download state of the first returned model",
!models[0].isDownloaded,
)
}
}
}

Просмотреть файл

@ -2499,6 +2499,20 @@ public class GeckoSessionTestRule implements TestRule {
webExtensionApiCall(session, "TriggerCookieBannerHandled", null);
}
public void triggerTranslationsOffer(final @NonNull GeckoSession session) {
webExtensionApiCall(session, "TriggerTranslationsOffer", null);
}
public void triggerLanguageStateChange(
final @NonNull GeckoSession session, final @NonNull JSONObject languageState) {
webExtensionApiCall(
session,
"TriggerLanguageStateChange",
args -> {
args.put("languageState", languageState);
});
}
private Object waitForMessage(final WebExtension.Port port, final String id) {
mPendingResponses.add(port, id);
UiThreadUtils.waitForCondition(() -> mPendingMessages.containsKey(id), mTimeoutMillis);

Просмотреть файл

@ -180,6 +180,31 @@ export const GeckoViewTranslationsSettings = {
break;
}
case "GeckoView:Translations:TranslationInformation": {
if (
Cu.isInAutomation &&
Services.prefs.getBoolPref(
"browser.translations.geckoview.enableAllTestMocks",
false
)
) {
const mockResult = {
languagePairs: [
{ fromLang: "en", toLang: "es" },
{ fromLang: "es", toLang: "en" },
],
fromLanguages: [
{ langTag: "en", displayName: "English" },
{ langTag: "es", displayName: "Spanish" },
],
toLanguages: [
{ langTag: "en", displayName: "English" },
{ langTag: "es", displayName: "Spanish" },
],
};
aCallback.onSuccess(mockResult);
return;
}
lazy.TranslationsParent.getSupportedLanguages().then(
function (value) {
aCallback.onSuccess(value);
@ -194,6 +219,33 @@ export const GeckoViewTranslationsSettings = {
break;
}
case "GeckoView:Translations:ModelInformation": {
if (
Cu.isInAutomation &&
Services.prefs.getBoolPref(
"browser.translations.geckoview.enableAllTestMocks",
false
)
) {
const mockResult = {
models: [
{
langTag: "es",
displayName: "Spanish",
isDownloaded: false,
size: 12345,
},
{
langTag: "de",
displayName: "German",
isDownloaded: false,
size: 12345,
},
],
};
aCallback.onSuccess(mockResult);
return;
}
// Helper function to process remote server records size and download state for GV use
async function _processLanguageModelData(language, remoteRecords) {
// Aggregate size of downloads, e.g., one language has many model binary files