diff --git a/tests/e2e/e2e-helpers.ts b/tests/e2e/e2e-helpers.ts index 7da9118b8..0d7091c80 100644 --- a/tests/e2e/e2e-helpers.ts +++ b/tests/e2e/e2e-helpers.ts @@ -411,6 +411,25 @@ export async function setSettings( ); } +/** + * True when a stored setting is not equal to its value in `settings`. It also + * finds writes from extension pages and commands, which `setSettings` does not see. + */ +export async function storedSettingsDiffer( + worker: BackgroundContext, + settings: Record, +): Promise { + const stored = await withWorker(worker, (context) => + context.evaluate( + (keys) => chrome.storage.local.get(keys), + Object.keys(settings).map((key) => `${SETTINGS_PREFIX}${key}`), + ), + ); + return Object.entries(settings).some( + ([key, value]) => stored[`${SETTINGS_PREFIX}${key}`] !== JSON.stringify(value), + ); +} + export async function setSetting( worker: BackgroundContext, key: string, diff --git a/tests/e2e/full.e2e.test.ts b/tests/e2e/full.e2e.test.ts index 7b65694f2..1141bbd53 100644 --- a/tests/e2e/full.e2e.test.ts +++ b/tests/e2e/full.e2e.test.ts @@ -34,6 +34,7 @@ import { KEY_INSERT_SPACE_AFTER_AUTOCOMPLETE, KEY_AUTOCOMPLETE_ON_ENTER, KEY_CODE_MODE, + DEFAULT_NUM_SUGGESTIONS, } from "../../src/core/domain/constants"; import { SUPPORTED_PREDICTION_LANGUAGE_KEYS } from "../../src/core/domain/lang"; import { grammarRuleSelectionToOverrides } from "../../src/core/domain/grammar/GrammarRuleSettings"; @@ -60,6 +61,7 @@ import { sendExtensionCommand, setSetting, setSettings, + storedSettingsDiffer, sleep, startTestPageServer, takeSettingsWritten, @@ -795,6 +797,23 @@ async function highlightSuggestion( ); } +/** The settings that each test starts from: the defaults, with no grammar rules. */ +const PER_TEST_RESET_SETTINGS = { + // Keep the legacy baseline for non-grammar E2E flows so popup/inline + // prediction scenarios remain deterministic regardless of defaults. + [KEY_ENABLED_GRAMMAR_RULES]: grammarRuleSelectionToOverrides([]), + // Language tests narrow the enabled languages. A narrowed list silently + // changes a later test's language to the first enabled one. + [KEY_ENABLED_LANGUAGES]: SUPPORTED_PREDICTION_LANGUAGE_KEYS, + [KEY_LANGUAGE]: "en_US", + [KEY_FALLBACK_LANGUAGE]: "en_US", + [KEY_AUTO_LANGUAGE_SITE_PRIORS]: {}, + [KEY_SITE_PROFILES]: {}, + [KEY_INLINE_SUGGESTION]: false, + [KEY_NUM_SUGGESTIONS]: DEFAULT_NUM_SUGGESTIONS, + [KEY_MIN_WORD_LENGTH_TO_PREDICT]: 1, +}; + describeE2E(`Extension E2E Test [${BROWSER_TYPE}]`, () => { let browser: Browser; let page: Page; @@ -839,10 +858,9 @@ describeE2E(`Extension E2E Test [${BROWSER_TYPE}]`, () => { // timeout kills the subprocesses of the file: the browser of the whole shard. beforeEach(async () => { worker = await ensureWorker(browser, worker); - if (takeSettingsWritten()) { - // Keep the legacy baseline for non-grammar E2E flows so popup/inline - // prediction scenarios remain deterministic regardless of defaults. - await setGrammarRules(worker, []); + // The popup, the options page and commands write settings without the flag. + if (takeSettingsWritten() || (await storedSettingsDiffer(worker, PER_TEST_RESET_SETTINGS))) { + await setSettings(worker, PER_TEST_RESET_SETTINGS); await notifyConfigChange(browser, worker); takeSettingsWritten(); } @@ -4287,11 +4305,6 @@ describeE2E(`Extension E2E Test [${BROWSER_TYPE}]`, () => { expect(storedLanguage).toBe("de_DE"); await popupPage.close(); - - await setSettings(worker, { - [KEY_ENABLED_LANGUAGES]: SUPPORTED_PREDICTION_LANGUAGE_KEYS, - [KEY_LANGUAGE]: "en_US", - }); }, suiteTimeout(5000, 15000), ); @@ -4678,46 +4691,36 @@ describeE2E(`Extension E2E Test [${BROWSER_TYPE}]`, () => { "Auto-detect keeps the stored global language while typing in a detected language", async () => { const selector = "#test-input"; - try { - await setSettings(worker, { - enable: true, - [KEY_ENABLED_LANGUAGES]: ["en_US", "el_GR"], - [KEY_LANGUAGE]: "auto_detect", - [KEY_FALLBACK_LANGUAGE]: "en_US", - [KEY_AUTO_LANGUAGE_SITE_PRIORS]: {}, - [KEY_SITE_PROFILES]: {}, - [KEY_INLINE_SUGGESTION]: false, - [KEY_MIN_WORD_LENGTH_TO_PREDICT]: 1, - [KEY_NUM_SUGGESTIONS]: 5, - }); - await notifyConfigChange(browser, worker); + await setSettings(worker, { + enable: true, + [KEY_ENABLED_LANGUAGES]: ["en_US", "el_GR"], + [KEY_LANGUAGE]: "auto_detect", + [KEY_FALLBACK_LANGUAGE]: "en_US", + [KEY_AUTO_LANGUAGE_SITE_PRIORS]: {}, + [KEY_SITE_PROFILES]: {}, + [KEY_INLINE_SUGGESTION]: false, + [KEY_MIN_WORD_LENGTH_TO_PREDICT]: 1, + [KEY_NUM_SUGGESTIONS]: 5, + }); + await notifyConfigChange(browser, worker); - await gotoTestPage(page); - await waitForInputReady(page, selector); - await clearInputContent(page, selector); - await typeInInput(page, selector, "φιλο"); - await typeInInput(page, selector, "σ"); - const greekSuggestions = await waitForVisibleSuggestionTexts( - page, - suiteTimeout(12000, 15000), - ).catch(() => []); - if (greekSuggestions.length > 0) { - expect(greekSuggestions.some((text) => text.toLowerCase().includes("φιλοσοφία"))).toBe( - true, - ); - } else { - expect((await getInputContent(page, selector)).toLowerCase()).toContain("φιλοσ"); - } - expect(await getSetting(worker, KEY_LANGUAGE)).toBe("auto_detect"); - } finally { - await setSettings(worker, { - [KEY_LANGUAGE]: "en_US", - [KEY_FALLBACK_LANGUAGE]: "en_US", - [KEY_AUTO_LANGUAGE_SITE_PRIORS]: {}, - [KEY_SITE_PROFILES]: {}, - }); - await notifyConfigChange(browser, worker); + await gotoTestPage(page); + await waitForInputReady(page, selector); + await clearInputContent(page, selector); + await typeInInput(page, selector, "φιλο"); + await typeInInput(page, selector, "σ"); + const greekSuggestions = await waitForVisibleSuggestionTexts( + page, + suiteTimeout(12000, 15000), + ).catch(() => []); + if (greekSuggestions.length > 0) { + expect(greekSuggestions.some((text) => text.toLowerCase().includes("φιλοσοφία"))).toBe( + true, + ); + } else { + expect((await getInputContent(page, selector)).toLowerCase()).toContain("φιλοσ"); } + expect(await getSetting(worker, KEY_LANGUAGE)).toBe("auto_detect"); }, suiteTimeout(20000, 35000), ); @@ -4726,47 +4729,37 @@ describeE2E(`Extension E2E Test [${BROWSER_TYPE}]`, () => { "Auto-detect switches to Arabic for Arabic-script typing", async () => { const selector = "#test-input"; - try { - await setSettings(worker, { - enable: true, - [KEY_ENABLED_LANGUAGES]: ["en_US", "ar_SA"], - [KEY_LANGUAGE]: "auto_detect", - [KEY_FALLBACK_LANGUAGE]: "en_US", - [KEY_AUTO_LANGUAGE_SITE_PRIORS]: {}, - [KEY_SITE_PROFILES]: {}, - [KEY_INLINE_SUGGESTION]: false, - [KEY_MIN_WORD_LENGTH_TO_PREDICT]: 1, - [KEY_NUM_SUGGESTIONS]: 5, - }); - await notifyConfigChange(browser, worker); + await setSettings(worker, { + enable: true, + [KEY_ENABLED_LANGUAGES]: ["en_US", "ar_SA"], + [KEY_LANGUAGE]: "auto_detect", + [KEY_FALLBACK_LANGUAGE]: "en_US", + [KEY_AUTO_LANGUAGE_SITE_PRIORS]: {}, + [KEY_SITE_PROFILES]: {}, + [KEY_INLINE_SUGGESTION]: false, + [KEY_MIN_WORD_LENGTH_TO_PREDICT]: 1, + [KEY_NUM_SUGGESTIONS]: 5, + }); + await notifyConfigChange(browser, worker); - await gotoTestPage(page); - await waitForInputReady(page, selector); - await clearInputContent(page, selector); - await typeInInput(page, selector, "الي"); - let latest: string[] = []; - await waitUntil( - "Arabic auto-detect suggestion", - async () => { - latest = await getVisibleSuggestionTexts(page).catch(() => []); - return latest.some((text) => text.includes("اليوم")) ? latest : false; - }, - { timeoutMs: suiteTimeout(12000, 15000) }, - ).catch(() => { - throw new Error( - `Expected an Arabic suggestion containing "اليوم", got: ${latest.join(" | ")}`, - ); - }); - expect(await getSetting(worker, KEY_LANGUAGE)).toBe("auto_detect"); - } finally { - await setSettings(worker, { - [KEY_LANGUAGE]: "en_US", - [KEY_FALLBACK_LANGUAGE]: "en_US", - [KEY_AUTO_LANGUAGE_SITE_PRIORS]: {}, - [KEY_SITE_PROFILES]: {}, - }); - await notifyConfigChange(browser, worker); - } + await gotoTestPage(page); + await waitForInputReady(page, selector); + await clearInputContent(page, selector); + await typeInInput(page, selector, "الي"); + let latest: string[] = []; + await waitUntil( + "Arabic auto-detect suggestion", + async () => { + latest = await getVisibleSuggestionTexts(page).catch(() => []); + return latest.some((text) => text.includes("اليوم")) ? latest : false; + }, + { timeoutMs: suiteTimeout(12000, 15000) }, + ).catch(() => { + throw new Error( + `Expected an Arabic suggestion containing "اليوم", got: ${latest.join(" | ")}`, + ); + }); + expect(await getSetting(worker, KEY_LANGUAGE)).toBe("auto_detect"); }, suiteTimeout(20000, 35000), ); @@ -4775,83 +4768,73 @@ describeE2E(`Extension E2E Test [${BROWSER_TYPE}]`, () => { "CMD_TOGGLE_FT_ACTIVE_LANG creates an auto-detect session lock without persisting site overrides", async () => { const selector = "#test-input"; - try { - await setSettings(worker, { - enable: true, - [KEY_ENABLED_LANGUAGES]: ["en_US", "el_GR"], - [KEY_LANGUAGE]: "auto_detect", - [KEY_FALLBACK_LANGUAGE]: "en_US", - [KEY_AUTO_LANGUAGE_SITE_PRIORS]: {}, - [KEY_SITE_PROFILES]: {}, - [KEY_INLINE_SUGGESTION]: false, - [KEY_MIN_WORD_LENGTH_TO_PREDICT]: 1, - [KEY_NUM_SUGGESTIONS]: 5, - }); - await notifyConfigChange(browser, worker); - - await gotoTestPage(page); - await waitForInputReady(page, selector); - await clearInputContent(page, selector); - await typeInInput(page, selector, "φιλο"); - await typeInInput(page, selector, "σ"); - const greekSuggestions = await waitForVisibleSuggestionTexts( - page, - suiteTimeout(12000, 15000), - ).catch(() => []); - if (greekSuggestions.length > 0) { - expect(greekSuggestions.some((text) => text.toLowerCase().includes("φιλοσοφία"))).toBe( - true, - ); - } else { - expect((await getInputContent(page, selector)).toLowerCase()).toContain("φιλοσ"); - } - await triggerCommandForTesting(worker, "CMD_TOGGLE_FT_ACTIVE_LANG"); + await setSettings(worker, { + enable: true, + [KEY_ENABLED_LANGUAGES]: ["en_US", "el_GR"], + [KEY_LANGUAGE]: "auto_detect", + [KEY_FALLBACK_LANGUAGE]: "en_US", + [KEY_AUTO_LANGUAGE_SITE_PRIORS]: {}, + [KEY_SITE_PROFILES]: {}, + [KEY_INLINE_SUGGESTION]: false, + [KEY_MIN_WORD_LENGTH_TO_PREDICT]: 1, + [KEY_NUM_SUGGESTIONS]: 5, + }); + await notifyConfigChange(browser, worker); - const globalLanguage = await waitForSettingMatch( - worker, - KEY_LANGUAGE, - (value) => value === "auto_detect", - suiteTimeout(3000, 7000), + await gotoTestPage(page); + await waitForInputReady(page, selector); + await clearInputContent(page, selector); + await typeInInput(page, selector, "φιλο"); + await typeInInput(page, selector, "σ"); + const greekSuggestions = await waitForVisibleSuggestionTexts( + page, + suiteTimeout(12000, 15000), + ).catch(() => []); + if (greekSuggestions.length > 0) { + expect(greekSuggestions.some((text) => text.toLowerCase().includes("φιλοσοφία"))).toBe( + true, ); - expect(globalLanguage).toBe("auto_detect"); + } else { + expect((await getInputContent(page, selector)).toLowerCase()).toContain("φιλοσ"); + } + await triggerCommandForTesting(worker, "CMD_TOGGLE_FT_ACTIVE_LANG"); - const siteProfiles = await getSetting>(worker, KEY_SITE_PROFILES); - expect(siteProfiles ?? {}).toEqual({}); + const globalLanguage = await waitForSettingMatch( + worker, + KEY_LANGUAGE, + (value) => value === "auto_detect", + suiteTimeout(3000, 7000), + ); + expect(globalLanguage).toBe("auto_detect"); - const sitePriors = await waitForSettingMatch>>( - worker, - KEY_AUTO_LANGUAGE_SITE_PRIORS, - (value) => - Boolean( - value?.[TEST_HOST] && - typeof value[TEST_HOST].en_US === "number" && - value[TEST_HOST].en_US > 0, - ), - suiteTimeout(3000, 7000), - ); - expect(sitePriors?.[TEST_HOST]?.en_US).toBeGreaterThan(0); + const siteProfiles = await getSetting>(worker, KEY_SITE_PROFILES); + expect(siteProfiles ?? {}).toEqual({}); - await page.bringToFront(); - await clearInputContent(page, selector); - await typeInInput(page, selector, "φιλο"); - await typeInInput(page, selector, "σ"); - const lockedGreekSuggestions = await waitForVisibleSuggestionTexts( - page, - suiteTimeout(12000, 15000), - ).catch(() => []); - if (lockedGreekSuggestions.length > 0) { - expect(lockedGreekSuggestions.length).toBeGreaterThan(0); - } else { - expect((await getInputContent(page, selector)).toLowerCase()).toContain("φιλοσ"); - } - } finally { - await setSettings(worker, { - [KEY_LANGUAGE]: "en_US", - [KEY_FALLBACK_LANGUAGE]: "en_US", - [KEY_AUTO_LANGUAGE_SITE_PRIORS]: {}, - [KEY_SITE_PROFILES]: {}, - }); - await notifyConfigChange(browser, worker); + const sitePriors = await waitForSettingMatch>>( + worker, + KEY_AUTO_LANGUAGE_SITE_PRIORS, + (value) => + Boolean( + value?.[TEST_HOST] && + typeof value[TEST_HOST].en_US === "number" && + value[TEST_HOST].en_US > 0, + ), + suiteTimeout(3000, 7000), + ); + expect(sitePriors?.[TEST_HOST]?.en_US).toBeGreaterThan(0); + + await page.bringToFront(); + await clearInputContent(page, selector); + await typeInInput(page, selector, "φιλο"); + await typeInInput(page, selector, "σ"); + const lockedGreekSuggestions = await waitForVisibleSuggestionTexts( + page, + suiteTimeout(12000, 15000), + ).catch(() => []); + if (lockedGreekSuggestions.length > 0) { + expect(lockedGreekSuggestions.length).toBeGreaterThan(0); + } else { + expect((await getInputContent(page, selector)).toLowerCase()).toContain("φιλοσ"); } }, suiteTimeout(30000, 50000), @@ -5579,16 +5562,7 @@ describeE2E(`Extension E2E Test [${BROWSER_TYPE}]`, () => { "Grammar Rule Engine formats measurement units only in verified prose typing", async () => { const selector = "#test-input"; - await setGrammarRules(worker, ["measurementUnitFormatting"]); - await setSettings(worker, { - [KEY_LANGUAGE]: "en_US", - [KEY_MIN_WORD_LENGTH_TO_PREDICT]: 1, - }); - await notifyConfigChange(browser, worker); - await gotoTestPage(page); - await waitForInputReady(page, selector); - - await clearInputContent(page, selector); + await openEnglishField(selector, ["measurementUnitFormatting"]); await typeInInput(page, selector, "Mass: 10kg "); await waitUntil( "measurement separator", @@ -5616,7 +5590,6 @@ describeE2E(`Extension E2E Test [${BROWSER_TYPE}]`, () => { async () => (await getInputContent(page, selector)) === "Masa: 1,50\u00a0kg ", { timeoutMs: suiteTimeout(5000, 8000) }, ); - await setSetting(worker, KEY_LANGUAGE, "en_US"); }, suiteTimeout(25000, 40000), );