从jquery_ujs迁移到rails-ujs后JS测试中jQuery对象仅首次初始化
我之前也碰到过一模一样的问题!把jquery_ujs换成rails-ujs之后,Capybara的JS测试就出现了这种“单个测试跑都没问题,连起来跑就只有第一个能找到Select2元素”的诡异情况。折腾了一阵后发现,核心原因是rails-ujs和jquery_ujs在页面加载、Turbolinks处理以及测试环境重置上的差异,导致后续测试的jQuery/Select2没有正确初始化。
下面是几个我亲测有效的解决思路,按优先级推荐:
1. 修正Select2的初始化时机(最常见原因)
如果你的Select2是用$(document).ready()初始化的,换成rails-ujs后就踩坑了——因为rails-ujs配合Turbolinks时,页面切换是通过Turbolinks完成的,不会触发传统的ready事件,只有首次加载页面才会触发。后续测试里访问页面时,Select2根本没初始化,自然找不到.select2-container。
把初始化代码改成绑定到turbolinks:load事件:
// 替换原来的$(document).ready() $(document).on('turbolinks:load', function() { $('.my-select').select2(); });
另外,别忘了加个页面缓存前的销毁逻辑,避免残留的Select2实例影响下一个测试:
$(document).on('turbolinks:before-cache', function() { $('.select2-container').select2('destroy'); });
2. 强制测试之间重置Capybara会话
有时候rails-ujs的页面清理逻辑没完全跟上,导致前一个测试的JS环境残留到了下一个测试里。可以在RSpec配置里给所有JS测试前后强制重置会话:
RSpec.configure do |config| config.before(:each, :js) do Capybara.reset_sessions! end config.after(:each, :js) do Capybara.reset_sessions! # 跳转到空白页彻底清理上下文 page.execute_script('window.location.href = "about:blank"') end end
这样每个测试都能拿到全新的浏览器会话,不会被之前的测试污染。
3. 检查Capybara驱动的复用设置
如果你用的是Chrome/Selenium驱动,有时候驱动会复用浏览器进程,导致JS环境没重置。可以在配置里明确让驱动每次启动新进程:
Capybara.register_driver :selenium_chrome do |app| options = Selenium::WebDriver::Chrome::Options.new(args: ['--no-sandbox']) Capybara::Selenium::Driver.new(app, browser: :chrome, options: options) end Capybara.javascript_driver = :selenium_chrome
这个设置能避免驱动复用会话带来的各种奇怪问题。
4. 极端情况:手动注入jQuery(不推荐但有效)
如果上面的方法都没用,可能是测试环境里jQuery没被正确加载。可以在测试的before块里手动注入:
before(:each, :js) do visit new_my_object_path # 如果页面没有jQuery就加载 page.execute_script(<<-JS if (typeof jQuery === 'undefined') { const script = document.createElement('script'); script.src = 'https://code.jquery.com/jquery-3.6.4.min.js'; document.head.appendChild(script); } JS ) # 等待jQuery加载完成 page.wait_for do page.evaluate_script('typeof jQuery !== "undefined"') end end
不过这属于hack手段,尽量先排查前面的常规问题。
我当时是用第一种方法解决的,改完Select2的初始化事件后,所有测试连起来跑都正常了。你可以先试试这个方案!
内容的提问来源于stack exchange,提问作者Andy Harvey

