Просмотр исходного кода

Fixes #5491 - Deadlocks on updating taskbars.

Co-authored-by: Florian Liebe <fl@zammad.com>
Rolf Schmidt 3 недель назад
Родитель
Сommit
22a0b20a54

+ 15 - 0
app/jobs/taskbar_update_related_tasks_job.rb

@@ -0,0 +1,15 @@
+# Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/
+
+class TaskbarUpdateRelatedTasksJob < ApplicationJob
+  def perform(task_ids)
+    Taskbar.where(id: task_ids).each do |taskbar|
+      taskbar.with_lock do
+        taskbar.update!(
+          preferences:       taskbar.preferences.merge(tasks: taskbar.collect_related_tasks),
+          local_update:      true,
+          skip_item_trigger: true,
+        )
+      end
+    end
+  end
+end

+ 19 - 18
app/models/taskbar.rb

@@ -29,7 +29,8 @@ class Taskbar < ApplicationModel
   before_update   :update_last_contact, :set_user, :update_preferences_infos
 
   after_update    :notify_clients
-  after_destroy   :update_preferences_infos, :notify_clients
+  after_destroy :update_preferences_infos, :notify_clients
+  after_commit :update_related_taskbars
 
   association_attributes_ignored :user
 
@@ -96,6 +97,14 @@ class Taskbar < ApplicationModel
     save!
   end
 
+  def collect_related_tasks
+    related_taskbars.map(&:preferences_task_info)
+      .tap { |arr| arr.push(preferences_task_info) if !destroyed? }
+      .each_with_object({}) { |elem, memo| reduce_related_tasks(elem, memo) }
+      .values
+      .sort_by { |elem| elem[:id] || Float::MAX } # sort by IDs to pass old tests
+  end
+
   private
 
   def update_last_contact
@@ -125,22 +134,17 @@ class Taskbar < ApplicationModel
   def update_preferences_infos
     return if key == 'Search'
     return if local_update
+    return if changed_only_prio?
 
     preferences = self.preferences || {}
     preferences[:tasks] = collect_related_tasks
 
-    update_related_taskbars(preferences)
-
     # remember preferences for current taskbar
     self.preferences = preferences if !destroyed?
   end
 
-  def collect_related_tasks
-    related_taskbars.map(&:preferences_task_info)
-      .tap { |arr| arr.push(preferences_task_info) if !destroyed? }
-      .each_with_object({}) { |elem, memo| reduce_related_tasks(elem, memo) }
-      .values
-      .sort_by { |elem| elem[:id] || Float::MAX } # sort by IDs to pass old tests
+  def changed_only_prio?
+    changed_attribute_names_to_save.to_set == Set.new(%w[updated_at prio])
   end
 
   def reduce_related_tasks(elem, memo)
@@ -154,15 +158,12 @@ class Taskbar < ApplicationModel
     memo[key] = elem
   end
 
-  def update_related_taskbars(preferences)
-    related_taskbars.each do |taskbar|
-      taskbar.with_lock do
-        taskbar.preferences = preferences
-        taskbar.local_update = true
-        taskbar.skip_item_trigger = true
-        taskbar.save!
-      end
-    end
+  def update_related_taskbars
+    return if key == 'Search'
+    return if local_update
+    return if changed_only_prio?
+
+    TaskbarUpdateRelatedTasksJob.perform_later(related_taskbars.map(&:id))
   end
 
   def notify_clients

+ 3 - 1
spec/graphql/gql/subscriptions/ticket_live_user_updates_spec.rb

@@ -2,7 +2,7 @@
 
 require 'rails_helper'
 
-RSpec.describe Gql::Subscriptions::TicketLiveUserUpdates, :aggregate_failures, authenticated_as: :agent, type: :graphql do
+RSpec.describe Gql::Subscriptions::TicketLiveUserUpdates, :aggregate_failures, authenticated_as: :agent, performs_jobs: true, type: :graphql do
   let(:agent)                         { create(:agent) }
   let(:customer)                      { create(:customer) }
   let(:ticket)                        { create(:ticket) }
@@ -33,6 +33,7 @@ RSpec.describe Gql::Subscriptions::TicketLiveUserUpdates, :aggregate_failures, a
 
   before do
     live_user_entry && live_user_entry_customer
