如何在Capybara控制的无头浏览器中Stub时间与时区?
当然可以搞定!不过得先搞清楚为啥你之前的Timecop没用——因为Timecop是在Ruby后端层面篡改时间,而Capybara控制的无头浏览器是个独立的进程,它的JS运行环境根本不知道Ruby这边的时间被改了。所以得直接对浏览器的JS环境动手才行,给你几个实用的方法:
方法1:直接用Capybara执行JS重写Date对象
浏览器里的时间完全由Date对象和Date.now()控制,我们可以直接通过execute_script重写这些API,把时间钉死在你需要的点上:
before do target_time = Time.zone.parse("Apr 15, 2018 12:00PM") # 后端层面用Timecop freeze时间,保证接口返回的时间一致 Timecop.freeze(target_time) # 前端浏览器Mock Date对象 execute_script(<<~JS) // 先保存原生Date,方便测试后恢复 window.OriginalDate = Date; // 重写无参数的Date构造函数,返回指定时间 Date = function() { if (arguments.length === 0) { return new window.OriginalDate(#{target_time.to_i * 1000}); } // 有参数的情况还是用原生逻辑 return new window.OriginalDate(...arguments); }; // 重写Date.now(),返回指定时间戳 Date.now = function() { return #{target_time.to_i * 1000}; }; // 继承原生原型,避免其他Date方法出错 Date.prototype = window.OriginalDate.prototype; JS end after do # 测试结束后恢复原生Date execute_script("Date = window.OriginalDate; delete window.OriginalDate;") Timecop.return end
这样你的JS日期选择器就会读取到你指定的时间,和后端保持一致了。
方法2:用Sinon.js优雅Mock时间(推荐)
如果你的测试环境已经引入了Sinon.js,那可以用它的useFakeTimers来一键Mock所有时间相关API,包括setTimeout、setInterval这些,比手动重写更稳妥:
before do target_time = Time.zone.parse("Apr 15, 2018 12:00PM") Timecop.freeze(target_time) # 用Sinon创建假时钟 execute_script(<<~JS) const clock = sinon.useFakeTimers(#{target_time.to_i * 1000}); // 把时钟存在window上,方便后续恢复 window.testClock = clock; JS end after do # 恢复原生时间API execute_script("window.testClock.restore();") Timecop.return end
额外需求:Mock时区
如果你的组件还依赖系统时区,同样有两种方式:
- JS层面重写时区相关API:
execute_script(<<~JS) // 强制Intl.DateTimeFormat使用纽约时区 const originalDateTimeFormat = Intl.DateTimeFormat; Intl.DateTimeFormat = function(locales, options) { options = options || {}; options.timeZone = options.timeZone || 'America/New_York'; return new originalDateTimeFormat(locales, options); }; // 重写Date的toString方法,适配时区 Object.defineProperty(Date.prototype, 'toString', { value: function() { return this.toLocaleString('en-US', { timeZone: 'America/New_York' }); } }); JS
- 启动浏览器时指定时区:
对于Chrome无头浏览器,可以在注册驱动时直接加时区参数,从根源上让浏览器运行在指定时区:
Capybara.register_driver :chrome_headless do |app| options = Selenium::WebDriver::Chrome::Options.new options.add_argument('--headless=new') # 指定时区为纽约 options.add_argument('--timezone=America/New_York') Capybara::Selenium::Driver.new(app, browser: :chrome, options: options) end
总结一下:Timecop管后端Ruby的时间,浏览器的时间得直接操作JS环境来Mock,两者结合才能让前后端时间完全同步,满足你的组件测试需求。
内容的提问来源于stack exchange,提问作者user2490003
相关产品推荐
相关产品推荐

