2
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-03-03 23:54:20 +08:00
discourse/spec/system/email_change_spec.rb
Alan Guo Xiang Tan 55b05c921b
DEV: Add client settled checks for system tests (#35230)
This change seeks to improve the reliability of our system tests by
resolving the lack of consistency in the state of the client side
application between steps in a system test. This is achieved by patching
various action methods in `Capybara::Playwright::Node` and
`Capybara::Playwright::Browser` so that the methods execute an async
JavaScript function on the client side that waits for the client side
application to reach a settled state. A settled state is currently
defined as:

1. No inflight ajax requests. (_messageBus and presence requests are
excluded_)
2. 2 event cycles of the Javascript event loop has happened for for all
"click", "input", "mousedown", "keydown", "focusin", "focusout",
"touchstart", "change", "resize", "scroll" DOM events that fired.

For debugging purposes, a `--debug-client-settled` CLI flag has been
added to `bin/rspec`. When used, detailed debugging information will be
printed to the browser's console as well as to `stdout` of the
`bin/rspec` process.

This change was inspired by
https://evilmartians.com/chronicles/flaky-tests-be-gone-long-lasting-relief-chronic-ci-retry-irritation
and the https://github.com/makandra/capybara-lockstep rubygem.
2025-10-10 11:03:18 +08:00

165 lines
4.8 KiB
Ruby

# frozen_string_literal: true
describe "Changing email", type: :system do
fab!(:password) { "mysupersecurepassword" }
fab!(:user) { Fabricate(:user, active: true, password: password) }
let(:new_email) { "newemail@example.com" }
let(:user_preferences_security_page) { PageObjects::Pages::UserPreferencesSecurity.new }
let(:user_preferences_page) { PageObjects::Pages::UserPreferences.new }
before { Jobs.run_immediately! }
def generate_confirm_link
visit "/my/preferences/account"
email_dropdown = PageObjects::Components::SelectKit.new(".email-dropdown")
expect(email_dropdown.visible?).to eq(true)
email_dropdown.select_row_by_value("updateEmail")
find("#change-email").fill_in with: "newemail@example.com"
find(".save-button button").click
wait_for(timeout: Capybara.default_max_wait_time * 2) do
ActionMailer::Base.deliveries.count === 1
end
if user.admin?
get_link_from_email(:old)
else
get_link_from_email(:new)
end
end
def get_link_from_email(type)
mail = ActionMailer::Base.deliveries.last
expect(mail.to).to contain_exactly(type == :new ? new_email : user.email)
mail.body.to_s[%r{/u/confirm-#{type}-email/\S+}, 0]
end
it "allows regular user to change their email" do
sign_in user
visit generate_confirm_link
find(".confirm-new-email .btn-primary").click
expect(page).to have_css(".dialog-body", text: I18n.t("js.user.change_email.confirm_success"))
find(".dialog-footer .btn-primary").click
expect(user.reload.primary_email.email).to eq(new_email)
end
it "works when user has totp 2fa", dump_threads_on_failure: true do
SiteSetting.hide_email_address_taken = false
second_factor = Fabricate(:user_second_factor_totp, user: user)
sign_in user
visit generate_confirm_link
find(".confirm-new-email .btn-primary").click
find(".second-factor-token-input").fill_in with: second_factor.totp_object.now
find("button[type=submit]").click
expect(user.reload.primary_email.email).to eq(new_email)
end
it "works when user has webauthn 2fa" do
with_security_key(user) do
sign_in user
visit generate_confirm_link
find(".confirm-new-email .btn-primary").click
find("#security-key-authenticate-button").click
try_until_success(
reason: "Something about webauthn.js that we can't seem to track properly",
) { expect(user.reload.primary_email.email).to eq(new_email) }
end
end
it "does not require login to confirm email change" do
second_factor = Fabricate(:user_second_factor_totp, user: user)
sign_in user
confirm_link = generate_confirm_link
Capybara.reset_sessions! # log out
visit confirm_link
find(".confirm-new-email .btn-primary").click
find(".second-factor-token-input").fill_in with: second_factor.totp_object.now
find("button[type=submit]:not([disabled])").click
expect(user.reload.primary_email.email).to eq(new_email)
end
it "makes admins verify old email" do
user.update!(admin: true)
sign_in user
confirm_old_link = generate_confirm_link
# Confirm old email
visit confirm_old_link
find(".confirm-old-email .btn-primary").click
expect(page).to have_css(
".dialog-body",
text: I18n.t("js.user.change_email.authorizing_old.confirm_success"),
)
find(".dialog-footer .btn-primary").click
# Confirm new email
wait_for(timeout: Capybara.default_max_wait_time * 2) do
ActionMailer::Base.deliveries.count === 2
end
confirm_new_link = get_link_from_email(:new)
visit confirm_new_link
find(".confirm-new-email .btn-primary").click
expect(page).to have_css(".dialog-body", text: I18n.t("js.user.change_email.confirm_success"))
find(".dialog-footer .btn-primary").click
expect(user.reload.primary_email.email).to eq(new_email)
end
it "allows admin to verify old email while logged out" do
user.update!(admin: true)
sign_in user
confirm_old_link = generate_confirm_link
Capybara.reset_sessions! # log out
# Confirm old email
visit confirm_old_link
find(".confirm-old-email .btn-primary").click
expect(page).to have_css(
".dialog-body",
text: I18n.t("js.user.change_email.authorizing_old.confirm_success"),
)
find(".dialog-footer .btn-primary").click
# Confirm new email
wait_for(timeout: Capybara.default_max_wait_time * 2) do
ActionMailer::Base.deliveries.count === 2
end
confirm_new_link = get_link_from_email(:new)
visit confirm_new_link
find(".confirm-new-email .btn-primary").click
expect(page).to have_css(".dialog-body", text: I18n.t("js.user.change_email.confirm_success"))
find(".dialog-footer .btn-primary").click
expect(user.reload.primary_email.email).to eq(new_email)
end
end