+    perform_enqueued_jobs
 
     gql.execute(subscription, variables: variables, context: { channel: mock_channel })
   end
@@ -42,6 +43,7 @@ RSpec.describe Gql::Subscriptions::TicketLiveUserUpdates, :aggregate_failures, a
     # We need to work around this, otherwise this test would fail.
     UserInfo.current_user_id = agent_id
     taskbar_item.update!(state: state)
+    perform_enqueued_jobs
     UserInfo.current_user_id = agent.id
   end
 

+ 6 - 1
spec/models/taskbar/triggers_subscriptions_spec.rb

@@ -2,7 +2,7 @@
 
 require 'rails_helper'
 
-RSpec.describe Taskbar::TriggersSubscriptions, :aggregate_failures do
+RSpec.describe Taskbar::TriggersSubscriptions, :aggregate_failures, performs_jobs: true do
   let(:taskbar)         { create(:taskbar, user: create(:user)) }
   let(:related_taskbar) { create(:taskbar, key: taskbar.key, user: create(:user)) }
 
@@ -34,6 +34,7 @@ RSpec.describe Taskbar::TriggersSubscriptions, :aggregate_failures do
     it 'triggers correctly' do
       taskbar.prio += 1
       taskbar.save!
+      perform_enqueued_jobs
       expect(gqs::TicketLiveUserUpdates).to have_received(:trigger).exactly(2)
       expect(gqs_uc::TaskbarItemUpdates).not_to have_received(:trigger_after_update)
       expect(gqs_uc::TaskbarItemStateUpdates).not_to have_received(:trigger)
@@ -43,6 +44,7 @@ RSpec.describe Taskbar::TriggersSubscriptions, :aggregate_failures do
   context 'when updating last_contact_at' do
     it 'triggers correctly' do
       taskbar.touch_last_contact!
+      perform_enqueued_jobs
       expect(gqs::TicketLiveUserUpdates).to have_received(:trigger).exactly(1) # only for related_taskbar
       expect(gqs_uc::TaskbarItemUpdates).not_to have_received(:trigger_after_update)
       expect(gqs_uc::TaskbarItemStateUpdates).not_to have_received(:trigger)
@@ -54,6 +56,7 @@ RSpec.describe Taskbar::TriggersSubscriptions, :aggregate_failures do
       it 'triggers correctly' do
         taskbar.state = { 'body' => 'test' }
         taskbar.save!
+        perform_enqueued_jobs
         expect(gqs::TicketLiveUserUpdates).to have_received(:trigger).exactly(2)
         expect(gqs_uc::TaskbarItemUpdates).to have_received(:trigger_after_update).once # only for taskbar
         expect(gqs_uc::TaskbarItemStateUpdates).to have_received(:trigger).once
@@ -66,6 +69,7 @@ RSpec.describe Taskbar::TriggersSubscriptions, :aggregate_failures do
       it 'triggers correctly' do
         taskbar.state = { 'body' => 'test' }
         taskbar.save!
+        perform_enqueued_jobs
         expect(gqs::TicketLiveUserUpdates).to have_received(:trigger).exactly(2)
         expect(gqs_uc::TaskbarItemUpdates).to have_received(:trigger_after_update).once # only for taskbar
         expect(gqs_uc::TaskbarItemStateUpdates).not_to have_received(:trigger)
@@ -76,6 +80,7 @@ RSpec.describe Taskbar::TriggersSubscriptions, :aggregate_failures do
   context 'when deleting the record' do
     it 'triggers correctly' do
       taskbar.destroy!
+      perform_enqueued_jobs
       expect(gqs::TicketLiveUserUpdates).to have_received(:trigger).once # only for related_taskbar
       expect(gqs_uc::TaskbarItemUpdates).to have_received(:trigger_after_destroy)
       expect(gqs_uc::TaskbarItemStateUpdates).not_to have_received(:trigger)

+ 24 - 4
spec/models/taskbar_spec.rb

@@ -3,7 +3,7 @@
 require 'rails_helper'
 require 'models/taskbar/has_attachments_examples'
 
-RSpec.describe Taskbar, type: :model do
+RSpec.describe Taskbar, performs_jobs: true, type: :model do
   it_behaves_like 'Taskbar::HasAttachments'
 
   context 'item' do
@@ -146,6 +146,8 @@ RSpec.describe Taskbar, type: :model do
         user_id:  1,
       )
 
