diff --git a/.rubocop/todo.yml b/.rubocop/todo.yml index e468a4e81..e0d3fa39b 100644 --- a/.rubocop/todo.yml +++ b/.rubocop/todo.yml @@ -690,6 +690,7 @@ Metrics/ModuleLength: - 'lib/signature_detection.rb' - 'lib/static_assets.rb' - 'lib/transaction_dispatcher.rb' + - 'spec/support/capybara/common_actions.rb' Metrics/PerceivedComplexity: Exclude: diff --git a/app/assets/javascripts/app/controllers/_application_controller/table.coffee b/app/assets/javascripts/app/controllers/_application_controller/table.coffee index 5ee35e98b..4971a20ba 100644 --- a/app/assets/javascripts/app/controllers/_application_controller/table.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller/table.coffee @@ -1038,12 +1038,13 @@ class App.ControllerTable extends App.Controller # update store @preferencesStore('order', 'customOrderBy', @orderBy) @preferencesStore('order', 'customOrderDirection', @orderDirection) - render = => - @renderTableFull(false, skipHeadersResize: true) - App.QueueManager.add('tableRender', render) if @sortRenderCallback App.QueueManager.add('tableRender', @sortRenderCallback) + else + render = => + @renderTableFull(false, skipHeadersResize: true) + App.QueueManager.add('tableRender', render) App.QueueManager.run('tableRender') diff --git a/spec/support/capybara/browser_test_helper.rb b/spec/support/capybara/browser_test_helper.rb index 8c1a71d00..50cfbaa44 100644 --- a/spec/support/capybara/browser_test_helper.rb +++ b/spec/support/capybara/browser_test_helper.rb @@ -68,9 +68,11 @@ module BrowserTestHelper # await_empty_ajax_queue # def await_empty_ajax_queue - wait(5, interval: 0.5).until_constant do + wait(5, interval: 0.1).until_constant do page.evaluate_script('App.Ajax.queue().length').zero? end + rescue + nil end # Moves the mouse from its current position by the given offset. @@ -112,6 +114,7 @@ module BrowserTestHelper # def release_mouse page.driver.browser.action.release.perform + await_empty_ajax_queue end class Waiter < SimpleDelegator diff --git a/spec/support/capybara/common_actions.rb b/spec/support/capybara/common_actions.rb index 9e6df222e..021aa1439 100644 --- a/spec/support/capybara/common_actions.rb +++ b/spec/support/capybara/common_actions.rb @@ -38,6 +38,8 @@ module CommonActions wait(4).until_exists do current_login end + + await_empty_ajax_queue end # Checks if the current session is logged in. @@ -120,6 +122,16 @@ module CommonActions route = "/##{route}" end super(route) + + # wait for AJAX requets only on WebApp visits + return if !route.start_with?('/#') + return if route == '/#logout' + + # make sure all AJAX requests are done + await_empty_ajax_queue + + # make sure loading is completed (e.g. ticket zoom may take longer) + expect(page).to have_no_css('.icon-loading', wait: 30) end # Overwrites the global Capybara.always_include_port setting (true) diff --git a/spec/support/capybara/custom_extensions.rb b/spec/support/capybara/custom_extensions.rb index e4a6e93d4..a3d8785ba 100644 --- a/spec/support/capybara/custom_extensions.rb +++ b/spec/support/capybara/custom_extensions.rb @@ -34,3 +34,108 @@ class Capybara::Node::Element raise "Element still moving after #{checks} checks" end end + +module ZammadCapybarActionDelegator + def select(*) + super.tap do + await_empty_ajax_queue + end + end + + def click(*) + super.tap do + await_empty_ajax_queue + end + end + + def click_on(*) + super.tap do + await_empty_ajax_queue + end + end + + def click_link_or_button(*) + super.tap do + await_empty_ajax_queue + end + end + + def click_button(*) + super.tap do + await_empty_ajax_queue + end + end + + def select_option(*) + super.tap do + await_empty_ajax_queue + end + end + + def send_keys(*) + super.tap do + await_empty_ajax_queue + end + end +end + +module ZammadCapybarSelectorDelegator + def find_field(*) + ZammadCapybaraElementDelegator.new(element: super, context: self) + end + + def find_button(*) + ZammadCapybaraElementDelegator.new(element: super, context: self) + end + + def find_by_id(*) + ZammadCapybaraElementDelegator.new(element: super, context: self) + end + + def find_link(*) + ZammadCapybaraElementDelegator.new(element: super, context: self) + end + + def find(*) + ZammadCapybaraElementDelegator.new(element: super, context: self) + end + + def first(*) + ZammadCapybaraElementDelegator.new(element: super, context: self) + end + + def all(*) + super.map { |element| ZammadCapybaraElementDelegator.new(element: element, context: self) } + end +end + +class ZammadCapybaraSessionDelegator < SimpleDelegator + extend Forwardable + + def_delegator :@context, :await_empty_ajax_queue + + include ZammadCapybarSelectorDelegator + + def initialize(context:, element:) + @context = context + + super(element) + end +end + +class ZammadCapybaraElementDelegator < ZammadCapybaraSessionDelegator + include ZammadCapybarActionDelegator +end + +module CapybaraCustomExtensions + include ZammadCapybarActionDelegator + include ZammadCapybarSelectorDelegator + + def page(*) + ZammadCapybaraSessionDelegator.new(element: super, context: self) + end +end + +RSpec.configure do |config| + config.include CapybaraCustomExtensions, type: :system +end diff --git a/spec/system/basic/authentication_spec.rb b/spec/system/basic/authentication_spec.rb index b151f00e7..b141372a3 100644 --- a/spec/system/basic/authentication_spec.rb +++ b/spec/system/basic/authentication_spec.rb @@ -15,12 +15,12 @@ RSpec.describe 'Authentication', type: :system do it 'Logout' do logout - expect_current_route 'login', wait: 2 + expect_current_route 'login', wait: 10 end it 'will unset user attributes after logout' do logout - expect_current_route 'login', wait: 2 + expect_current_route 'login', wait: 10 visit '/#signup' @@ -31,20 +31,20 @@ RSpec.describe 'Authentication', type: :system do it 'Login and redirect to requested url', authenticated_as: false do visit 'ticket/zoom/1' - expect_current_route 'login', wait: 2 + expect_current_route 'login', wait: 10 login( username: 'master@example.com', password: 'test', ) - expect_current_route 'ticket/zoom/1', wait: 2 + expect_current_route 'ticket/zoom/1', wait: 10 end it 'Login and redirect to requested url via external authentication', authenticated_as: false do visit 'ticket/zoom/1' - expect_current_route 'login', wait: 2 + expect_current_route 'login', wait: 10 # simulate jump to external ressource visit 'https://www.zammad.org' @@ -59,7 +59,7 @@ RSpec.describe 'Authentication', type: :system do # jump back and check if origin requested url is shown visit '' - expect_current_route 'ticket/zoom/1', wait: 2 + expect_current_route 'ticket/zoom/1', wait: 10 expect(current_login).to eq('master@example.com') end diff --git a/spec/system/dashboard_spec.rb b/spec/system/dashboard_spec.rb index 5eaab2f39..ba8dc8773 100644 --- a/spec/system/dashboard_spec.rb +++ b/spec/system/dashboard_spec.rb @@ -30,7 +30,6 @@ RSpec.describe 'Dashboard', type: :system, authenticated_as: true do fill_in 'Lastname', with: 'Braun' fill_in 'Email', with: 'nick.braun@zammad.org' click_on 'Invite' - await_empty_ajax_queue expect(User.find_by(firstname: 'Nick').roles).to eq([Role.find_by(name: 'Public')]) end end @@ -42,6 +41,7 @@ RSpec.describe 'Dashboard', type: :system, authenticated_as: true do before do ensure_websocket(check_if_pinged: false) + sleep 3 # fast relog causes raise conditions in websocket server end context 'Logout by frontend plugin - Default', authenticated_as: :authenticate do diff --git a/spec/system/examples/pagination_examples.rb b/spec/system/examples/pagination_examples.rb index c10d44a5e..4dfba3343 100644 --- a/spec/system/examples/pagination_examples.rb +++ b/spec/system/examples/pagination_examples.rb @@ -1,46 +1,46 @@ # Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/ RSpec.shared_examples 'pagination' do |model:, klass:, path:, sort_by: :name| - def prepare(model) + let(:model) { model } + + def authenticate create_list(model, 500) + true end - it 'does paginate' do - prepare(model) + it 'does paginate', authenticated_as: :authenticate do visit path - refresh # more stability expect(page).to have_css('.js-pager', wait: 10) class_page1 = klass.order(sort_by => :asc, id: :asc).offset(50).first expect(page).to have_text(class_page1.name, wait: 10) + expect(page).to have_css('.js-page.is-selected', text: '1') - page.first('.js-page', text: '2').click - await_empty_ajax_queue + page.first('.js-page', text: '2', wait: 10).click class_page2 = klass.order(sort_by => :asc, id: :asc).offset(175).first expect(page).to have_text(class_page2.name, wait: 10) + expect(page).to have_css('.js-page.is-selected', text: '2') - page.first('.js-page', text: '3').click - await_empty_ajax_queue + page.first('.js-page', text: '3', wait: 10).click class_page3 = klass.order(sort_by => :asc, id: :asc).offset(325).first expect(page).to have_text(class_page3.name, wait: 10) + expect(page).to have_css('.js-page.is-selected', text: '3') - page.first('.js-page', text: '4').click - await_empty_ajax_queue + page.first('.js-page', text: '4', wait: 10).click class_page4 = klass.order(sort_by => :asc, id: :asc).offset(475).first expect(page).to have_text(class_page4.name, wait: 10) + expect(page).to have_css('.js-page.is-selected', text: '4') - page.first('.js-page', text: '1').click - await_empty_ajax_queue + page.first('.js-page', text: '1', wait: 10).click page.first('.js-tableHead[data-column-key=name]').click - await_empty_ajax_queue expect(page).to have_text(class_page1.name, wait: 10) + expect(page).to have_css('.js-page.is-selected', text: '1') page.first('.js-tableHead[data-column-key=name]').click - await_empty_ajax_queue class_last = klass.order(sort_by => :desc).first expect(page).to have_text(class_last.name, wait: 10) end diff --git a/spec/system/manage/users_spec.rb b/spec/system/manage/users_spec.rb index 21db6f176..6c5b111ff 100644 --- a/spec/system/manage/users_spec.rb +++ b/spec/system/manage/users_spec.rb @@ -42,12 +42,12 @@ RSpec.describe 'Manage > Users', type: :system do visit 'manage/users' within(:active_content) do - row = find("tr[data-id=\"#{user.id}\"]") + row = find("tr[data-id=\"#{user.id}\"]", wait: 10) row.find('.js-action').click row.find('.js-switchTo').click end - await_empty_ajax_queue + expect(page).to have_text("Zammad looks like this for \"#{user.firstname} #{user.lastname}\"", wait: 10) end end end diff --git a/spec/system/system/object_manager_spec.rb b/spec/system/system/object_manager_spec.rb index 9e39a1070..c0acbc340 100644 --- a/spec/system/system/object_manager_spec.rb +++ b/spec/system/system/object_manager_spec.rb @@ -63,8 +63,6 @@ RSpec.describe 'Admin Panel > Objects', type: :system, authenticated_as: true do 'nulloption' => true, 'maxlength' => 255 } - await_empty_ajax_queue - expect(ObjectManager::Attribute.last.data_option).to eq(expected_data_options) end end diff --git a/spec/system/system/translations_spec.rb b/spec/system/system/translations_spec.rb index d13fe064b..a37059a8c 100644 --- a/spec/system/system/translations_spec.rb +++ b/spec/system/system/translations_spec.rb @@ -15,6 +15,6 @@ RSpec.describe 'System > Translations', type: :system do click '.js-syncChanges' - modal_ready && modal_disappear # make sure test is not terminated while modal is visible + modal_disappear # make sure test is not terminated while modal is visible end end diff --git a/spec/system/ticket/create_spec.rb b/spec/system/ticket/create_spec.rb index 184fd546f..d504e46cc 100644 --- a/spec/system/ticket/create_spec.rb +++ b/spec/system/ticket/create_spec.rb @@ -204,8 +204,6 @@ RSpec.describe 'Ticket Create', type: :system do encrypt_button = find('.js-securityEncrypt', wait: 5) sign_button = find('.js-securitySign', wait: 5) - await_empty_ajax_queue - active_button_class = '.btn--active' expect(encrypt_button.matches_css?(active_button_class, wait: 2)).to be(encrypt) expect(sign_button.matches_css?(active_button_class, wait: 2)).to be(sign) @@ -234,8 +232,6 @@ RSpec.describe 'Ticket Create', type: :system do within(:active_content) do use_template(template) - await_empty_ajax_queue - select new_group.name, from: 'group_id' end end @@ -378,7 +374,6 @@ RSpec.describe 'Ticket Create', type: :system do click_on 'Link issue' fill_in 'link', with: ENV['GITLAB_ISSUE_LINK'] click_on 'Submit' - await_empty_ajax_queue # verify issue content = find('.sidebar-git-issue-content') @@ -390,7 +385,6 @@ RSpec.describe 'Ticket Create', type: :system do # create Ticket click '.js-submit' - await_empty_ajax_queue # check stored data expect(Ticket.last.preferences[:gitlab][:issue_links][0]).to eq(ENV['GITLAB_ISSUE_LINK']) @@ -432,7 +426,6 @@ RSpec.describe 'Ticket Create', type: :system do click_on 'Link issue' fill_in 'link', with: ENV['GITHUB_ISSUE_LINK'] click_on 'Submit' - await_empty_ajax_queue # verify issue content = find('.sidebar-git-issue-content') @@ -444,7 +437,6 @@ RSpec.describe 'Ticket Create', type: :system do # create Ticket click '.js-submit' - await_empty_ajax_queue # check stored data expect(Ticket.last.preferences[:github][:issue_links][0]).to eq(ENV['GITHUB_ISSUE_LINK']) diff --git a/spec/system/ticket/update_spec.rb b/spec/system/ticket/update_spec.rb index ef8630fe4..adc01f901 100644 --- a/spec/system/ticket/update_spec.rb +++ b/spec/system/ticket/update_spec.rb @@ -35,7 +35,7 @@ RSpec.describe 'Ticket Update', type: :system do select('closed', from: 'state_id') click('.js-attributeBar .js-submit') - expect(page).to have_no_css('.js-submitDropdown .js-submit[disabled]', wait: 2) + expect(page).to have_no_css('.js-submitDropdown .js-submit[disabled]', wait: 10) end # the update should have failed and thus the ticket is still in the new state @@ -45,7 +45,7 @@ RSpec.describe 'Ticket Update', type: :system do # update should work now find(".edit [name=#{attribute.name}]").select('name 2') click('.js-attributeBar .js-submit') - expect(page).to have_no_css('.js-submitDropdown .js-submit[disabled]', wait: 2) + expect(page).to have_no_css('.js-submitDropdown .js-submit[disabled]', wait: 10) end ticket.reload @@ -185,7 +185,7 @@ RSpec.describe 'Ticket Update', type: :system do it 'tickets history of both tickets should show the merge event' do visit "#ticket/zoom/#{origin_ticket.id}" within(:active_content) do - expect(page).to have_css('.js-actions .dropdown-toggle', wait: 3) + expect(page).to have_css('.js-actions .dropdown-toggle', wait: 10) click '.js-actions .dropdown-toggle' click '.js-actions .dropdown-menu [data-type="ticket-history"]' diff --git a/spec/system/ticket/view_spec.rb b/spec/system/ticket/view_spec.rb index 822515ce1..c3c3b28dc 100644 --- a/spec/system/ticket/view_spec.rb +++ b/spec/system/ticket/view_spec.rb @@ -112,9 +112,9 @@ RSpec.describe 'Ticket views', type: :system do release_mouse - await_empty_ajax_queue - - expect(Ticket.first.articles.last.subject).to eq('macro note') + expect do + wait(10, interval: 0.1).until { Ticket.first.articles.last.subject == 'macro note' } + end.not_to raise_error end end end @@ -136,12 +136,9 @@ RSpec.describe 'Ticket views', type: :system do click '.js-submit' end - await_empty_ajax_queue - - expect([ - ticket1.articles.last&.body, - ticket2.articles.last&.body - ]).to be_all note + expect do + wait(10, interval: 0.1).until { [ ticket1.articles.last&.body, ticket2.articles.last&.body ] == [note, note] } + end.not_to raise_error end end @@ -184,9 +181,9 @@ RSpec.describe 'Ticket views', type: :system do it 'does basic view test of tickets' do visit 'ticket/view/my_tickets' - expect(page).to have_text(ticket.title) + expect(page).to have_text(ticket.title, wait: 10) click_on 'My Organization Tickets' - expect(page).to have_text(ticket.title) + expect(page).to have_text(ticket.title, wait: 10) end end end diff --git a/spec/system/ticket/zoom_spec.rb b/spec/system/ticket/zoom_spec.rb index 03923cb6b..2ca4bbff5 100644 --- a/spec/system/ticket/zoom_spec.rb +++ b/spec/system/ticket/zoom_spec.rb @@ -448,7 +448,7 @@ RSpec.describe 'Ticket zoom', type: :system do end context 'button is hidden on the go' do - let(:setting_delete_timeframe) { 5 } + let(:setting_delete_timeframe) { 10 } let(:user) { agent } let(:item) { 'article_note_self' } @@ -692,8 +692,6 @@ RSpec.describe 'Ticket zoom', type: :system do encrypt_button = find('.js-securityEncrypt', wait: 5) sign_button = find('.js-securitySign', wait: 5) - await_empty_ajax_queue - active_button_class = '.btn--active' expect(encrypt_button.matches_css?(active_button_class, wait: 2)).to be(encrypt) expect(sign_button.matches_css?(active_button_class, wait: 2)).to be(sign) @@ -709,8 +707,6 @@ RSpec.describe 'Ticket zoom', type: :system do within(:active_content) do all('a[data-type=emailReply]').last.click find('.articleNewEdit-body').send_keys('Test') - - await_empty_ajax_queue end end @@ -726,8 +722,6 @@ RSpec.describe 'Ticket zoom', type: :system do all('a[data-type=emailReply]').last.click find('.articleNewEdit-body').send_keys('Test') - await_empty_ajax_queue - select new_group.name, from: 'group_id' end end @@ -883,8 +877,6 @@ RSpec.describe 'Ticket zoom', type: :system do find(:richtext).execute_script "this.innerHTML = \"#{ticket_article_body}\"" find('.js-submit').click end - - await_empty_ajax_queue end def forward @@ -893,8 +885,6 @@ RSpec.describe 'Ticket zoom', type: :system do fill_in 'To', with: 'customer@example.com' find('.js-submit').click end - - await_empty_ajax_queue end def images_identical?(image_a, image_b) @@ -986,7 +976,7 @@ RSpec.describe 'Ticket zoom', type: :system do visit "ticket/zoom/#{ticket.id}" refresh # refresh to have assets generated for ticket - expect(page).to have_select('state_id', options: %w[new open closed]) + expect(page).to have_select('state_id', options: %w[new open closed], wait: 10) expect(page).to have_no_select('priority_id') expect(page).to have_no_select('owner_id') expect(page).to have_no_css('div.tabsSidebar-tab[data-tab=customer]') @@ -1119,11 +1109,10 @@ RSpec.describe 'Ticket zoom', type: :system do # make sure ticket is done loading and change will be pushed via WS find(:active_ticket_article, ticket_article) - await_empty_ajax_queue ticket_note.update!(internal: false) - expect(page).to have_selector(:active_ticket_article, ticket_note) + expect(page).to have_selector(:active_ticket_article, ticket_note, wait: 10) end end end @@ -1190,37 +1179,37 @@ RSpec.describe 'Ticket zoom', type: :system do it 'previous is not clickable for the first item' do open_nth_item(0) - expect { click '.pagination .previous' }.not_to change { current_url } + expect { click '.pagination .previous' }.not_to change { page.find('.content.active')[:id] } end it 'next is clickable for the first item' do open_nth_item(0) - expect { click '.pagination .next' }.to change { current_url } + expect { click '.pagination .next' }.to change { page.find('.content.active')[:id] } end it 'previous is clickable for the middle item' do open_nth_item(1) - expect { click '.pagination .previous' }.to change { current_url } + expect { click '.pagination .previous' }.to change { page.find('.content.active')[:id] } end it 'next is clickable for the middle item' do open_nth_item(1) - expect { click '.pagination .next' }.to change { current_url } + expect { click '.pagination .next' }.to change { page.find('.content.active')[:id] } end it 'previous is clickable for the last item' do open_nth_item(2) - expect { click '.pagination .previous' }.to change { current_url } + expect { click '.pagination .previous' }.to change { page.find('.content.active')[:id] } end it 'next is not clickable for the last item' do open_nth_item(2) - expect { click '.pagination .next' }.not_to change { current_url } + expect { click '.pagination .next' }.not_to change { page.find('.content.active')[:id] } end def open_nth_item(nth) @@ -1243,8 +1232,6 @@ RSpec.describe 'Ticket zoom', type: :system do visit "ticket/zoom/#{ticket_a.id}" - await_empty_ajax_queue - visit 'ticket/view/all_unassigned' end @@ -1252,7 +1239,7 @@ RSpec.describe 'Ticket zoom', type: :system do within :active_content do click_on ticket_a.title - expect(page).to have_css('.pagination-counter') + expect(page).to have_css('.pagination-counter', wait: 10) end end @@ -1262,7 +1249,7 @@ RSpec.describe 'Ticket zoom', type: :system do visit 'dashboard' visit "ticket/zoom/#{ticket_a.id}" - expect(page).to have_css('.pagination-counter') + expect(page).to have_css('.pagination-counter', wait: 10) end end end @@ -1293,7 +1280,6 @@ RSpec.describe 'Ticket zoom', type: :system do # wait for article to be added to the page expect(page).to have_css('.ticket-article-item', count: 1) - await_empty_ajax_queue # create a on-the-fly article with attachment that will get pushed to open browser article1 = create(:ticket_article, ticket: ticket) @@ -1310,7 +1296,6 @@ RSpec.describe 'Ticket zoom', type: :system do # wait for article to be added to the page expect(page).to have_css('.ticket-article-item', count: 2, wait: 10) - await_empty_ajax_queue # click on forward of created article within :active_ticket_article, article1 do @@ -1327,7 +1312,6 @@ RSpec.describe 'Ticket zoom', type: :system do click '.js-submit' # wait for article to be added to the page - await_empty_ajax_queue expect(page).to have_css('.ticket-article-item', count: 3) # check if attachment was forwarded successfully @@ -1376,7 +1360,6 @@ RSpec.describe 'Ticket zoom', type: :system do it 'not shown to customer' do visit "ticket/zoom/#{ticket.id}" - await_empty_ajax_queue within :active_content do expect(page).to have_no_css('.controls[data-name=pending_time]') @@ -1389,7 +1372,6 @@ RSpec.describe 'Ticket zoom', type: :system do ticket.update(pending_time: 1.day.from_now, state: Ticket::State.lookup(name: 'pending reminder')) visit "ticket/zoom/#{ticket.id}" - await_empty_ajax_queue end let(:ticket) { Ticket.first } @@ -1436,7 +1418,6 @@ RSpec.describe 'Ticket zoom', type: :system do ensure_websocket do visit "ticket/zoom/#{ticket.id}" - await_empty_ajax_queue within :active_ticket_article, article do expect(page).to have_css(%(a[href="#{url}"])) @@ -1457,44 +1438,33 @@ RSpec.describe 'Ticket zoom', type: :system do end end + def check_obscured(top: true, middle: true, bottom: true, scroll_y: 0) + expect(page).to have_text(ticket.title, wait: 10) + wait(5, interval: 0.2).until do + scroll_y != find('.ticketZoom').native.location.y + end + expect(page).to have_css("div#article-content-#{article_at_the_top.id}", obscured: top, wait: 10) + expect(page).to have_css("div#article-content-#{article_in_the_middle.id}", obscured: middle, wait: 10) + expect(page).to have_css("div#article-content-#{article_at_the_bottom.id}", obscured: bottom, wait: 10) + find('.ticketZoom').native.location.y + end + it 'scrolls to given Article ID' do ensure_websocket do - visit "ticket/zoom/#{ticket.id}/#{article_in_the_middle.id}" - await_empty_ajax_queue - # workaround because browser scrolls in test initially to the bottom - # maybe because the articles are not present?! - refresh + visit "ticket/zoom/#{ticket.id}" + y = check_obscured(bottom: false) # scroll to article in the middle of the page - within :active_content do - find("div#article-content-#{article_in_the_middle.id}").in_fixed_position(wait: 0.5) - - expect(find("div#article-content-#{article_at_the_top.id}")).to be_obscured - expect(find("div#article-content-#{article_in_the_middle.id}")).not_to be_obscured - expect(find("div#article-content-#{article_at_the_bottom.id}")).to be_obscured - end + visit "ticket/zoom/#{ticket.id}/#{article_in_the_middle.id}" + y = check_obscured(middle: false, scroll_y: y) # scroll to article at the top of the page visit "ticket/zoom/#{ticket.id}/#{article_at_the_top.id}" - await_empty_ajax_queue - within :active_content do - find("div#article-content-#{article_in_the_middle.id}").in_fixed_position(wait: 0.5) - - expect(find("div#article-content-#{article_at_the_top.id}")).not_to be_obscured - expect(find("div#article-content-#{article_in_the_middle.id}")).to be_obscured - expect(find("div#article-content-#{article_at_the_bottom.id}")).to be_obscured - end + y = check_obscured(top: false, scroll_y: y) # scroll to article at the bottom of the page visit "ticket/zoom/#{ticket.id}/#{article_at_the_bottom.id}" - await_empty_ajax_queue - within :active_content do - find("div#article-content-#{article_in_the_middle.id}").in_fixed_position(wait: 0.5) - - expect(find("div#article-content-#{article_at_the_top.id}")).to be_obscured - expect(find("div#article-content-#{article_in_the_middle.id}")).to be_obscured - expect(find("div#article-content-#{article_at_the_bottom.id}")).not_to be_obscured - end + check_obscured(bottom: false, scroll_y: y) end end end @@ -1503,12 +1473,11 @@ RSpec.describe 'Ticket zoom', type: :system do it 'will properly show the "See more" link if you switch between the ticket and the dashboard on new articles' do ensure_websocket do visit "ticket/zoom/#{ticket.id}" - await_empty_ajax_queue visit 'dashboard' expect(page).to have_css("a.js-dashboardMenuItem[data-key='Dashboard'].is-active", wait: 10) article_id = create(:'ticket/article', ticket: ticket, body: "#{SecureRandom.uuid} #{"lorem ipsum\n" * 200}") - expect(page).to have_css('div.tasks a.is-modified', wait: 10) + expect(page).to have_css('div.tasks a.is-modified', wait: 30) visit "ticket/zoom/#{ticket.id}" within :active_content do @@ -1533,7 +1502,6 @@ RSpec.describe 'Ticket zoom', type: :system do visit "ticket/zoom/#{ticket.id}" find('.js-openDropdownMacro').click find(:macro, macro.id).click - await_empty_ajax_queue expect(ticket.reload.articles.last.body).to eq(macro_body) expect(ticket.reload.articles.last.content_type).to eq('text/html') @@ -1598,7 +1566,6 @@ RSpec.describe 'Ticket zoom', type: :system do click_on 'Link issue' fill_in 'link', with: ENV['GITLAB_ISSUE_LINK'] click_on 'Submit' - await_empty_ajax_queue # verify issue content = find('.sidebar-git-issue-content') @@ -1615,7 +1582,6 @@ RSpec.describe 'Ticket zoom', type: :system do # delete issue click(".sidebar-git-issue-delete span[data-issue-id='#{ENV['GITLAB_ISSUE_LINK']}']") - await_empty_ajax_queue content = find('.sidebar[data-tab=gitlab] .sidebar-content') expect(content).to have_text('No linked issues') @@ -1658,7 +1624,6 @@ RSpec.describe 'Ticket zoom', type: :system do click_on 'Link issue' fill_in 'link', with: ENV['GITHUB_ISSUE_LINK'] click_on 'Submit' - await_empty_ajax_queue # verify issue content = find('.sidebar-git-issue-content') @@ -1675,7 +1640,6 @@ RSpec.describe 'Ticket zoom', type: :system do # delete issue click(".sidebar-git-issue-delete span[data-issue-id='#{ENV['GITHUB_ISSUE_LINK']}']") - await_empty_ajax_queue content = find('.sidebar[data-tab=github] .sidebar-content') expect(content).to have_text('No linked issues') @@ -1707,7 +1671,6 @@ RSpec.describe 'Ticket zoom', type: :system do expect(page).to have_text(ticket_open.title, wait: 20) visit "#ticket/zoom/#{ticket_open.id}" - click '.tabsSidebar-tab[data-tab=customer]' click '.user-tickets[data-type=closed]' expect(page).to have_text(ticket_closed.title, wait: 20) end diff --git a/test/browser/abb_one_group_test.rb b/test/browser/abb_one_group_test.rb index 94970b20b..a84938f04 100644 --- a/test/browser/abb_one_group_test.rb +++ b/test/browser/abb_one_group_test.rb @@ -159,7 +159,7 @@ class AgentTicketActionLevel0Test < TestCase css: '.newTicket', value: 'New Ticket', ) - exists_not(css: '.newTicket select[name="group_id"]') + exists(css: '.newTicket .form-group.hide select[name="group_id"]') set( css: '.newTicket input[name="title"]', diff --git a/test/browser/agent_ticket_attachment_test.rb b/test/browser/agent_ticket_attachment_test.rb index 4678d3c94..b77f56a29 100644 --- a/test/browser/agent_ticket_attachment_test.rb +++ b/test/browser/agent_ticket_attachment_test.rb @@ -30,14 +30,7 @@ class AgentTicketAttachmentTest < TestCase sleep 1 # submit form - click(css: '.content.active .js-submit') - sleep 2 - - # check warning - alert = @browser.switch_to.alert - alert.dismiss() - #alert.accept() - #alert = alert.text + click(css: '.content.active .js-submit', expect_alert: true) # since selenium webdriver with firefox is not able to upload files, skipp here # https://github.com/w3c/webdriver/issues/1230 @@ -90,12 +83,7 @@ class AgentTicketAttachmentTest < TestCase ) # submit form - click(css: '.content.active .js-submit') - sleep 2 - - # check warning - alert = @browser.switch_to.alert - alert.dismiss() + click(css: '.content.active .js-submit', expect_alert: true) # add attachment, attachment check should quiet file_upload( @@ -111,12 +99,6 @@ class AgentTicketAttachmentTest < TestCase # submit form click(css: '.content.active .js-submit') - sleep 2 - - # no warning - #alert = @browser.switch_to.alert - - # check if article exists # discard changes should gone away watch_for_disappear( diff --git a/test/browser/agent_ticket_macro_test.rb b/test/browser/agent_ticket_macro_test.rb index 823d51e8c..918f40141 100644 --- a/test/browser/agent_ticket_macro_test.rb +++ b/test/browser/agent_ticket_macro_test.rb @@ -160,7 +160,12 @@ class AgentTicketMacroTest < TestCase # when we re-enter the Zoom view for a ticket via the overview tasks_close_all() - sleep 8 # to update overview list to open correct/next ticket in overview + overview_open( + link: '#ticket/view/all_unassigned', + ) + + await_text(text: ticket1[:title]) + await_text(text: ticket2[:title]) ticket_open_by_overview( title: ticket1[:title], diff --git a/test/browser/agent_ticket_overview_level0_test.rb b/test/browser/agent_ticket_overview_level0_test.rb index c237c0cee..5b288cebe 100644 --- a/test/browser/agent_ticket_overview_level0_test.rb +++ b/test/browser/agent_ticket_overview_level0_test.rb @@ -67,6 +67,9 @@ class AgentTicketOverviewLevel0Test < TestCase css: %(.content.active table tr td input[value="#{ticket2[:id]}"][type="checkbox"]:checked), ) + # remember current overview count + overview_counter_before = overview_counter() + # select close state & submit select( css: '.content.active .bulkAction [name="state_id"]', @@ -92,6 +95,7 @@ class AgentTicketOverviewLevel0Test < TestCase ) # remember current overview count + await_overview_counter(view: '#ticket/view/all_unassigned', count: overview_counter_before['#ticket/view/all_unassigned'] - 2) overview_counter_before = overview_counter() # click options and enable number and article count @@ -188,11 +192,9 @@ class AgentTicketOverviewLevel0Test < TestCase body: 'overview count test #3', } ) - sleep 6 # get new overview count - overview_counter_new = overview_counter() - assert_equal(overview_counter_before['#ticket/view/all_unassigned'] + 1, overview_counter_new['#ticket/view/all_unassigned']) + await_overview_counter(view: '#ticket/view/all_unassigned', count: overview_counter_before['#ticket/view/all_unassigned'] + 1) # open ticket by search ticket_open_by_search( @@ -206,11 +208,9 @@ class AgentTicketOverviewLevel0Test < TestCase state: 'closed', } ) - sleep 6 # get current overview count - overview_counter_after = overview_counter() - assert_equal(overview_counter_before['#ticket/view/all_unassigned'], overview_counter_after['#ticket/view/all_unassigned']) + await_overview_counter(view: '#ticket/view/all_unassigned', count: overview_counter_before['#ticket/view/all_unassigned']) # cleanup tasks_close_all() @@ -334,8 +334,7 @@ class AgentTicketOverviewLevel0Test < TestCase ) # get new overview count - overview_counter_new = overview_counter() - assert_equal(overview_counter_before['#ticket/view/all_unassigned'] - 2, overview_counter_new['#ticket/view/all_unassigned']) + await_overview_counter(view: '#ticket/view/all_unassigned', count: overview_counter_before['#ticket/view/all_unassigned'] - 2) # open ticket by search ticket_open_by_search( @@ -463,8 +462,7 @@ class AgentTicketOverviewLevel0Test < TestCase ) # get new overview count - overview_counter_new = overview_counter() - assert_equal(overview_counter_before['#ticket/view/all_unassigned'] - 2, overview_counter_new['#ticket/view/all_unassigned']) + await_overview_counter(view: '#ticket/view/all_unassigned', count: overview_counter_before['#ticket/view/all_unassigned'] - 2) # cleanup tasks_close_all() diff --git a/test/browser/agent_ticket_overview_tab_test.rb b/test/browser/agent_ticket_overview_tab_test.rb index d0b241274..b2c084113 100644 --- a/test/browser/agent_ticket_overview_tab_test.rb +++ b/test/browser/agent_ticket_overview_tab_test.rb @@ -3,6 +3,20 @@ require 'browser_test_helper' class AgentTicketOverviewTabTest < TestCase + def task_count_equals(count) + + retries ||= 0 + assert_equal(count, @browser.find_elements(css: '.tasks .task').count) + rescue + retries += 1 + if retries < 5 + sleep 1 + retry + end + raise e + + end + def test_i @browser = browser_instance login( @@ -54,7 +68,7 @@ class AgentTicketOverviewTabTest < TestCase link: '#ticket/view/all_unassigned', ) - assert_equal(1, @browser.find_elements(css: '.tasks .task').count) + task_count_equals(1) ticket_update( data: { @@ -70,7 +84,7 @@ class AgentTicketOverviewTabTest < TestCase timeout: 8, ) - assert_equal(1, @browser.find_elements(css: '.tasks .task').count) + task_count_equals(1) ticket_update( data: { @@ -80,7 +94,7 @@ class AgentTicketOverviewTabTest < TestCase task_type: 'closeTab', # default: stayOnTab / possible: closeTab, closeNextInOverview, stayOnTab ) - assert_equal(0, @browser.find_elements(css: '.tasks .task').count) + task_count_equals(0) # cleanup tasks_close_all() diff --git a/test/browser/agent_user_manage_test.rb b/test/browser/agent_user_manage_test.rb index 15e335568..b6a2fc8d1 100644 --- a/test/browser/agent_user_manage_test.rb +++ b/test/browser/agent_user_manage_test.rb @@ -4,8 +4,9 @@ require 'browser_test_helper' class AgentUserManageTest < TestCase def test_agent_customer_ticket_create - customer_user_email = "customer-test-#{rand(999_999)}@example.com" - firstname = 'Customer Firstname' + random_number = rand(999_999) + customer_user_email = "customer-test-#{random_number}@example.com" + firstname = "Customer Firstname #{random_number}" lastname = 'Customer Lastname' fullname = "#{firstname} #{lastname} <#{customer_user_email}>" @@ -21,10 +22,10 @@ class AgentUserManageTest < TestCase click(css: 'a[href="#new"]', only_if_exists: true) click(css: 'a[href="#ticket/create"]') - watch_for( - css: '.content.active .newTicket', - timeout: 1, - ) + await_text(text: 'New Ticket') + + # Rumors say there is a modal reaper which will kill your modals if you dont sleep before a new ticket create + sleep 3 click(css: '.content.active .newTicket [name="customer_id_completion"]') @@ -77,11 +78,13 @@ class AgentUserManageTest < TestCase css: '.content.active .newTicket input[name="customer_id_completion"]', value: fullname, ) - sleep 4 # call new ticket screen again tasks_close_all() + # wait for user get indexed in elastic search + await_global_search(query: random_number) + click(css: 'a[href="#new"]', only_if_exists: true) click(css: 'a[href="#ticket/create"]') diff --git a/test/browser/form_test.rb b/test/browser/form_test.rb index b63e3a421..22f3be877 100644 --- a/test/browser/form_test.rb +++ b/test/browser/form_test.rb @@ -160,13 +160,11 @@ class FormTest < TestCase value: "some text\nnew line", ) click( - browser: customer, - css: 'body div.zammad-form-modal button[type="submit"]', + browser: customer, + css: 'body div.zammad-form-modal button[type="submit"]', + expect_alert: true, ) - # check warning - alert = customer.switch_to.alert - alert.dismiss() sleep 10 # fill form invalid data - within correct time diff --git a/test/browser/switch_to_user_test.rb b/test/browser/switch_to_user_test.rb index 1ad160c48..90435df28 100644 --- a/test/browser/switch_to_user_test.rb +++ b/test/browser/switch_to_user_test.rb @@ -28,7 +28,8 @@ class SwitchToUserTest < TestCase css: '.content.active .dropdown--actions', ) click( - css: '.content.active .icon-switchView', + css: '.content.active .icon-switchView', + ajax: false ) sleep 3 @@ -42,7 +43,7 @@ class SwitchToUserTest < TestCase ) login = @browser.find_elements({ css: '.user-menu .user a' })[0].attribute('title') assert_equal(login, 'nicole.braun@zammad.org') - click(css: '.switchBackToUser .js-close') + click(css: '.switchBackToUser .js-close', ajax: false) sleep 5 login = @browser.find_elements({ css: '.user-menu .user a' })[0].attribute('title') diff --git a/test/browser_test_helper.rb b/test/browser_test_helper.rb index f87768923..83991aca8 100644 --- a/test/browser_test_helper.rb +++ b/test/browser_test_helper.rb @@ -542,6 +542,12 @@ class TestCase < ActiveSupport::TestCase sleep 0.2 if !params[:fast] sleep params[:wait] if params[:wait] + + if params[:expect_alert] + check_alert(params) + else + await_empty_ajax_queue(params) + end end =begin @@ -825,6 +831,7 @@ class TestCase < ActiveSupport::TestCase end sleep 0.2 + await_empty_ajax_queue(params) end =begin @@ -880,6 +887,8 @@ class TestCase < ActiveSupport::TestCase dropdown.select_by(:text, params[:value]) #puts "select2 - #{params.inspect}" end + + await_empty_ajax_queue(params) end =begin @@ -1265,16 +1274,17 @@ set type of task (closeTab, closeNextInOverview, stayOnTab) =end - def verify_task(params = {}, fallback = false) + def verify_task(params = {}) switch_window_focus(params) log('verify_task', params) instance = params[:browser] || @browser data = params[:data] - sleep 1 - begin + retries ||= 0 + sleep 1 + # verify title if data[:title] title = instance.find_elements(css: '.tasks .is-active')[0].text.strip @@ -1318,10 +1328,8 @@ set type of task (closeTab, closeNextInOverview, stayOnTab) end end rescue => e - # just try again - if !fallback - verify_task(params, true) - end + retries += 1 + retry if retries < 5 raise "ERROR: #{e.inspect}" end true @@ -1572,6 +1580,7 @@ wait untill text in selector disabppears .key_up(:control) .perform screenshot(browser: instance, comment: 'shortcut_after') + await_empty_ajax_queue(params) end =begin @@ -1588,6 +1597,7 @@ wait untill text in selector disabppears log('window_keys', params) instance = params[:browser] || @browser instance.action.send_keys(params[:value]).perform + await_empty_ajax_queue(params) end =begin @@ -2122,19 +2132,14 @@ wait untill text in selector disabppears mute_log: true, ) - found = false - 7.times do - element = instance.find_elements(css: '.content.active .newTicket')[0] - if element - found = true - break - end - sleep 1 - end - if !found - screenshot(browser: instance, comment: 'ticket_create_failed') - raise 'no ticket create screen found!' - end + watch_for( + browser: instance, + css: '.content.active .newTicket', + timeout: 30, + ) + + # Rumors say there is a modal reaper which will kill your modals if you dont sleep before a new ticket create + sleep 3 if data[:group] if data[:group] == '-NONE-' @@ -2570,6 +2575,7 @@ wait untill text in selector disabppears end instance.find_elements(css: '.content.active .js-submit')[0].click + await_empty_ajax_queue(params) # do not stay on tab if params[:task_type] == 'closeTab' || params[:task_type] == 'closeNextInOverview' @@ -2867,7 +2873,7 @@ wait untill text in selector disabppears instance = params[:browser] || @browser instance.find_elements(css: '.js-overviewsMenuItem')[0].click - sleep 2 + await_empty_ajax_queue(params) execute( browser: instance, @@ -2878,18 +2884,28 @@ wait untill text in selector disabppears # js: '$(".content.active .overview-header").css("display", "none")', #) - overviews = {} - instance.find_elements(css: '.content.active .sidebar a[href]').each do |element| - url = element.attribute('href') - url.gsub!(%r{(http|https)://.+?/(.+?)$}, '\\2') - overviews[url] = 0 - #puts url.inspect - #puts element.inspect - end - overviews.each_key do |url| - count = instance.find_elements(css: ".content.active .sidebar a[href=\"#{url}\"] .badge")[0].text - overviews[url] = count.to_i + begin + overviews = {} + instance.find_elements(css: '.content.active .sidebar a[href]').each do |element| + url = element.attribute('href') + url.gsub!(%r{(http|https)://.+?/(.+?)$}, '\\2') + overviews[url] = 0 + #puts url.inspect + #puts element.inspect + end + + overviews.each_key do |url| + count = instance.find_elements(css: ".content.active .sidebar a[href=\"#{url}\"] .badge")[0].text + overviews[url] = count.to_i + end + rescue => e + retries ||= 0 + retries += 1 + sleep 0.5 + retry if retries < 5 + raise e end + log('overview_counter', overviews) overviews end @@ -3340,6 +3356,7 @@ wait untill text in selector disabppears element.send_keys(data[:name]) instance.find_elements(css: '.modal button.js-submit')[0].click + await_empty_ajax_queue(params) modal_disappear( browser: instance, timeout: 5, @@ -3458,6 +3475,7 @@ wait untill text in selector disabppears element.clear element.send_keys(data[:first_response_time_in_text]) instance.find_elements(css: '.modal button.js-submit')[0].click + await_empty_ajax_queue(params) modal_disappear(browser: instance) 7.times do element = instance.find_elements(css: 'body')[0] @@ -3661,6 +3679,7 @@ wait untill text in selector disabppears dropdown.select_by(:text, data[:signature]) end instance.find_elements(css: '.modal button.js-submit')[0].click + await_empty_ajax_queue(params) modal_disappear(browser: instance) element = instance.find_elements(css: 'body')[0] @@ -3673,6 +3692,7 @@ wait untill text in selector disabppears data[:member]&.each do |member| instance.find_elements(css: 'a[href="#manage"]')[0].click sleep 1 + scroll_to(params.merge(css: '.content.active a[href="#manage/users"]')) instance.find_elements(css: '.content.active a[href="#manage/users"]')[0].click sleep 3 element = instance.find_elements(css: '.content.active [name="search"]')[0] @@ -3685,6 +3705,7 @@ wait untill text in selector disabppears #instance.find_elements(:css => 'label:contains(" ' + action[:name] + '")')[0].click instance.execute_script(%($(".js-groupList tr:contains(\\"#{data[:name]}\\") .js-groupListItem[value=#{member[:access]}]").prop("checked", true))) instance.find_elements(css: '.modal button.js-submit')[0].click + await_empty_ajax_queue(params) modal_disappear(browser: instance) end end @@ -3933,6 +3954,8 @@ wait untill text in selector disabppears css: '.content.active a[href="#manage/roles"]', mute_log: true, ) + + await_text(container: '.content.active table tr td', text: data[:name]) instance.execute_script(%($('.content.active table tr td:contains(" #{data[:name]}")').first().click())) modal_ready(browser: instance) @@ -4830,4 +4853,124 @@ wait untill text in selector disabppears screenshot(browser: instance, comment: "object_manager_attribute_#{action}_failed") raise "object_manager_attribute_#{action}_failed" end + + def check_alert(params = {}) + instance = params[:browser] || @browser + + tries = 5 + begin + alert = instance.switch_to.alert + alert.dismiss() + rescue e + tries -= 1 + sleep 0.5 + retry if tries.positive? + raise e + end + end + +=begin + + This function waits for ajax requests and object form flow to be done + + await_empty_ajax_queue + +=end + + def await_empty_ajax_queue(params = {}) + return if params[:ajax] == false + + instance = params[:browser] || @browser + + 10.times do + sleep 0.5 + + break if instance.execute_script('return typeof(App) === "undefined"') + break if instance.execute_script('return App.Ajax.queue().length').zero? + end + end + +=begin + + This function waits for a text to be ready in the dom. By default it searches in the active content. + + await_text(text: 'New Ticket') + + await_text(text: 'New Ticket', container: 'body') + +=end + + def await_text(params) + return if params[:ajax] == false + + instance = params[:browser] || @browser + + container = '.content.active' + if params[:container] + container = params[:container] + end + + 20.times do + log('await_text', params) + + break if instance.execute_script("return $(\"#{container}:contains('#{params[:text]}')\").length").positive? + + sleep 0.5 + end + end + +=begin + + This function waits for the overview_counter to return a specific result. + + await_overview_counter(view: '#ticket/view/all_unassigned', count: overview_counter_before['#ticket/view/all_unassigned'] - 2) + +=end + + def await_overview_counter(params) + result = nil + 40.times do + result = overview_counter + + if result[ params[:view] ] != params[:count] + sleep 0.5 + next + end + + break + end + + assert_equal(params[:count], result[ params[:view] ]) + end + +=begin + + This function waits for a search result to be available in the global search. + It can help to verify if a user is indexed in elastic search. + + await_global_search(query: 'customer 1 firstname') + +=end + + def await_global_search(params) + instance = params[:browser] || @browser + + 30.times do + log('await_global_search', params) + + set( + css: 'input#global-search', + value: params[:query], + ) + + break if instance.execute_script("return $(\"ul.global-search-result:visible:contains('#{params[:query]}')\").length") == 1 + + sleep 0.5 + end + + set( + css: 'input#global-search', + value: '', + ) + end end