0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-05 15:18:34 +08:00
discourse/spec/system/locale_spec.rb
Alan Guo Xiang Tan 9e83a1b304
DEV: Reuse the Playwright browser context between system specs (#41304)
This PR adds a `CAPYBARA_PLAYWRIGHT_SOFT_RESET` env flag which reuses
the Playwright browser context between system specs so each example no
longer pays the full bundle load cost.

`capybara-playwright-driver` already keeps the browser process alive,
but its reset closes the context after every example. That throws away
the context-level HTTP cache and other browser work cached with the
context, so the next example pays the bundle load cost again.

Key changes:
* Add `PlaywrightSoftReset` in `spec/support/system/capybara_patches.rb`
so `Capybara::Playwright::Driver#reset!` clears storage, cookies, and
permissions instead of closing the context. This keeps per-example
isolation while leaving the browser caches warm.
* Detect installed fake clocks by probing `setTimeout.toString()` on the
fresh page during the soft reset. Playwright's clock is context-scoped
and cannot be uninstalled, so any example that installed one (via
`BrowserTime.freeze` or `pw_page.clock.install` directly) automatically
gets a full context reset without having to declare anything.
* Detect downloads by listening for the context's `download` event.
Contexts that produced downloads fall back to a full reset so
Playwright's own artifact lifecycle handles cleanup, avoiding races with
reused contexts.
* Clear `ExtraLocalesController`'s bundle digest cache in `TestSetup`
whenever leftover translation overrides are detected (the same branch
that already calls `I18n.reload!`). Overrides change locale bundle
contents but specs that create them with `create!`/`update_columns`
bypass the digest invalidation that `TranslationOverride.upsert!`
performs, so a warm HTTP cache could serve stale bundles. A fresh digest
changes the bundle URL, which bypasses every cache naturally.
* Store downloads under a worker-local `Downloads::FOLDER` so per-worker
cleanup cannot delete another parallel worker's artifacts.
* Block service workers in system specs (`serviceWorkers: "block"`).
Letting each example register a service worker that the soft reset then
force-clears caused mass spec timeouts on CI. The exact cause requires
further investigation, but system specs currently do not rely on service
workers so blocking them is an acceptable trade-off.
* Measure the impact on a 16-vCPU DO droplet over three rounds, where
the core system suite dropped from ~843s to ~721s (~14.5%).

The optimization is opt-in via `CAPYBARA_PLAYWRIGHT_SOFT_RESET=1`, which
CI sets only for the core system and chat plugin system jobs while the
optimization proves itself.
2026-07-03 09:07:32 +08:00

210 lines
6.6 KiB
Ruby
Vendored
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# frozen_string_literal: true
RSpec.describe "Locale choice" do
it "loads english locale successfully" do
visit "/"
expect(page).to have_css("html[lang='en']")
expect(page).to have_css(
"#navigation-bar .categories",
text: I18n.t("js.filters.categories.title", locale: :en),
)
expect(page.evaluate_script("moment.locale()")).to eq("en")
expect(page.evaluate_script("I18n._mfMessages.locale")).to eq("en")
end
it "loads french locale successfully" do
SiteSetting.default_locale = "fr"
visit "/"
expect(page).to have_css("html[lang='fr']")
expect(page).to have_css(
"#navigation-bar .categories",
text: I18n.t("js.filters.categories.title", locale: :fr),
)
expect(page.evaluate_script("moment.locale()")).to eq("fr")
expect(page.evaluate_script("I18n._mfMessages.locale")).to eq("fr")
end
it "loads polish locale successfully" do
SiteSetting.default_locale = "pl_PL"
visit "/"
expect(page).to have_css("html[lang='pl-PL']")
expect(page).to have_css(
"#navigation-bar .categories",
text: I18n.t("js.filters.categories.title", locale: :pl_PL),
)
expect(page.evaluate_script("moment.locale()")).to eq("pl")
expect(page.evaluate_script("I18n._mfMessages.locale")).to eq("pl-PL")
end
context "with test locales" do
before do
JsLocaleHelper.clear_cache!
JsLocaleHelper.set_translations(
"en",
"en" => {
"js" => {
"only_english" => "1-en",
"english_and_site" => "3-en",
"english_and_user" => "5-en",
"all_three" => "7-en",
},
},
)
JsLocaleHelper.set_translations(
"uk",
"uk" => {
"js" => {
"only_user" => "4-uk",
"english_and_user" => "5-uk",
"site_and_user" => "6-uk",
"all_three" => "7-uk",
},
},
)
end
after { JsLocaleHelper.clear_cache! }
it "handles fallback correctly" do
expected = {
"none" => "[uk.js.none]",
"only_english" => "1-en",
"only_site" => "[uk.js.only_site]",
"english_and_site" => "3-en",
"only_user" => "4-uk",
"english_and_user" => "5-uk",
"site_and_user" => "6-uk",
"all_three" => "7-uk",
}
SiteSetting.default_locale = "uk"
visit "/"
expect(page.evaluate_script("I18n.locale")).to eq("uk")
expect(page.evaluate_script("Object.keys(I18n.translations)")).to contain_exactly("uk", "en")
expect(page.evaluate_script("I18n.translations.uk.js").keys).to contain_exactly(
"all_three",
"english_and_user",
"only_user",
"site_and_user",
)
expect(page.evaluate_script("I18n.translations.en.js").keys).to contain_exactly(
"only_english",
"english_and_site",
)
expected.each do |key, expect|
expect(page.evaluate_script("I18n.t(#{"js.#{key}".inspect})")).to eq(expect)
end
end
end
context "with messageformat overrides" do
fab!(:overriden_translation_en) do
Fabricate(
:translation_override,
translation_key: "admin_js.admin.user.penalty_history_MF",
value: "OVERRIDEN",
)
end
fab!(:overriden_translation_ja) do
Fabricate(:translation_override, locale: "ja", translation_key: "js.posts_likes_MF")
end
fab!(:overriden_translation_zh_tw) do
Fabricate(:translation_override, locale: "zh_TW", translation_key: "js.posts_likes_MF")
end
before do
overriden_translation_ja.update_columns(
value: "{ count, plural, one {返信 # 件、} other {返信 # 件、} }",
)
overriden_translation_zh_tw.update_columns(value: "{ count, plural, ")
# update_columns skips the model layer, so burst the bundle digest cache
# like TranslationOverride.upsert! would have.
ExtraLocalesController.clear_cache!
end
it "works for english" do
SiteSetting.default_locale = "en"
visit "/"
expect(page.evaluate_script("Object.keys(I18n._mfMessages._data)")).to eq(["en"])
expect(
page.evaluate_script("I18n._mfMessages.get('posts_likes_MF', {count: 3, ratio: 'med'})"),
).to eq("3 replies, very high like to post ratio, jump to the first or last post…\n")
expect(
page.evaluate_script(
"I18n._mfMessages.get('admin.user.penalty_history_MF', { SUSPENDED: 3, SILENCED: 2 })",
),
).to eq("OVERRIDEN")
end
it "works for other locales" do
SiteSetting.default_locale = "fr"
visit "/"
expect(page.evaluate_script("Object.keys(I18n._mfMessages._data)")).to contain_exactly(
"en",
"fr",
)
expect(
page.evaluate_script("I18n._mfMessages.get('posts_likes_MF', {count: 3, ratio: 'med'})"),
).to eq(
"3 réponses, avec un taux très élevé de « J'aime » par publication, accéder à la première ou dernière publication...\n",
)
page.evaluate_script("delete I18n._mfMessages._data.fr.posts_likes_MF")
expect(
page.evaluate_script("I18n._mfMessages.get('posts_likes_MF', {count: 3, ratio: 'med'})"),
).to eq("3 replies, very high like to post ratio, jump to the first or last post…\n")
expect(
page.evaluate_script(
"I18n._mfMessages.get('admin.user.penalty_history_MF', { SUSPENDED: 3, SILENCED: 2 })",
),
).to eq(
"Au cours des 6 derniers mois, cet utilisateur a été <b>suspendu 3 fois</b> et <b>mis en sourdine 2 fois</b>.",
)
page.evaluate_script("delete I18n._mfMessages._data.fr['admin.user.penalty_history_MF']")
expect(
page.evaluate_script(
"I18n._mfMessages.get('admin.user.penalty_history_MF', { SUSPENDED: 3, SILENCED: 2 })",
),
).to eq("OVERRIDEN")
end
it "does not throw error for invalid plural keys" do
SiteSetting.default_locale = "ja"
visit "/"
expect(page.evaluate_script("Object.keys(I18n._mfMessages._data)")).to contain_exactly(
"ja",
"en",
)
expect(
page.evaluate_script("I18n._mfMessages.get('posts_likes_MF', {count: 3, ratio: 'med'})"),
).to eq("返信 3 件、")
end
it "does not throw error for malformed messages" do
SiteSetting.default_locale = "zh_TW"
# Suppress the expected error from the intentionally malformed zh_TW MF string
silence_stdout { visit "/" }
expect(page.evaluate_script("Object.keys(I18n._mfMessages._data)").length).to eq(0)
expect(
page.evaluate_script("I18n._mfMessages.get('posts_likes_MF', {count: 3, ratio: 'med'})"),
).to eq("posts_likes_MF")
end
end
end