+      perform_enqueued_jobs
+
       taskbar1.reload
       expect(taskbar1.preferences[:tasks].count).to eq(2)
       expect(taskbar1.preferences[:tasks][0][:user_id]).to eq(1)
@@ -172,6 +174,8 @@ RSpec.describe Taskbar, type: :model do
         user_id:  1,
       )
 
+      perform_enqueued_jobs
+
       taskbar1.reload
       expect(taskbar1.preferences[:tasks].count).to eq(2)
       expect(taskbar1.preferences[:tasks][0][:user_id]).to eq(1)
@@ -206,6 +210,8 @@ RSpec.describe Taskbar, type: :model do
         user_id:  1,
       )
 
+      perform_enqueued_jobs
+
       taskbar1.reload
       expect(taskbar1.preferences[:tasks].count).to eq(3)
       expect(taskbar1.preferences[:tasks][0][:user_id]).to eq(1)
@@ -242,6 +248,8 @@ RSpec.describe Taskbar, type: :model do
       taskbar2.state = { article: {}, ticket: {} }
       taskbar2.save!
 
+      perform_enqueued_jobs
+
       taskbar1.reload
       expect(taskbar1.preferences[:tasks].count).to eq(3)
       expect(taskbar1.preferences[:tasks][0][:user_id]).to eq(1)
@@ -278,6 +286,8 @@ RSpec.describe Taskbar, type: :model do
       taskbar2.state = { article: { body: 'some body' }, ticket: {} }
       taskbar2.save!
 
+      perform_enqueued_jobs
+
       taskbar1.reload
       expect(taskbar1.preferences[:tasks].count).to eq(3)
       expect(taskbar1.preferences[:tasks][0][:user_id]).to eq(1)
@@ -314,6 +324,8 @@ RSpec.describe Taskbar, type: :model do
       taskbar1.state = { article: { body: '' }, ticket: { state_id: 123 } }
       taskbar1.save!
 
+      perform_enqueued_jobs
+
       taskbar1.reload
       expect(taskbar1.preferences[:tasks].count).to eq(3)
       expect(taskbar1.preferences[:tasks][0][:user_id]).to eq(1)
@@ -357,6 +369,8 @@ RSpec.describe Taskbar, type: :model do
       taskbar2.notify = true
       taskbar2.save!
 
+      perform_enqueued_jobs
+
       taskbar1.reload
       expect(taskbar1.preferences[:tasks].count).to eq(3)
       expect(taskbar1.preferences[:tasks][0][:user_id]).to eq(1)
@@ -404,6 +418,8 @@ RSpec.describe Taskbar, type: :model do
       taskbar2.notify = true
       taskbar2.save!
 
+      perform_enqueued_jobs
+
       taskbar1.reload
       expect(taskbar1.preferences[:tasks].count).to eq(3)
       expect(taskbar1.preferences[:tasks][0][:user_id]).to eq(1)
@@ -515,6 +531,7 @@ RSpec.describe Taskbar, type: :model do
 
       it 'updates related items when creating a taskbar' do
         create(:taskbar, key: key)
+        perform_enqueued_jobs
 
         expect(other_taskbar.reload.preferences[:tasks]).to include(include(user_id: other_user.id), include(user_id: 1))
       end
@@ -545,6 +562,8 @@ RSpec.describe Taskbar, type: :model do
 
         taskbar.update! state: { a: :b }
 
+        perform_enqueued_jobs
+
         expect(other_taskbar.reload.preferences[:tasks]).to include(include(user_id: other_user.id), include(user_id: 1))
       end
 
@@ -619,10 +638,11 @@ RSpec.describe Taskbar, type: :model do
     before { taskbar_1 && taskbar_2 && taskbar_3 }
 
     it 'updates related taskbars' do
-      taskbar_1.send(:update_related_taskbars, { a: 1 })
+      taskbar_1.send(:update_related_taskbars)
+      perform_enqueued_jobs
 
-      expect(taskbar_2.reload).to have_attributes(preferences: { a: 1 })
-      expect(taskbar_3.reload).not_to have_attributes(preferences: { a: 1 })
+      expect(taskbar_2.reload.preferences[:tasks].count).to eq(2)
+      expect(taskbar_3.reload.preferences[:tasks].count).to eq(1)
     end
   end
 

+ 112 - 102
spec/system/apps/mobile_old/tickets/viewer/live_users_spec.rb

@@ -2,7 +2,7 @@
 
 require 'rails_helper'
 
-RSpec.describe 'Mobile > Ticket > Viewers > Live Users', app: :mobile, authenticated_as: :agent, type: :system do
+RSpec.describe 'Mobile > Ticket > Viewers > Live Users', app: :mobile, authenticated_as: :agent, performs_jobs: true, type: :system do
   let(:group)         { Group.find_by(name: 'Users') }
   let(:agent)         { create(:agent, groups: [group]) }
   let(:another_agent) { create(:agent, groups: [group]) }
@@ -32,151 +32,161 @@ RSpec.describe 'Mobile > Ticket > Viewers > Live Users', app: :mobile, authentic
 
   context 'when opening viewers', authenticated_as: :agent do
     it 'shows the users currently looking at the ticket' do
-      taskbar_item = create(:taskbar, user_id: another_agent.id, key: "Ticket-#{ticket.id}", app: 'mobile')
-      open_viewers_dialog
-
-      # No idle viewers.
-      expect(page).to have_no_text('Opened in tabs')
-
-      # One active viewer, without editing.
-      expect(page)
-        .to have_text('Viewing ticket')
-        .and have_no_text(agent.fullname)
-        .and have_text(another_agent.fullname)
-        .and have_no_css('.icon.icon-edit')
-
-      # Checking pencil icon.
-      update_taskbar_item(taskbar_item, { editing: true }, another_agent.id, 2)
-
-      expect(page).to have_css('.icon.icon-edit')
-
-      # Checking idle.
-      travel_to 10.minutes.ago
-      update_taskbar_item(taskbar_item, { editing: false }, another_agent.id, 3)
-      travel_back
-
-      expect(page)
-        .to have_text('Opened in tabs')
-        .and have_no_text(agent.fullname)
-        .and have_no_text('Viewing ticket')
-        .and have_text(another_agent.fullname)
-        .and have_no_css('.icon.icon-edit')
+      perform_enqueued_jobs do
+        taskbar_item = create(:taskbar, user_id: another_agent.id, key: "Ticket-#{ticket.id}", app: 'mobile')
+        open_viewers_dialog
+
+        # No idle viewers.
+        expect(page).to have_no_text('Opened in tabs')
+
+        # One active viewer, without editing.
+        expect(page)
+          .to have_text('Viewing ticket')
+          .and have_no_text(agent.fullname)
+          .and have_text(another_agent.fullname)
+          .and have_no_css('.icon.icon-edit')
+
+        # Checking pencil icon.
+        update_taskbar_item(taskbar_item, { editing: true }, another_agent.id, 2)
+
+        expect(page).to have_css('.icon.icon-edit')
+
+        # Checking idle.
+        travel_to 10.minutes.ago
+        update_taskbar_item(taskbar_item, { editing: false }, another_agent.id, 3)
+        travel_back
+
+        expect(page)
+          .to have_text('Opened in tabs')
+          .and have_no_text(agent.fullname)
+          .and have_no_text('Viewing ticket')
+          .and have_text(another_agent.fullname)
+          .and have_no_css('.icon.icon-edit')
+      end
     end
 
     it 'shows the users that start looking at the ticket' do
-      visit "/tickets/#{ticket.id}"
+      perform_enqueued_jobs do
+        visit "/tickets/#{ticket.id}"
 
-      expect(page).to have_no_button('Show ticket viewers')
+        expect(page).to have_no_button('Show ticket viewers')
 
-      taskbar_item = create(:taskbar, user_id: third_agent.id, key: "Ticket-#{ticket.id}", app: 'mobile')
-      update_taskbar_item(taskbar_item, { editing: true }, third_agent.id, 1)
-      open_viewers_dialog
+        taskbar_item = create(:taskbar, user_id: third_agent.id, key: "Ticket-#{ticket.id}", app: 'mobile')
+        update_taskbar_item(taskbar_item, { editing: true }, third_agent.id, 1)
+        open_viewers_dialog
 
-      expect(page)
-        .to have_text('Viewing ticket')
-        .and have_text(third_agent.fullname)
-        .and have_css('.icon.icon-edit')
+        expect(page)
+          .to have_text('Viewing ticket')
+          .and have_text(third_agent.fullname)
+          .and have_css('.icon.icon-edit')
+      end
     end
 
     context 'when editing is started on mobile' do
       it 'updates the other session' do
-        visit "/tickets/#{ticket.id}"
+        perform_enqueued_jobs do
+          visit "/tickets/#{ticket.id}"
 
-        using_session(:customer) do
-          login(
-            username: another_agent.login,
-            password: 'test',
-          )
+          using_session(:customer) do
+            login(
+              username: another_agent.login,
+              password: 'test',
+            )
 
-          visit "/tickets/#{ticket.id}"
-        end
+            visit "/tickets/#{ticket.id}"
+          end
 
-        open_viewers_dialog
+          open_viewers_dialog
 
-        expect(page)
-          .to have_text(another_agent.fullname)
-          .and have_no_css('.icon.icon-edit')
+          expect(page)
+            .to have_text(another_agent.fullname)
+            .and have_no_css('.icon.icon-edit')
 
-        using_session(:customer) do
-          visit "/tickets/#{ticket.id}/information"
+          using_session(:customer) do
+            visit "/tickets/#{ticket.id}/information"
 
-          wait_for_form_to_settle('form-ticket-edit')
+            wait_for_form_to_settle('form-ticket-edit')
 
-          within_form(form_updater_gql_number: 1) do
-            find_input('Ticket title').type('New Title')
+            within_form(form_updater_gql_number: 1) do
+              find_input('Ticket title').type('New Title')
+            end
           end
-        end
 
-        wait_for_viewers_subscription(number: 2)
+          wait_for_viewers_subscription(number: 2)
 
-        expect(page)
-          .to have_text('Viewing ticket')
-          .and have_text(another_agent.fullname)
-          .and have_css('.icon.icon-edit')
+          expect(page)
+            .to have_text('Viewing ticket')
+            .and have_text(another_agent.fullname)
+            .and have_css('.icon.icon-edit')
+        end
       end
     end
 
     context 'when editing is started on desktop' do
       it 'updates the other session' do
-        visit "/tickets/#{ticket.id}"
+        perform_enqueued_jobs do
+          visit "/tickets/#{ticket.id}"
 
-        using_session(:customer) do
-          login(
-            username:    another_agent.login,
-            password:    'test',
-            remember_me: false,
-            app:         :desktop,
-          )
+          using_session(:customer) do
+            login(
+              username:    another_agent.login,
+              password:    'test',
+              remember_me: false,
+              app:         :desktop,
+            )
 
-          visit "/#ticket/zoom/#{ticket.id}", app: :desktop
-        end
+            visit "/#ticket/zoom/#{ticket.id}", app: :desktop
+          end
 
-        open_viewers_dialog
+          open_viewers_dialog
 
-        expect(page)
-          .to have_text(another_agent.fullname)
-          .and have_no_css('.icon.icon-desktop-edit')
+          expect(page)
+            .to have_text(another_agent.fullname)
+            .and have_no_css('.icon.icon-desktop-edit')
 
-        using_session(:customer) do
-          within :active_content, '.tabsSidebar' do
-            select 'closed', from: 'State'
+          using_session(:customer) do
+            within :active_content, '.tabsSidebar' do
+              select 'closed', from: 'State'
+            end
           end
-        end
 
-        wait_for_viewers_subscription(number: 2)
+          wait_for_viewers_subscription(number: 2)
 
-        expect(page)
-          .to have_text('Viewing ticket')
-          .and have_text(another_agent.fullname)
-          .and have_css('.icon.icon-desktop-edit')
+          expect(page)
+            .to have_text('Viewing ticket')
+            .and have_text(another_agent.fullname)
+            .and have_css('.icon.icon-desktop-edit')
+        end
       end
     end
 
     context 'when current user is using both desktop and mobile' do
       it 'shows correct icons' do
-        visit "/tickets/#{ticket.id}"
+        perform_enqueued_jobs do
+          visit "/tickets/#{ticket.id}"
 
-        using_session(:customer) do
-          login(
-            username:    agent.login,
-            password:    'test',
-            remember_me: false,
-            app:         :desktop,
-          )
+          using_session(:customer) do
+            login(
+              username:    agent.login,
+              password:    'test',
+              remember_me: false,
+              app:         :desktop,
+            )
 
