From 5984cb2e751ebd71250ae3e0f63fb501b66935c4 Mon Sep 17 00:00:00 2001 From: Bartosz Tomczyk Date: Mon, 5 Oct 2026 14:20:54 +0200 Subject: [PATCH 1/3] fix(e2e): enable all languages in the measurement unit test The Arabic auto-detect test sets enabled_languages to ["en_US", "ar_SA"]. With pl_PL disabled, resolveFallbackLanguage changes pl_PL to en_US, and the Polish decimal step fails in an ordered run. The test now uses openEnglishField, which enables all languages. Co-Authored-By: Claude Opus 5.5 --- tests/e2e/full.e2e.test.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/tests/e2e/full.e2e.test.ts b/tests/e2e/full.e2e.test.ts index 7b65694f2..69cea8892 100644 --- a/tests/e2e/full.e2e.test.ts +++ b/tests/e2e/full.e2e.test.ts @@ -5579,16 +5579,9 @@ 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); + // Enables every language too: the pl_PL step below falls back to the + // first enabled language when an earlier test left pl_PL disabled. + await openEnglishField(selector, ["measurementUnitFormatting"]); await typeInInput(page, selector, "Mass: 10kg "); await waitUntil( "measurement separator", From a3e98b3a24b2a66d7509afa6c4c42fee153fb2ba Mon Sep 17 00:00:00 2001 From: Bartosz Tomczyk Date: Mon, 5 Oct 2026 14:32:32 +0200 Subject: [PATCH 2/3] fix(e2e): reset the language settings before each full-suite test Seven tests narrow enabled_languages and do not restore it. With a narrowed list, resolveFallbackLanguage changes the language of a later test to the first enabled language. The beforeEach baseline now sets the enabled languages, the language, the fallback language, the site profiles and the auto-detect site priors to their defaults. It writes them in one storage call with the grammar rules. The measurement unit test no longer resets the language at its end. Co-Authored-By: Claude Opus 5.5 --- tests/e2e/full.e2e.test.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/e2e/full.e2e.test.ts b/tests/e2e/full.e2e.test.ts index 69cea8892..db49deebd 100644 --- a/tests/e2e/full.e2e.test.ts +++ b/tests/e2e/full.e2e.test.ts @@ -840,9 +840,18 @@ describeE2E(`Extension E2E Test [${BROWSER_TYPE}]`, () => { 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, []); + await setSettings(worker, { + // 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]: {}, + }); await notifyConfigChange(browser, worker); takeSettingsWritten(); } @@ -5579,8 +5588,6 @@ describeE2E(`Extension E2E Test [${BROWSER_TYPE}]`, () => { "Grammar Rule Engine formats measurement units only in verified prose typing", async () => { const selector = "#test-input"; - // Enables every language too: the pl_PL step below falls back to the - // first enabled language when an earlier test left pl_PL disabled. await openEnglishField(selector, ["measurementUnitFormatting"]); await typeInInput(page, selector, "Mass: 10kg "); await waitUntil( @@ -5609,7 +5616,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), ); From 503631debef2e928b3b7f90548789b7644be2a69 Mon Sep 17 00:00:00 2001 From: Bartosz Tomczyk Date: Mon, 5 Oct 2026 14:38:53 +0200 Subject: [PATCH 3/3] fix(e2e): reset the full-suite baseline after all setting writes The beforeEach reset ran only after writes from the test helpers. The popup, the options page and commands also write settings, and those changes leaked into the next test. The hook now also reads the baseline keys and resets them when they are not equal to the baseline. The baseline now also sets inline_suggestion, num_suggestions and min_word_length to their defaults. The language resets in the auto-detect tests and in the popup language test are removed, because the baseline does them. Co-Authored-By: Claude Opus 5.5 --- tests/e2e/e2e-helpers.ts | 19 +++ tests/e2e/full.e2e.test.ts | 310 +++++++++++++++++-------------------- 2 files changed, 161 insertions(+), 168 deletions(-) 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 db49deebd..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,19 +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()) { - await setSettings(worker, { - // 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]: {}, - }); + // 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(); } @@ -4296,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), ); @@ -4687,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), ); @@ -4735,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), ); @@ -4784,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),