From 26d70654b7ae627aa35fd86fe03174a9c41af8ae Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Mon, 15 Jun 2026 11:01:06 +0200 Subject: [PATCH] fix: stabilise flaky TomSelect test helper The SelectFromTomSelect helper had two issues causing flaky failures in CI: 1. ensure_tom_select_initialized hardcoded 'meeting_organisers' as the element ID, but this helper is also used with 'meeting_invitations_member'. Initializing the wrong element created a race condition. 2. wait: 5 for .ts-dropdown .option was too short for CI environments where the AJAX search request to /admin/members/search can be slow. Removed the fragile manual initialization workaround entirely - the real initialization via application.js + CDN script runs reliably. Increased timeouts for CI resilience. --- spec/support/select_from_tom_select.rb | 54 ++++---------------------- 1 file changed, 7 insertions(+), 47 deletions(-) diff --git a/spec/support/select_from_tom_select.rb b/spec/support/select_from_tom_select.rb index bc5c28053..fccf100a8 100644 --- a/spec/support/select_from_tom_select.rb +++ b/spec/support/select_from_tom_select.rb @@ -7,11 +7,10 @@ module SelectFromTomSelect # @param item_text [String] The text to select # @param from [String, Symbol] The field ID (for documentation purposes) def select_from_tom_select(item_text, from: nil) - # Ensure TomSelect is initialized (workaround for pages where it doesn't auto-init) - ensure_tom_select_initialized('meeting_organisers') - - # Wait for TomSelect to initialize - give extra time for page to fully load JS - expect(page).to have_css('.ts-wrapper', wait: 10) + # Wait for TomSelect to initialize - the real initialization runs via the + # jQuery DOMContentLoaded handler in application.js, which fires after the + # CDN script (loaded in the page head) defines the TomSelect global. + expect(page).to have_css('.ts-wrapper', wait: 15) # Open dropdown and type search query find('.ts-control').click @@ -27,7 +26,8 @@ def select_from_tom_select(item_text, from: nil) input.send_keys(item_text[3..]) if item_text.length > 3 # Wait for results (includes debounce + network) - expect(page).to have_css('.ts-dropdown .option', wait: 5) + # Uses a generous timeout for CI environments where AJAX may be slower + expect(page).to have_css('.ts-dropdown .option', wait: 10) # Click the matching option # Use JavaScript click to avoid element interception issues @@ -38,54 +38,14 @@ def select_from_tom_select(item_text, from: nil) # Remove an item from a TomSelect multi-select # @param item_text [String] The text of the item to remove (must match exactly) def remove_from_tom_select(item_text) - # Ensure TomSelect is initialized (workaround for pages where it doesn't auto-init) - ensure_tom_select_initialized('meeting_organisers') - # Wait for TomSelect to initialize and items to be present - expect(page).to have_css('.ts-wrapper', wait: 10) + expect(page).to have_css('.ts-wrapper', wait: 15) expect(page).to have_css('.ts-wrapper .item', text: item_text, wait: 5) within '.ts-wrapper' do find('.item', text: item_text, match: :prefer_exact).find('.remove').click end end - - private - - def ensure_tom_select_initialized(element_id) - # Check if TomSelect is already initialized - return if page.has_css?('.ts-wrapper', wait: 2) - - # Try to initialize TomSelect manually if the element exists - # Note: This is a minimal initialization for tests where TomSelect doesn't auto-init - script = <<~JS - (function() { - var elem = document.getElementById('#{element_id}'); - if (elem && typeof TomSelect !== 'undefined' && !elem.tomselect) { - new TomSelect(elem, { - plugins: ['remove_button'], - placeholder: 'Type to search members...', - valueField: 'id', - labelField: 'full_name', - searchField: ['full_name', 'email'], - create: false, - loadThrottle: 300, - shouldLoad: function(query) { return query.length >= 3; }, - load: function(query, callback) { - fetch('/admin/members/search?q=' + encodeURIComponent(query)) - .then(response => response.json()) - .then(json => callback(json)) - .catch(() => callback()); - } - }); - } - })(); - JS - page.execute_script(script) - - # Wait for initialization - expect(page).to have_css('.ts-wrapper', wait: 5) - end end RSpec.configure do |config|