-          visit "/#ticket/zoom/#{ticket.id}", app: :desktop
+            visit "/#ticket/zoom/#{ticket.id}", app: :desktop
 
-          within :active_content, '.tabsSidebar' do
-            select 'closed', from: 'State'
+            within :active_content, '.tabsSidebar' do
+              select 'closed', from: 'State'
+            end
           end
-        end
 
-        open_viewers_dialog
+          open_viewers_dialog
 
-        expect(page)
-          .to have_text('Viewing ticket')
-          .and have_text(agent.fullname)
-          .and have_css('.icon.icon-desktop-edit')
+          expect(page)
+            .to have_text('Viewing ticket')
+            .and have_text(agent.fullname)
+            .and have_css('.icon.icon-desktop-edit')
+        end
       end
     end
   end

+ 117 - 89
spec/system/ticket/update/simultaneously_with_two_user_spec.rb

@@ -2,11 +2,13 @@
 
 require 'rails_helper'
 
-RSpec.describe 'Ticket > Update > Simultaneously with two different user', type: :system do
+RSpec.describe 'Ticket > Update > Simultaneously with two different user', performs_jobs: true, type: :system do
   let(:group)  { Group.find_by(name: 'Users') }
   let(:ticket) { create(:ticket, group: group) }
   let(:agent)  { User.find_by(login: 'agent1@example.com') }
 
+  around { |example| perform_enqueued_jobs { example.run } }
+
   # rubocop:disable RSpec/InstanceVariable
   define :have_avatar do |expected|
     chain(:changed, :text)
@@ -95,135 +97,143 @@ RSpec.describe 'Ticket > Update > Simultaneously with two different user', type:
     end
 
     it 'avatar from other user should be visible in ticket zoom' do
-      expect(page).to have_avatar('AT')
+      perform_enqueued_jobs do
+        expect(page).to have_avatar('AT')
 
-      using_session(:second_browser) do
-        expect(page).to have_avatar('TA')
+        using_session(:second_browser) do
+          expect(page).to have_avatar('TA')
+        end
       end
     end
 
     it 'check changes from the first user and added changes from the second user' do
-      within(:active_content) do
-        find('.js-textarea').send_keys('some note')
+      perform_enqueued_jobs do
+        within(:active_content) do
+          find('.js-textarea').send_keys('some note')
 
-        expect(page).to have_css('.js-reset')
-      end
+          expect(page).to have_css('.js-reset')
+        end
 
-      expect(page).to have_avatar('AT')
+        expect(page).to have_avatar('AT')
 
-      using_session(:second_browser) do
-        expect(page).to have_avatar('TA').changed!
+        using_session(:second_browser) do
+          expect(page).to have_avatar('TA').changed!
 
-        within(:active_content) do
-          find('.js-textarea').send_keys('some other note')
+          within(:active_content) do
+            find('.js-textarea').send_keys('some other note')
 
-          expect(page).to have_css('.js-reset')
+            expect(page).to have_css('.js-reset')
+          end
         end
-      end
 
-      expect(page).to have_avatar('AT').changed!
+        expect(page).to have_avatar('AT').changed!
 
-      using_session(:second_browser) do
-        within(:active_content) do
-          click '.js-attributeBar .js-submit'
+        using_session(:second_browser) do
+          within(:active_content) do
+            click '.js-attributeBar .js-submit'
 
-          expect(page).to have_no_css('.js-reset')
-          expect(page).to have_css('.article-content', text: 'some other note')
+            expect(page).to have_no_css('.js-reset')
+            expect(page).to have_css('.article-content', text: 'some other note')
+          end
+
+          expect(page).to have_avatar('TA').changed!
         end
 
-        expect(page).to have_avatar('TA').changed!
-      end
+        expect(page).to have_avatar('AT')
+        check_taskbar_tab(ticket.id, title: ticket.title, modified: true)
 
-      expect(page).to have_avatar('AT')
-      check_taskbar_tab(ticket.id, title: ticket.title, modified: true)
+        within(:active_content) do
+          expect(page).to have_css('.article-content', text: 'some other note')
 
-      within(:active_content) do
-        expect(page).to have_css('.article-content', text: 'some other note')
+          click '.js-attributeBar .js-submit'
 
-        click '.js-attributeBar .js-submit'
+          expect(page).to have_no_css('.js-reset')
+          expect(page).to have_css('.article-content', text: 'some note')
+        end
 
