зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
c7f4eee33a
Коммит
1886b3e702
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче