/** *Opensanewbrowserwindowandreturnsitasthecurrentlyfocusedwindow. * *@returns{Promise<Window>}
*/
async function openNewFocusedBrowserWindow() { // Avoid BrowserTestUtils.openNewBrowserWindow() here because it has been flaky // and has caused timeouts in multi-window translations tests in CI, particularly // when address sanitizer (asan) is enabled. const win = OpenBrowserWindow();
/** *Getallelementsthatmatchthel10nid. * *@param{string}l10nId *@param{Document}doc *@returns{Element}
*/ function getAllByL10nId(l10nId, doc = document) { const elements = doc.querySelectorAll(`[data-l10n-id="${l10nId}"]`); if (elements.length === 0) { thrownew Error("Could not find the element by l10n id: " + l10nId);
} return elements;
}
/** *RetrievesanelementbyitsId. * *@param{string}id *@param{Document}[doc] *@returns{Element} *@throwsThrowsiftheelementisnotvisibleintheDOM.
*/ function getById(id, doc = document) { const element = maybeGetById(id, /* ensureIsVisible */ true, doc); if (!element) { thrownew Error("The element is not visible in the DOM: #" + id);
} return element;
}
/** *Getanelementbyitsl10nid,asthisisauser-visiblewaytofindanelement. *The`l10nId`representsthetextthatauserwouldactuallysee. * *@param{string}l10nId *@param{Document}doc *@returns{Element}
*/ function getByL10nId(l10nId, doc = document) { const elements = doc.querySelectorAll(`[data-l10n-id="${l10nId}"]`); if (elements.length === 0) { thrownew Error("Could not find the element by l10n id: " + l10nId);
} for (const element of elements) { if (BrowserTestUtils.isVisible(element)) { return element;
}
} thrownew Error("The element is not visible in the DOM: " + l10nId);
}
/** *NavigatetoaURLandindicateamessageastowhy.
*/
async function navigate(
message,
{ url, onOpenPanel = null, downloadHandler = null, pivotTranslation = false }
) {
logAction(); // When the translations panel is open from the app menu, // it doesn't close on navigate the way that it does when it's // open from the translations button, so ensure that we always // close it when we navigate to a new page.
await closeAllOpenPanelsAndMenus();
info(message + " - " + url);
// Load a blank page first to ensure that tests don't hang. // I don't know why this is needed, but it appears to be necessary.
await loadBlankPage(); const loadTargetPage = async () => {
await loadNewPage(gBrowser.selectedBrowser, url);
if (downloadHandler) {
await FullPageTranslationsTestUtils.assertTranslationsButton(
{ button: true, circleArrows: true, locale: false, icon: true }, "The icon presents the loading indicator."
);
await downloadHandler(pivotTranslation ? 2 : 1);
}
};
await waitForCondition(
() => content.scrollY <= 10, "Waiting for scroll animation to complete."
);
// Wait for the new position to be painted.
await new Promise(resolve => {
content.requestAnimationFrame(() =>
content.requestAnimationFrame(resolve)
);
});
});
}
await waitForCondition(() => { return content.scrollY >= scrollHeight - content.innerHeight - 10;
}, "Waiting for scroll animation to complete.");
// Wait for the new position to be painted.
await new Promise(resolve => {
content.requestAnimationFrame(() =>
content.requestAnimationFrame(resolve)
);
});
});
}
if (parentMemoryMiB > this.#peakParentMemoryMiB) { this.#peakParentMemoryMiB = parentMemoryMiB;
} if (inferenceMemoryMiB > this.#peakInferenceMemoryMiB) { this.#peakInferenceMemoryMiB = inferenceMemoryMiB;
}
}
/** *Startstheintervaltimertobeginsamplingnewpeakmemoryusagevalues.
*/
start() { if (this.#intervalId !== null) { thrownew Error( "Attempt to start a PeakMemorySampler that was already running."
);
}
/** *Stopstheintervaltimerfromcontinuingtosamplepeakmemoryusage.
*/
stop() { if (this.#intervalId === null) { thrownew Error( "Attempt to stop a PeakMemorySampler that was not running."
);
}
/** *Returnsthepeakrecordedmemoryusageinmebibytes(MiB). * *@returns{{peakParentMemoryMiB:number,peakInferenceMemoryMiB:number}}
*/
getPeakRecordedMemoryUsage() { if (this.#intervalId) { thrownew Error( "Attempt to retrieve peak recorded memory usage while the memory sampler is running."
);
}
4) Include the resulting metadata for ${testPageName} in the TranslationsBencher.#PAGE_DATA object.
`);
}
if (sourceLanguage !== pageLanguage) { thrownew Error(
`Perf test source language '${sourceLanguage}' did not match the expected page language '${pageLanguage}'.`
);
}
const journal = new TranslationsBencher.Journal();
// Create a new PeakMemorySampler using the provided interval. const peakMemorySampler = new TranslationsBencher.PeakMemorySampler(
memorySampleInterval
);
// Destroy the TranslationsEngine, then force cycle collection and garbage collection again.
await EngineProcess.destroyTranslationsEngine();
Services.obs.notifyObservers(null, "child-cc-request");
Services.obs.notifyObservers(null, "child-gc-request");
window.windowUtils.cycleCollect();
Cu.forceGC();
if (
translationsChild.translatedDoc?.hasPendingCallbackOnEventLoop() ||
translationsChild.translatedDoc?.hasPendingTranslationRequests() ||
translationsChild.translatedDoc?.isObservingAnyElementForContentIntersection()
) { // The final paragraph was translated, but it wasn't the final request, // so we must still wait for every translation request to complete.
await waitForCondition(
() =>
!translationsChild.translatedDoc?.hasPendingCallbackOnEventLoop() &&
!translationsChild.translatedDoc?.hasPendingTranslationRequests() &&
!translationsChild.translatedDoc?.isObservingAnyElementForContentIntersection(), "Waiting for all pending translation requests to complete."
);
}
});
let inferenceInfo = {}; for (const child of info.children) { // At the time of writing, there is only a single inference process. // If we one day spawn multiple inference processes, this code will // need to be revised. if (child.type === "inference") {
inferenceInfo = {
pid: child.pid,
memory: child.memory,
cpuTime: child.cpuTime,
cpuCycleCount: child.cpuCycleCount,
}; break;
}
}
return {
parentInfo,
inferenceInfo,
};
}
}
/** *Acollectionofsharedfunctionalityutilizedby *FullPageTranslationsTestUtilsandSelectTranslationsTestUtils. * *Usingfunctionsfromtheaforementionedclassesispreferredover *usingfunctionsfromthisclassdirectly.
*/ class SharedTranslationsTestUtils { /** *Assertsthatthespecifiedelementcurrentlyhasfocus. * *@param{Element}element-Theelementtocheckforfocus.
*/ static _assertHasFocus(element) {
is(
document.activeElement,
element,
`The element '${element.id}' should have focus.`
);
}
/** *AssertsthatthegivenelementhastheexpectedL10nId. * *@param{Element}element-Theelementtoassertagainst. *@param{string}l10nId-Theexpectedlocalizationid.
*/ static _assertL10nId(element, l10nId) {
is(
element.getAttribute("data-l10n-id"),
l10nId,
`The element ${element.id} should have L10n Id ${l10nId}.`
);
}
/** *AssertsthatthemainViewIdofthepanelmatchesthegivenstring. * *@param{FullPageTranslationsPanel|SelectTranslationsPanel}panel *@param{string}expectedId-TheexpectedidthatmainViewIdissetto.
*/ static _assertPanelMainViewId(panel, expectedId) { const mainViewId = panel.elements.multiview.getAttribute("mainViewId");
is(
mainViewId,
expectedId, "The mainViewId should match its expected value"
);
}
/** *AssertsthattheselectedlanguageinthemenumatchesthelangTagorl10nId. * *@param{Element}menuList-Themenulistelementtocheck. *@param{object}options-Optionscontaining'langTag'and'l10nId'toassertagainst. *@param{string}[options.langTag]-TheBCP-47languagetagtomatch. *@param{string}[options.l10nId]-ThelocalizationIdtomatch.
*/ static _assertSelectedLanguage(menuList, { langTag, l10nId }) {
ok(
menuList.label,
`The label for the menulist ${menuList.id} should not be empty.`
); if (langTag !== undefined) {
is(
menuList.value,
langTag,
`Expected ${menuList.id} selection to match '${langTag}'`
);
} if (l10nId !== undefined) {
is(
menuList.getAttribute("data-l10n-id"),
l10nId,
`Expected ${menuList.id} l10nId to match '${l10nId}'`
);
}
}
for (const propertyName in expectations) {
ok(
elements.hasOwnProperty(propertyName),
`Expected panel elements to have property ${propertyName}`
); if (expectations[propertyName]) {
visible[propertyName] = elements[propertyName];
} else {
hidden[propertyName] = elements[propertyName];
}
}
if (elements.length) {
elements[0].focus();
elements.push(elements[0]);
} for (const element of elements) {
SharedTranslationsTestUtils._assertHasFocus(element);
EventUtils.synthesizeKey("KEY_Tab");
}
activeElementAtStart.focus();
}
/** *Executestheprovidedcallbackbeforewaitingfortheeventandthenwaitsforthegivenevent *tobefiredfortheelementcorrespondingtotheprovidedelementId. * *OptionallyexecutesapostEventAssertionfunctiononcetheeventoccurs. * *@param{string}elementId-TheIdoftheelementtowaitfortheeventon. *@param{string}eventName-Thenameoftheeventtowaitfor. *@param{Function}callback-Acallbackfunctiontoexecuteimmediatelybeforewaitingfortheevent. *Thisisoftenusedtotriggertheeventontheexpectedelement. *@param{Function|null}[postEventAssertion=null]-Anoptionalcallbackfunctiontoexecuteafter *theeventhasoccurred. *@param{ChromeWindow}[win] *@throwsThrowsiftheelementwiththespecified`elementId`doesnotexist. *@returns{Promise<void>}
*/ static async _waitForPopupEvent(
elementId,
eventName,
callback,
postEventAssertion = null,
win = window
) { const element = win.document.getElementById(elementId); if (!element) { thrownew Error(
`Unable to find the ${elementId} element in the document.`
);
} const promise = BrowserTestUtils.waitForEvent(element, eventName);
await callback();
info(`Waiting for the ${elementId} ${eventName} event`);
await promise; if (postEventAssertion) {
await postEventAssertion();
} // Wait a single tick on the event loop.
await new Promise(resolve => setTimeout(resolve, 0));
}
}
while (
translationsChild.translatedDoc?.hasPendingTranslationRequests() ||
translationsChild.translatedDoc?.hasPendingCallbackOnEventLoop()
) {
await waitForCondition(
() =>
!translationsChild.translatedDoc?.hasPendingTranslationRequests(), "Waiting for all pending translation requests to complete."
);
await waitForCondition(
() =>
!translationsChild.translatedDoc?.hasPendingCallbackOnEventLoop(), "Waiting for pending event-loop callbacks to resolve in the TranslationsDocument."
);
}
});
}
await waitForCondition(
() =>
!translationsChild.translatedDoc?.isObservingAnyElementForContentIntersection(), "Waiting until no elements are observed for content intersection."
);
});
}
await waitForCondition(
() =>
!translationsChild.translatedDoc?.isObservingAnyElementForAttributeIntersection(), "Waiting until no elements are observed for attribute intersection."
);
});
}
await waitForCondition(
() =>
translationsChild.translatedDoc?.isObservingAnyElementForContentIntersection(), "Waiting until an element is observed for content intersection."
);
});
}
await waitForCondition(
() =>
translationsChild.translatedDoc?.isObservingAnyElementForAttributeIntersection(), "Waiting until an element is observed for attribute intersection."
);
});
}
await waitForCondition(
() => translationsChild.translatedDoc?.hasPendingTranslationRequests(), "Waiting for any translation request to initialize."
);
});
}
/** *AssertsthattheSpanishtestpageH1element'scontenthasbeentranslatedintothetargetlanguage. * *@param{object}options-Theoptionsfortheassertion. * *@param{string}options.fromLanguage-TheBCP-47languagetagbeingtranslatedfrom. *@param{string}options.toLanguage-TheBCP-47languagetagbeingtranslatedinto. *@param{Function}options.runInPage-Allowsrunningaclosureinthecontentpage. *@param{boolean}[options.endToEndTest=false]-Whetherthisassertionisforanend-to-endtest. *@param{string}[options.message]-Anoptionalmessagetologtoinfo.
*/ static async assertPageH1ContentIsTranslated({
fromLanguage,
toLanguage,
runInPage,
endToEndTest = false,
message = null,
}) { if (message) {
info(message);
}
info("Checking that the page header is translated");
let callback; if (endToEndTest) {
callback = async TranslationsTest => { const { getH1 } = TranslationsTest.getSelectors();
await TranslationsTest.assertTranslationResult( "The page's H1 is translated.",
getH1, "Don Quixote de La Mancha"
);
};
} else {
callback = async (TranslationsTest, { fromLang, toLang }) => { const { getH1 } = TranslationsTest.getSelectors();
await TranslationsTest.assertTranslationResult( "The page's H1 is translated.",
getH1,
`DON QUIJOTE DE LA MANCHA [${fromLang} to ${toLang}]`
);
};
}
info("Checking that the page header is not translated");
await runInPage(async TranslationsTest => { const { getH1 } = TranslationsTest.getSelectors();
await TranslationsTest.assertTranslationResult( "The page's H1 is not translated and is in the original Spanish.",
getH1, "Don Quijote de La Mancha"
);
});
}
/** *AssertsthattheSpanishtestpageH1element'stitlehasbeentranslatedintothetargetlanguage. * *@param{object}options-Theoptionsfortheassertion. * *@param{string}options.fromLanguage-TheBCP-47languagetagbeingtranslatedfrom. *@param{string}options.toLanguage-TheBCP-47languagetagbeingtranslatedinto. *@param{Function}options.runInPage-Allowsrunningaclosureinthecontentpage. *@param{boolean}[options.endToEndTest=false]-Whetherthisassertionisforanend-to-endtest. *@param{string}[options.message]-Anoptionalmessagetologtoinfo.
*/ static async assertPageH1TitleIsTranslated({
fromLanguage,
toLanguage,
runInPage,
endToEndTest = false,
message = null,
}) { if (message) {
info(message);
}
info("Checking that the page header's title attribute is translated");
let callback; if (endToEndTest) {
callback = async TranslationsTest => { const { getH1Title } = TranslationsTest.getSelectors();
await TranslationsTest.assertTranslationResult( "The page's H1's title attribute is translated.",
getH1Title, "This is the title of the page header"
);
};
} else {
callback = async (TranslationsTest, { fromLang, toLang }) => { const { getH1Title } = TranslationsTest.getSelectors();
await TranslationsTest.assertTranslationResult( "The page's H1's title attribute is translated.",
getH1Title,
`ESTE ES EL TÍTULO DEL ENCABEZADO DE PÁGINA [${fromLang} to ${toLang}]`
);
};
}
info("Checking that the page header's title is not translated");
await runInPage(async TranslationsTest => { const { getH1Title } = TranslationsTest.getSelectors();
await TranslationsTest.assertTranslationResult( "The page's H1's title is not translated and is in the original Spanish.",
getH1Title, "Este es el título del encabezado de página"
);
});
}
/** *AssertsthattheSpanishtestpagefinal<p>elementhasbeentranslatedintothetargetlanguage. * *@param{object}options-Theoptionsfortheassertion. * *@param{string}options.fromLanguage-TheBCP-47languagetagbeingtranslatedfrom. *@param{string}options.toLanguage-TheBCP-47languagetagbeingtranslatedinto. *@param{Function}options.runInPage-Allowsrunningaclosureinthecontentpage. *@param{boolean}[options.endToEndTest=false]-Whetherthisassertionisforanend-to-endtest. *@param{string}[options.message]-Anoptionalmessagetologtoinfo.
*/ static async assertPageFinalParagraphContentIsTranslated({
fromLanguage,
toLanguage,
runInPage,
endToEndTest = false,
message = null,
}) { if (message) {
info(message);
}
info("Checking that the page's final paragraph is translated");
let callback; if (endToEndTest) {
callback = async TranslationsTest => { const { getFinalParagraph } = TranslationsTest.getSelectors();
await TranslationsTest.assertTranslationResult( "The page's final paragraph is translated.",
getFinalParagraph,
[ // TODO (Bug 1967764) We need to investigate why some machines may produce // a different translated output, given the same models and the same WASM binary. "Well, even if you're more arms than those of the giant Briareo, you'll pay me.", "For, though you're more arms than those of the giant Briareo, you'll pay me.",
]
);
};
} else {
callback = async (TranslationsTest, { fromLang, toLang }) => { const { getFinalParagraph } = TranslationsTest.getSelectors();
await TranslationsTest.assertTranslationResult( "The page's final paragraph is translated.",
getFinalParagraph,
`— PUES, AUNQUE MOVÁIS MÁS BRAZOS QUE LOS DEL GIGANTE BRIAREO, ME LO HABÉIS DE PAGAR. [${fromLang} to ${toLang}]`
);
};
}
info("Checking that the page's final paragraph is not translated");
await runInPage(async TranslationsTest => { const { getFinalParagraph } = TranslationsTest.getSelectors();
await TranslationsTest.assertTranslationResult( "The page's final paragraph is not translated and is in the original Spanish.",
getFinalParagraph, "— Pues, aunque mováis más brazos que los del gigante Briareo, me lo habéis de pagar."
);
});
}
/** *AssertsthattheSpanishtestpagefinal<p>element'stitleattributehasbeentranslatedintothetargetlanguage. * *@param{object}options-Theoptionsfortheassertion. * *@param{string}options.fromLanguage-TheBCP-47languagetagbeingtranslatedfrom. *@param{string}options.toLanguage-TheBCP-47languagetagbeingtranslatedinto. *@param{Function}options.runInPage-Allowsrunningaclosureinthecontentpage. *@param{boolean}[options.endToEndTest=false]-Whetherthisassertionisforanend-to-endtest. *@param{string}[options.message]-Anoptionalmessagetologtoinfo.
*/ static async assertPageFinalParagraphTitleIsTranslated({
fromLanguage,
toLanguage,
runInPage,
endToEndTest = false,
message = null,
}) { if (message) {
info(message);
}
info("Checking that the final paragraph's title attribute is translated");
let callback; if (endToEndTest) {
callback = async TranslationsTest => { const { getFinalParagraphTitle } = TranslationsTest.getSelectors();
await TranslationsTest.assertTranslationResult( "The final paragraph's title attribute is translated.",
getFinalParagraphTitle, "This is the title of the final paragraph"
);
};
} else {
callback = async (TranslationsTest, { fromLang, toLang }) => { const { getFinalParagraphTitle } = TranslationsTest.getSelectors();
await TranslationsTest.assertTranslationResult( "The final paragraph's title attribute is translated.",
getFinalParagraphTitle,
`ESTE ES EL TÍTULO DEL ÚLTIMO PÁRRAFO [${fromLang} to ${toLang}]`
);
};
}
info( "Checking that the final paragraph's title attribute is not translated"
);
await runInPage(async TranslationsTest => { const { getFinalParagraphTitle } = TranslationsTest.getSelectors();
await TranslationsTest.assertTranslationResult( "The final paragraph's title attribute is not translated and is in the original Spanish.",
getFinalParagraphTitle, "Este es el título del último párrafo"
);
});
}
/** *Assertsthatthepanelelementvisibilitymatchesthepanelloadingview.
*/ static assertPanelViewLoading() {
info("Checking that the panel shows the loading view");
FullPageTranslationsTestUtils.assertPanelViewDefault(); const loadingButton = getByL10nId( "translations-panel-translate-button-loading"
);
ok(loadingButton, "The loading button is present");
ok(loadingButton.disabled, "The loading button is disabled");
}
/** *Assertsthatpanelelementvisibilitymatchesthepanelintroview.
*/ static assertPanelViewIntro() {
info("Checking that the panel shows the intro view");
FullPageTranslationsTestUtils.#assertPanelMainViewId( "full-page-translations-panel-view-default"
);
FullPageTranslationsTestUtils.#assertPanelElementVisibility({
intro: true,
introLearnMoreLink: true,
...FullPageTranslationsTestUtils.#defaultViewVisibilityExpectations,
});
FullPageTranslationsTestUtils.#assertPanelHeaderL10nId( "translations-panel-intro-header"
);
}
for (const [name, element] of Object.entries(elements)) { if (!element) { thrownew Error("Could not find the " + name);
}
}
try { // Test that the visibilities match.
await waitForCondition(() => { for (const [name, visible] of Object.entries(visibleAssertions)) { if (elements[name].hidden === visible) { returnfalse;
}
} returntrue;
}, message);
} catch (error) { // On a mismatch, report it. for (const [name, expected] of Object.entries(visibleAssertions)) {
is(!elements[name].hidden, expected, `Visibility for"${name}"`);
}
}
/** *Opensthetranslationspanelsettingsmenu. *Requiresthatthetranslationspanelisalreadyopen.
*/ static async openTranslationsSettingsMenu() {
logAction(); const gearIcons = getAllByL10nId("translations-panel-settings-button"); for (const gearIcon of gearIcons) { if (BrowserTestUtils.isHidden(gearIcon)) { continue;
}
click(gearIcon, "Open the settings menu");
info("Waiting for settings menu to open."); const manageLanguages = await waitForCondition(() =>
maybeGetByL10nId("translations-panel-settings-manage-languages")
);
ok(
manageLanguages, "The manage languages item should be visible in the settings menu."
); return;
}
}
const menuItem = menuPopup.querySelector(`[value="${langTag}"]`);
await FullPageTranslationsTestUtils.waitForPanelPopupEvent( "popuphidden",
() => { if (menuPopup.isNativeMenu) {
menuPopup.activateItem(menuItem); return;
}
click(menuItem); // Synthesizing a click on the menuitem isn't closing the popup // as a click normally would, so this tab keypress is added to // ensure the popup closes.
EventUtils.synthesizeKey("KEY_Tab", {}, win);
}, null/* postEventAssertion */,
win
);
}
if (expectMenuItemVisible === true) { if (expectedTargetLanguage) { // Target language expected, check for the data-l10n-id with a `{$language}` argument. const expectedL10nId =
selectH1 ||
selectPdfSpan ||
selectFrenchSection ||
selectEnglishSection ||
selectSpanishSection ||
selectFrenchSentence ||
selectEnglishSentence ||
selectSpanishSentence
? "main-context-menu-translate-selection-to-language"
: "main-context-menu-translate-link-text-to-language";
await waitForCondition(
() =>
TranslationsUtils.langTagsMatch(
menuItem.getAttribute("target-language"),
expectedTargetLanguage
),
`Waiting for translate-selection context menu item to match the expected target language ${expectedTargetLanguage}`
);
await waitForCondition(
() => menuItem.getAttribute("data-l10n-id") === expectedL10nId,
`Waiting for translate-selection context menu item to have the correct data-l10n-id '${expectedL10nId}`
);
if (Services.locale.appLocaleAsBCP47 === "en-US") { // We only want to test the localized name in CI if the current app locale is the default (en-US). const expectedLanguageDisplayName = getIntlDisplayName(
expectedTargetLanguage
);
await waitForCondition(() => { const l10nArgs = JSON.parse(
menuItem.getAttribute("data-l10n-args")
); return l10nArgs.language === expectedLanguageDisplayName;
}, `Waiting for translate-selection context menu item to have the correct data-l10n-args '${expectedLanguageDisplayName}`);
}
} else { // No target language expected, check for the data-l10n-id that has no `{$language}` argument. const expectedL10nId =
selectH1 ||
selectPdfSpan ||
selectFrenchSection ||
selectEnglishSection ||
selectSpanishSection ||
selectFrenchSentence ||
selectEnglishSentence ||
selectSpanishSentence
? "main-context-menu-translate-selection"
: "main-context-menu-translate-link-text";
await waitForCondition(
() => !menuItem.getAttribute("target-language"), "Waiting for translate-selection context menu item to remove its target-language attribute."
);
await waitForCondition(
() => menuItem.getAttribute("data-l10n-id") === expectedL10nId,
`Waiting for translate-selection context menu item to have the correct data-l10n-id '${expectedL10nId}`
);
}
}
}
await SelectTranslationsTestUtils.assertContextMenuTranslateSelectionItem(
runInPage,
{
selectSpanishSentence: true,
openAtSpanishSentence: true,
expectMenuItemVisible: true,
expectedTargetLanguage,
},
`The translate-selection context menu item should match the expected target language '${expectedTargetLanguage}'`
);
await waitForCondition(
() =>
!copyButton.classList.contains("copied") &&
copyButton.getAttribute("data-l10n-id") === "select-translations-panel-copy-button", "Waiting for copy button to match the not-copied state."
);
/** *AssertsthattheSelectTranslationsPaneltranslatedtextareais *bothscrollableandscrolledtothetop.
*/ static async #assertPanelTextAreaOverflow() { const { textArea } = SelectTranslationsPanel.elements; if (textArea.style.overflow !== "auto") {
await BrowserTestUtils.waitForMutationCondition(
textArea,
{ attributes: true, attributeFilter: ["style"] },
() => textArea.style.overflow === "auto"
);
} if (textArea.scrollHeight > textArea.clientHeight) {
info("Ensuring that the textarea is scrolled to the top.");
await waitForCondition(() => textArea.scrollTop === 0);
info("Ensuring that the textarea cursor is at the beginning.");
await waitForCondition(
() => textArea.selectionStart === 0 && textArea.selectionEnd === 0
);
}
}
if (
SelectTranslationsPanel.getSourceText().length <
SelectTranslationsPanel.textLengthThreshold
) {
is(
textArea.style.height,
SelectTranslationsPanel.shortTextHeight, "The panel text area should have the short-text height"
);
} else {
is(
textArea.style.height,
SelectTranslationsPanel.longTextHeight, "The panel text area should have the long-text height"
);
}
}
if (fromLanguage === toLanguage) {
is(
SelectTranslationsPanel.getSourceText(),
SelectTranslationsPanel.getTranslatedText(), "The source text should passthrough as the translated text."
); return;
}
const translatedSuffix = ` [${fromLanguage} to ${toLanguage}]`;
ok(
textArea.value.endsWith(translatedSuffix),
`Translated text should match ${fromLanguage} to ${toLanguage}`
);
is(
SelectTranslationsPanel.getSourceText().length,
SelectTranslationsPanel.getTranslatedText().length -
translatedSuffix.length, "Expected translated text length to correspond to the source text length."
);
}
for (const [elementName, expectEnabled] of Object.entries(enabledStates)) { const element = elements[elementName]; if (!element) { thrownew Error(
`SelectTranslationsPanel element '${elementName}' not found.`
);
}
is(
element.disabled,
!expectEnabled,
`The element '${elementName} should be ${
expectEnabled ? "enabled" : "disabled"
}.`
);
}
}
/**
* Simulates clicking the done button and waits for the panel to close.
*/
static async clickDoneButton() {
logAction();
const { doneButtonPrimary, doneButtonSecondary } =
SelectTranslationsPanel.elements;
let visibleDoneButton;
let hiddenDoneButton;
if (BrowserTestUtils.isVisible(doneButtonPrimary)) {
visibleDoneButton = doneButtonPrimary;
hiddenDoneButton = doneButtonSecondary;
} else if (BrowserTestUtils.isVisible(doneButtonSecondary)) {
visibleDoneButton = doneButtonSecondary;
hiddenDoneButton = doneButtonPrimary;
} else {
throw new Error(
"Expected either the primary or secondary done button to be visible."
);
}
assertVisibility({
visible: { visibleDoneButton },
hidden: { hiddenDoneButton },
});
await SelectTranslationsTestUtils.waitForPanelPopupEvent(
"popuphidden",
() => {
click(visibleDoneButton, "Clicking the done button");
}
);
}
/**
* Simulates clicking the cancel button and waits for the panel to close.
*/
static async clickCancelButton() {
logAction();
const { cancelButton } = SelectTranslationsPanel.elements;
assertVisibility({ visible: { cancelButton } });
await SelectTranslationsTestUtils.waitForPanelPopupEvent(
"popuphidden",
() => {
click(cancelButton, "Clicking the cancel button");
}
);
}
/**
* Simulates clicking the copy button and asserts that all relevant states are correctly updated.
*/
static async clickCopyButton() {
logAction();
const { copyButton } = SelectTranslationsPanel.elements;
assertVisibility({ visible: { copyButton } });
is(
SelectTranslationsPanel.phase(),
"translated",
'The copy button should only be clickable in the "translated" phase'
);
click(copyButton, "Clicking the copy button");
await waitForCondition(
() =>
copyButton.classList.contains("copied") &&
copyButton.getAttribute("data-l10n-id") ===
"select-translations-panel-copy-button-copied",
"Waiting for copy button to match the copied state."
);
const copiedText = SpecialPowers.getClipboardData("text/plain");
is(
// Because of differences in the clipboard code on Windows, we are going
// to explicitly sanitize carriage returns here when checking equality.
copiedText.replaceAll("\r", ""),
SelectTranslationsPanel.getTranslatedText().replaceAll("\r", ""),
"The clipboard should contain the translated text."
);
}
/**
* Simulates clicking the Translate button in the SelectTranslationsPanel,
* then waits for any pending translation effects, based on the provided options.
*
* @param {object} config
* @param {Function} [config.downloadHandler]
* - The function handle expected downloads, resolveDownloads() or rejectDownloads()
* Leave as null to test more granularly, such as testing opening the loading view,
* or allowing for the automatic downloading of files.
* @param {boolean} [config.pivotTranslation]
* - True if the expected translation is a pivot translation, otherwise false.
* Affects the number of expected downloads.
* @param {Function} [config.viewAssertion]
* - An optional callback function to execute for asserting the panel UI state.
*/
static async clickTranslateButton({
downloadHandler,
pivotTranslation,
viewAssertion,
}) {
logAction();
const {
doneButtonSecondary,
settingsButton,
translateButton,
tryAnotherSourceMenuList,
} = SelectTranslationsPanel.elements;
assertVisibility({ visible: { doneButtonPrimary: translateButton } });
ok(!translateButton.disabled, "The translate button should be enabled.");
SharedTranslationsTestUtils._assertTabIndexOrder([
settingsButton,
tryAnotherSourceMenuList,
...(AppConstants.platform === "win"
? [translateButton, doneButtonSecondary]
: [doneButtonSecondary, translateButton]),
]);
/**
* Simulates clicking the try-again button.
*
* @param {object} config
* @param {Function} [config.downloadHandler]
* - The function handle expected downloads, resolveDownloads() or rejectDownloads()
* Leave as null to test more granularly, such as testing opening the loading view,
* or allowing for the automatic downloading of files.
* @param {boolean} [config.pivotTranslation]
* - True if the expected translation is a pivot translation, otherwise false.
* Affects the number of expected downloads.
* @param {Function} [config.viewAssertion]
* - An optional callback function to execute for asserting the panel UI state.
*/
static async clickTryAgainButton({
downloadHandler,
pivotTranslation,
viewAssertion,
} = {}) {
logAction();
const { tryAgainButton } = SelectTranslationsPanel.elements;
assertVisibility({ visible: { tryAgainButton } });
if (SelectTranslationsPanel.phase() === "init-failure") {
// The try-again button reopens the panel from the "init-failure" phase.
await SelectTranslationsTestUtils.waitForPanelPopupEvent(
"popupshown",
() => click(tryAgainButton, "Clicking the try-again button")
);
} else {
// Otherwise the try-again button just attempts to re-translate.
click(tryAgainButton, "Clicking the try-again button");
}
/**
* Clicks the SelectTranslationsPanel settings menu item
* that leads to the Translations Settings in about:preferences.
*/
static clickTranslationsSettingsPageMenuItem() {
logAction();
const settingsPageMenuItem = document.getElementById(
"select-translations-panel-open-settings-page-menuitem"
);
assertVisibility({ visible: { settingsPageMenuItem } });
click(settingsPageMenuItem);
}
/**
* Opens the context menu at a specified element on the page, based on the provided options.
*
* @param {Function} runInPage - A content-exposed function to run within the context of the page.
* @param {object} options - Options for opening the context menu.
*
* @param {boolean} options.expectMenuItemVisible - Whether the select-translations menu item should be present in the context menu.
* @param {boolean} options.expectedTargetLanguage - The expected target language to be shown in the context menu.
*
* The following options will work on all test pages that have an <h1> element.
*
* @param {boolean} options.selectH1 - Selects the first H1 element of the page.
* @param {boolean} options.openAtH1 - Opens the context menu at the first H1 element of the page.
*
* The following options will work only in the PDF_TEST_PAGE_URL.
*
* @param {boolean} options.selectPdfSpan - Selects the first span of text on the first page of a pdf.
* @param {boolean} options.openAtPdfSpan - Opens the context menu at the first span of text on the first page of a pdf.
*
* The following options will only work when testing SELECT_TEST_PAGE_URL.
*
* @param {boolean} options.selectFrenchSection - Selects the section of French text.
* @param {boolean} options.selectEnglishSection - Selects the section of English text.
* @param {boolean} options.selectSpanishSection - Selects the section of Spanish text.
* @param {boolean} options.selectFrenchSentence - Selects a French sentence.
* @param {boolean} options.selectEnglishSentence - Selects an English sentence.
* @param {boolean} options.selectSpanishSentence - Selects a Spanish sentence.
* @param {boolean} options.openAtFrenchSection - Opens the context menu at the section of French text.
* @param {boolean} options.openAtEnglishSection - Opens the context menu at the section of English text.
* @param {boolean} options.openAtSpanishSection - Opens the context menu at the section of Spanish text.
* @param {boolean} options.openAtFrenchSentence - Opens the context menu at a French sentence.
* @param {boolean} options.openAtEnglishSentence - Opens the context menu at an English sentence.
* @param {boolean} options.openAtSpanishSentence - Opens the context menu at a Spanish sentence.
* @param {boolean} options.openAtFrenchHyperlink - Opens the context menu at a hyperlinked French text.
* @param {boolean} options.openAtEnglishHyperlink - Opens the context menu at a hyperlinked English text.
* @param {boolean} options.openAtSpanishHyperlink - Opens the context menu at a hyperlinked Spanish text.
* @param {boolean} options.openAtURLHyperlink - Opens the context menu at a hyperlinked URL text.
* @throws Throws an error if no valid option was provided for opening the menu.
*/
static async openContextMenu(runInPage, options) {
logAction();
/**
* Handles language-model downloads for the SelectTranslationsPanel, ensuring that expected
* UI states match based on the resolved download state.
*
* @param {object} options - Configuration options for downloads.
* @param {function(number): Promise<void>} options.downloadHandler - The function to resolve or reject the downloads.
* @param {boolean} [options.pivotTranslation] - Whether to expect a pivot translation.
*
* @returns {Promise<void>}
*/
static async handleDownloads({ downloadHandler, pivotTranslation }) {
if (downloadHandler) {
await SelectTranslationsTestUtils.assertPanelViewActivelyTranslating();
await downloadHandler(pivotTranslation ? 2 : 1);
}
}
/**
* Switches the selected from-language to the provided language tags
*
* @param {string[]} langTags - An array of BCP-47 language tags.
* @param {object} options - Configuration options for the language change.
* @param {boolean} options.openDropdownMenu - Determines whether the language change should be made via a dropdown menu or directly.
*
* @returns {Promise<void>}
*/
static async changeSelectedFromLanguage(langTags, options) {
logAction(langTags);
const { fromMenuList, fromMenuPopup } = SelectTranslationsPanel.elements;
const { openDropdownMenu } = options;
/**
* Change the selected language in the try-another-source-language dropdown.
*
* @param {string} langTag - A BCP-47 language tag.
*/
static async changeSelectedTryAnotherSourceLanguage(langTag) {
logAction(langTag);
const { tryAnotherSourceMenuList, translateButton } =
SelectTranslationsPanel.elements;
await SelectTranslationsTestUtils.#changeSelectedLanguageDirectly(
[langTag],
{ menuList: tryAnotherSourceMenuList },
{
onChangeLanguage: () =>
ok(
!translateButton.disabled,
"The translate button should be enabled after selecting a language."
),
}
);
}
/**
* Switches the selected to-language to the provided language tag.
*
* @param {string[]} langTags - An array of BCP-47 language tags.
* @param {object} options - Options for selecting paragraphs and opening the context menu.
* @param {boolean} options.openDropdownMenu - Determines whether the language change should be made via a dropdown menu or directly.
* @param {Function} options.downloadHandler - Handler for initiating downloads post language change, if applicable.
* @param {Function} options.onChangeLanguage - Callback function to be executed after the language change.
*
* @returns {Promise<void>}
*/
static async changeSelectedToLanguage(langTags, options) {
logAction(langTags);
const { toMenuList, toMenuPopup } = SelectTranslationsPanel.elements;
const { openDropdownMenu } = options;
/**
* Directly changes the selected language to each provided language tag without using a dropdown menu.
*
* @param {string[]} langTags - An array of BCP-47 language tags for direct selection.
* @param {object} elements - Elements required for changing the selected language.
* @param {Element} elements.menuList - The menu list element where languages are directly changed.
* @param {object} options - Configuration options for language change and additional actions.
* @param {Function} options.downloadHandler - Handler for initiating downloads post language change, if applicable.
* @param {Function} options.onChangeLanguage - Callback function to be executed after the language change.
*
* @returns {Promise<void>}
*/
static async #changeSelectedLanguageDirectly(langTags, elements, options) {
const { menuList } = elements;
const { textArea } = SelectTranslationsPanel.elements;
const { onChangeLanguage, downloadHandler } = options;
// Either of these events should trigger a translation after the selected
// language has been changed directly.
if (Math.random() < 0.5) {
info("Attempting to trigger translation via text-area focus.");
textArea.focus();
} else {
info("Attempting to trigger translation via pressing Enter.");
EventUtils.synthesizeKey("KEY_Enter");
}
if (downloadHandler) {
await SelectTranslationsTestUtils.handleDownloads(options);
}
if (onChangeLanguage) {
await onChangeLanguage();
}
}
/**
* Changes the selected language by opening the dropdown menu for each provided language tag.
*
* @param {string[]} langTags - An array of BCP-47 language tags for selection via dropdown.
* @param {object} elements - Elements involved in the dropdown language selection process.
* @param {Element} elements.menuList - The element that triggers the dropdown menu.
* @param {Element} elements.menuPopup - The dropdown menu element containing selectable languages.
* @param {object} options - Configuration options for language change and additional actions.
* @param {Function} options.downloadHandler - Handler for initiating downloads post language change, if applicable.
* @param {Function} options.onChangeLanguage - Callback function to be executed after the language change.
*
* @returns {Promise<void>}
*/
static async #changeSelectedLanguageViaDropdownMenu(
langTags,
elements,
options
) {
const { menuList, menuPopup } = elements;
const { onChangeLanguage } = options;
for (const langTag of langTags) {
await SelectTranslationsTestUtils.waitForPanelPopupEvent(
"popupshown",
() => click(menuList)
);
const menuItem = menuPopup.querySelector(`[value="${langTag}"]`);
await SelectTranslationsTestUtils.waitForPanelPopupEvent(
"popuphidden",
() => {
if (menuPopup.isNativeMenu) {
menuPopup.activateItem(menuItem);
return;
}
click(menuItem);
// Synthesizing a click on the menuitem isn't closing the popup
// as a click normally would, so this tab keypress is added to
// ensure the popup closes.
EventUtils.synthesizeKey("KEY_Tab");
}
);
await SelectTranslationsTestUtils.handleDownloads(options);
if (onChangeLanguage) {
await onChangeLanguage();
}
}
}
/**
* Opens the Select Translations panel via the context menu based on specified options.
*
* @param {Function} runInPage - A content-exposed function to run within the context of the page.
* @param {object} options - Options for selecting paragraphs and opening the context menu.
*
* The following options will only work when testing SELECT_TEST_PAGE_URL.
*
* @param {string} options.expectedFromLanguage - The expected from-language tag.
* @param {string} options.expectedToLanguage - The expected to-language tag.
* @param {boolean} options.selectFrenchSection - Selects the section of French text.
* @param {boolean} options.selectEnglishSection - Selects the section of English text.
* @param {boolean} options.selectSpanishSection - Selects the section of Spanish text.
* @param {boolean} options.selectFrenchSentence - Selects a French sentence.
* @param {boolean} options.selectEnglishSentence - Selects an English sentence.
* @param {boolean} options.selectSpanishSentence - Selects a Spanish sentence.
* @param {boolean} options.openAtFrenchSection - Opens the context menu at the section of French text.
* @param {boolean} options.openAtEnglishSection - Opens the context menu at the section of English text.
* @param {boolean} options.openAtSpanishSection - Opens the context menu at the section of Spanish text.
* @param {boolean} options.openAtFrenchSentence - Opens the context menu at a French sentence.
* @param {boolean} options.openAtEnglishSentence - Opens the context menu at an English sentence.
* @param {boolean} options.openAtSpanishSentence - Opens the context menu at a Spanish sentence.
* @param {boolean} options.openAtFrenchHyperlink - Opens the context menu at a hyperlinked French text.
* @param {boolean} options.openAtEnglishHyperlink - Opens the context menu at a hyperlinked English text.
* @param {boolean} options.openAtSpanishHyperlink - Opens the context menu at a hyperlinked Spanish text.
* @param {boolean} options.openAtURLHyperlink - Opens the context menu at a hyperlinked URL text.
* @param {Function} [options.onOpenPanel] - An optional callback function to execute after the panel opens.
* @param {string|null} [message] - An optional message to log to info.
* @throws Throws an error if the context menu could not be opened with the provided options.
* @returns {Promise<void>}
*/
static async openPanel(runInPage, options, message) {
logAction();
const documentRoleElement = panel.querySelector('[role="document"]');
ok(documentRoleElement, "The document-role element can be found.");
const ariaDescription = document.getElementById(
documentRoleElement.getAttribute("aria-describedby")
);
ok(ariaDescription, "The a11y description for the panel can be found.");
const ariaLabelIds = documentRoleElement
.getAttribute("aria-labelledby")
.split(" ");
for (const id of ariaLabelIds) {
const ariaLabel = document.getElementById(id);
ok(ariaLabel, `The a11y label element '${id}' can be found.`);
assertVisibility({ visible: { ariaLabel } });
}
}
/**
* XUL popups will fire the popupshown and popuphidden events. These will fire for
* any type of popup in the browser. This function waits for one of those events, and
* checks that the viewId of the popup is PanelUI-profiler
*
* @param {"popupshown" | "popuphidden"} eventName
* @param {Function} callback
* @param {Function} postEventAssertion
* An optional assertion to be made immediately after the event occurs.
* @returns {Promise<void>}
*/
static async waitForPanelPopupEvent(
eventName,
callback,
postEventAssertion = null
) {
// De-lazify the panel elements.
SelectTranslationsPanel.elements;
await SharedTranslationsTestUtils._waitForPopupEvent(
"select-translations-panel",
eventName,
callback,
postEventAssertion
);
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.