-        expect(page).to have_no_css('.js-reset')
-        expect(page).to have_css('.article-content', text: 'some note')
-      end
+        using_session(:second_browser) do
+          expect(page).to have_avatar('TA')
 
-      using_session(:second_browser) do
-        expect(page).to have_avatar('TA')
+          expect(page).to have_css('.article-content', text: 'some note')
+          check_taskbar_tab(ticket.id, title: ticket.title, modified: true)
+        end
 
-        expect(page).to have_css('.article-content', text: 'some note')
-        check_taskbar_tab(ticket.id, title: ticket.title, modified: true)
-      end
+        # Reload browsers and check if state is correct.
+        refresh
 
-      # Reload browsers and check if state is correct.
-      refresh
+        using_session(:second_browser) do
+          refresh
 
-      using_session(:second_browser) do
-        refresh
+          expect(page).to have_avatar('TA')
+          expect(page).to have_no_css('.js-reset')
+        end
 
-        expect(page).to have_avatar('TA')
+        expect(page).to have_avatar('AT')
         expect(page).to have_no_css('.js-reset')
       end
-
-      expect(page).to have_avatar('AT')
-      expect(page).to have_no_css('.js-reset')
     end
 
     it 'check refresh for unsaved changes and reset after refresh' do
-      using_session(:second_browser) do
-        within(:active_content) do
-          find('.js-textarea').send_keys('some other note')
+      perform_enqueued_jobs do
+        using_session(:second_browser) do
+          within(:active_content) do
+            find('.js-textarea').send_keys('some other note')
 
-          expect(page).to have_css('.js-reset')
-        end
+            expect(page).to have_css('.js-reset')
+          end
 
-        expect(page).to have_avatar('TA')
+          expect(page).to have_avatar('TA')
 
-        # We need to wait for the auto save feature.
-        wait.until do
-          Taskbar.find_by(key: "Ticket-#{ticket.id}", user_id: agent.id).state_changed?
-        end
+          # We need to wait for the auto save feature.
+          wait.until do
+            Taskbar.find_by(key: "Ticket-#{ticket.id}", user_id: agent.id).state_changed?
+          end
 
-        refresh
-      end
+          refresh
+        end
 
-      expect(page).to have_avatar('AT').changed!
+        expect(page).to have_avatar('AT').changed!
 
-      using_session(:second_browser) do
-        refresh
+        using_session(:second_browser) do
+          refresh
 
-        within(:active_content) do
-          click '.js-reset'
-          expect(page).to have_css('.js-textarea', text: '')
+          within(:active_content) do
+            click '.js-reset'
+            expect(page).to have_css('.js-textarea', text: '')
+          end
         end
-      end
 
-      expect(page).to have_avatar('AT')
+        expect(page).to have_avatar('AT')
+      end
     end
 
     it 'change title with second user' do
-      find('.js-textarea').send_keys('some note')
+      perform_enqueued_jobs do
+        find('.js-textarea').send_keys('some note')
 
-      using_session(:second_browser) do
-        find('.js-textarea').send_keys('some other note')
-        find('.ticketZoom-header .js-objectTitle').set('TTTsome level 2 <b>subject</b> 123äöü')
+        using_session(:second_browser) do
+          find('.js-textarea').send_keys('some other note')
+          find('.ticketZoom-header .js-objectTitle').set('TTTsome level 2 <b>subject</b> 123äöü')
 
-        # Click in the body field, to trigger the title update.
-        find('.js-textarea').send_keys('trigger title')
+          # Click in the body field, to trigger the title update.
+          find('.js-textarea').send_keys('trigger title')
 
-        expect(page).to have_css('.js-objectTitle', text: 'TTTsome level 2 <b>subject</b> 123äöü')
+          expect(page).to have_css('.js-objectTitle', text: 'TTTsome level 2 <b>subject</b> 123äöü')
 
-        check_taskbar_tab(ticket.id, title: 'TTTsome level 2 <b>subject</b> 123äöü')
+          check_taskbar_tab(ticket.id, title: 'TTTsome level 2 <b>subject</b> 123äöü')
 
-        expect(page).to have_css('.js-textarea', text: 'some other note')
-      end
+          expect(page).to have_css('.js-textarea', text: 'some other note')
+        end
 
