From 4ee9ff58d8eca9bd8144def50df86470a7acabb6 Mon Sep 17 00:00:00 2001 From: Christophe Bliard Date: Mon, 10 Nov 2025 10:51:30 +0100 Subject: [PATCH] Fix flaky spec failing spec: `spec/features/admin/enterprise/enterprise_spec.rb:130` failing run: `https://github.com/opf/openproject/actions/runs/19163991005/job/54780241287?pr=20840` Clicking on some elements in modals can fail as the click location is wrong if the modal animation is not finished yet. Fix the test by waiting for anmiation complete, and extract a page object for enterprise tokens page to prevent repetition. --- .../spec/support/pages/meetings/show.rb | 4 +- .../support/pages/recurring_meeting/show.rb | 2 +- .../admin/enterprise/enterprise_spec.rb | 43 +++++------- spec/support/components/common/modal.rb | 11 ++- .../pages/admin/enterprise_tokens/index.rb | 67 +++++++++++++++++++ 5 files changed, 93 insertions(+), 34 deletions(-) create mode 100644 spec/support/pages/admin/enterprise_tokens/index.rb diff --git a/modules/meeting/spec/support/pages/meetings/show.rb b/modules/meeting/spec/support/pages/meetings/show.rb index d8636e0afa6..6f3abce6ee3 100644 --- a/modules/meeting/spec/support/pages/meetings/show.rb +++ b/modules/meeting/spec/support/pages/meetings/show.rb @@ -126,9 +126,7 @@ module Pages::Meetings end def expect_modal(...) - expect(page).to have_modal(...) - modal = find(:modal, ...) - wait_for_size_animation_completion(modal) + Components::Common::Modal.new.expect_modal(...) end def expect_no_add_form diff --git a/modules/meeting/spec/support/pages/recurring_meeting/show.rb b/modules/meeting/spec/support/pages/recurring_meeting/show.rb index 461ee09ea13..a38e57afaf8 100644 --- a/modules/meeting/spec/support/pages/recurring_meeting/show.rb +++ b/modules/meeting/spec/support/pages/recurring_meeting/show.rb @@ -127,7 +127,7 @@ module Pages::RecurringMeeting end def expect_modal(...) - expect(page).to have_modal(...) + Components::Common::Modal.new.expect_modal(...) end def expect_no_meeting(date:) diff --git a/spec/features/admin/enterprise/enterprise_spec.rb b/spec/features/admin/enterprise/enterprise_spec.rb index f41202af875..dfe2840e247 100644 --- a/spec/features/admin/enterprise/enterprise_spec.rb +++ b/spec/features/admin/enterprise/enterprise_spec.rb @@ -35,10 +35,12 @@ RSpec.describe "Enterprise token", :js do shared_let(:admin) { create(:admin) } + let(:enterprise_tokens_page) { Pages::Admin::EnterpriseTokens::Index.new } + describe "EnterpriseToken management" do before do login_as admin - visit enterprise_tokens_path + enterprise_tokens_page.visit! end it "shows a teaser page and has a button to add a token with a dialog" do @@ -53,14 +55,11 @@ RSpec.describe "Enterprise token", :js do context "with invalid input" do it "shows an error message" do - click_button "Add Enterprise token" - fill_in "Type support token text", with: "foobar" - click_button "Add" + enterprise_tokens_page.add_enterprise_token("foobar") # The dialog is still open with an error message on token field - expect(page).to have_dialog("Add Enterprise token") validation_error = "Enterprise support token can't be read. Are you sure it is a support token?" - expect(page).to have_field("Type support token text", validation_error:) + enterprise_tokens_page.expect_add_token_validation_error(validation_error) end end @@ -74,19 +73,16 @@ RSpec.describe "Enterprise token", :js do token.domain = Setting.host_name end end + let(:modals) { Components::Common::Modal.new } before do allow(OpenProject::Token).to receive(:import).and_return(token_object) end it "allows token import flow" do - click_button "Add Enterprise token" - fill_in "Type support token text", with: "foobar" - click_button "Add" + enterprise_tokens_page.add_enterprise_token("foobar") - expect(page).to have_text("Quick feature overview") - expect(page).to have_css("#enterprise-trial-welcome-dialog video") - page.find('[data-close-dialog-id="enterprise-trial-welcome-dialog"]').click + enterprise_tokens_page.close_welcome_video_modal # Table headers [ @@ -117,7 +113,7 @@ RSpec.describe "Enterprise token", :js do wait_for_network_idle # Expect deletion modal - expect(page).to have_dialog("Delete enterprise token") + modals.expect_modal("Delete enterprise token") within_dialog("Delete enterprise token") do click_button "Delete" end @@ -128,31 +124,22 @@ RSpec.describe "Enterprise token", :js do end it "cannot import same token twice" do - click_button "Add Enterprise token" - fill_in "Type support token text", with: "foobar" - click_button "Add" + enterprise_tokens_page.add_enterprise_token("foobar") - expect(page).to have_text("Quick feature overview") - expect(page).to have_css("#enterprise-trial-welcome-dialog video") - page.find('[data-close-dialog-id="enterprise-trial-welcome-dialog"]').click + enterprise_tokens_page.close_welcome_video_modal - click_button "Add Enterprise token" - fill_in "Type support token text", with: "foobar" - click_button "Add" + # Add the token a second time + enterprise_tokens_page.add_enterprise_token("foobar") # The dialog is still open with an error message on token field - expect(page).to have_dialog("Add Enterprise token") - validation_error = "This token has already been added." - expect(page).to have_field("Type support token text", validation_error:) + enterprise_tokens_page.expect_add_token_validation_error("This token has already been added.") # Try importing with blank spaces and newlines before and after fill_in "Type support token text", with: " \nfoobar \n" click_button "Add" # The dialog is still open with an error message on token field - expect(page).to have_dialog("Add Enterprise token") - validation_error = "This token has already been added." - expect(page).to have_field("Type support token text", validation_error:) + enterprise_tokens_page.expect_add_token_validation_error("This token has already been added.") end end end diff --git a/spec/support/components/common/modal.rb b/spec/support/components/common/modal.rb index 370a67d3282..2a3413748d8 100644 --- a/spec/support/components/common/modal.rb +++ b/spec/support/components/common/modal.rb @@ -37,9 +37,16 @@ module Components include Capybara::RSpecMatchers include Flash::Expectations include RSpec::Matchers + include WaitHelpers - def expect_title(text) - expect(page).to have_modal(text) + def expect_modal(title, wait: Capybara.default_max_wait_time) + expect_title(title, wait:) + modal = find(:modal, title, wait:) + wait_for_size_animation_completion(modal) + end + + def expect_title(text, wait: Capybara.default_max_wait_time) + expect(page).to have_modal(text, wait:) end def expect_open diff --git a/spec/support/pages/admin/enterprise_tokens/index.rb b/spec/support/pages/admin/enterprise_tokens/index.rb new file mode 100644 index 00000000000..81b742ff61c --- /dev/null +++ b/spec/support/pages/admin/enterprise_tokens/index.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +require "support/pages/page" + +module Pages + module Admin + module EnterpriseTokens + class Index < ::Pages::Page + def path + enterprise_tokens_path + end + + def add_enterprise_token(token_text) + click_button "Add Enterprise token" + modals.expect_modal("Add Enterprise token") + fill_in "Type support token text", with: token_text + click_button "Add" + end + + def close_welcome_video_modal + modals.expect_modal("Quick feature overview") + expect(page).to have_css("#enterprise-trial-welcome-dialog video") + page.find('[data-close-dialog-id="enterprise-trial-welcome-dialog"]').click + end + + def expect_add_token_validation_error(message) + expect(page).to have_dialog("Add Enterprise token") + expect(page).to have_field("Type support token text", validation_error: message) + end + + private + + def modals + Components::Common::Modal.new + end + end + end + end +end