mirror of
https://github.com/discourse/discourse.git
synced 2026-08-05 15:18:34 +08:00
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.
210 lines
6.6 KiB
Ruby
Vendored
210 lines
6.6 KiB
Ruby
Vendored
# 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
|