-      expect(page).to have_css('.js-objectTitle', text: 'TTTsome level 2 <b>subject</b> 123äöü')
-      expect(page).to have_css('.js-textarea', text: 'some note')
+        expect(page).to have_css('.js-objectTitle', text: 'TTTsome level 2 <b>subject</b> 123äöü')
+        expect(page).to have_css('.js-textarea', text: 'some note')
 
-      check_taskbar_tab(ticket.id, title: 'TTTsome level 2 <b>subject</b> 123äöü', modified: true)
+        check_taskbar_tab(ticket.id, title: 'TTTsome level 2 <b>subject</b> 123äöü', modified: true)
 
-      # Refresh and check that modified flag is gone
-      refresh
-      check_taskbar_tab(ticket.id, title: 'TTTsome level 2 <b>subject</b> 123äöü', modified: false)
+        # Refresh and check that modified flag is gone
+        refresh
+        check_taskbar_tab(ticket.id, title: 'TTTsome level 2 <b>subject</b> 123äöü', modified: false)
+      end
     end
   end
 
@@ -248,7 +258,9 @@ RSpec.describe 'Ticket > Update > Simultaneously with two different user', type:
       end
 
       it 'does not show current user' do
-        expect(page).not_to have_avatar(user)
+        perform_enqueued_jobs do
+          expect(page).not_to have_avatar(user)
+        end
       end
     end
 
@@ -261,7 +273,9 @@ RSpec.describe 'Ticket > Update > Simultaneously with two different user', type:
       end
 
       it 'shows another user' do
-        expect(page).to have_avatar(another_user).with_no_icon!
+        perform_enqueued_jobs do
+          expect(page).to have_avatar(another_user).with_no_icon!
+        end
       end
     end
 
@@ -274,7 +288,9 @@ RSpec.describe 'Ticket > Update > Simultaneously with two different user', type:
       end
 
       it 'shows another user' do
-        expect(page).to have_avatar(another_user).with_icon(:mobile)
+        perform_enqueued_jobs do
+          expect(page).to have_avatar(another_user).with_icon(:mobile)
+        end
       end
     end
 
@@ -288,7 +304,9 @@ RSpec.describe 'Ticket > Update > Simultaneously with two different user', type:
       end
 
       it 'shows another user' do
-        expect(page).to have_avatar(another_user).with_no_icon!
+        perform_enqueued_jobs do
+          expect(page).to have_avatar(another_user).with_no_icon!
+        end
       end
     end
 
@@ -301,7 +319,9 @@ RSpec.describe 'Ticket > Update > Simultaneously with two different user', type:
       end
 
       it 'shows another user' do
-        expect(page).to have_avatar(another_user).with_icon(:pen).changed!
+        perform_enqueued_jobs do
+          expect(page).to have_avatar(another_user).with_icon(:pen).changed!
+        end
       end
     end
 
@@ -314,7 +334,9 @@ RSpec.describe 'Ticket > Update > Simultaneously with two different user', type:
       end
 
       it 'shows another user' do
-        expect(page).to have_avatar(another_user).with_icon(:pen).changed!
+        perform_enqueued_jobs do
+          expect(page).to have_avatar(another_user).with_icon(:pen).changed!
+        end
       end
     end
 
@@ -327,7 +349,9 @@ RSpec.describe 'Ticket > Update > Simultaneously with two different user', type:
       end
 
       it 'shows same user' do
-        expect(page).not_to have_avatar(user)
+        perform_enqueued_jobs do
+          expect(page).not_to have_avatar(user)
+        end
       end
     end
 
@@ -339,7 +363,9 @@ RSpec.describe 'Ticket > Update > Simultaneously with two different user', type:
       end
 
       it 'do not show same user' do
-        expect(page).not_to have_avatar(user)
+        perform_enqueued_jobs do
+          expect(page).not_to have_avatar(user)
+        end
       end
     end
 
@@ -352,7 +378,9 @@ RSpec.describe 'Ticket > Update > Simultaneously with two different user', type:
       end
 
       it 'shows same user' do
-        expect(page).to have_avatar(user).with_icon(:'mobile-edit').changed!
+        perform_enqueued_jobs do
+          expect(page).to have_avatar(user).with_icon(:'mobile-edit').changed!
+        end
       end
     end
   end