JavaScriptの処理で時間に依存してたりする実装を行っているとE2Eのときに時間を固定したいときが稀によくあるのですが、
travel_to
を使っても結局はサーバーサイド上の時刻しか固定出来ません😢
というわけでJavaScript上の時刻を固定するtravel_to_javascript
というgemを作ってみました💎
中身は前に記事にしたHelperです😅
使い方
まずは下記のような形でGemfile
に追記してbundle installしてください📦
gem 'travel_to_javascript'
あとは任意のfeature spec内でrequire 'travel_to_javascript'
を実行してinclude TravelToJavascript
してください。
そうするとtravel_to_javascript
が使用出来るようになり、Capybara::Session
のオブジェクトと固定したい時間を引数で渡すと、
block内のJavaScript上の現在時刻を引数で渡した時間で固定出来ます🕛
Rspecのサンプルは下記のような感じです。
require 'spec_helper' require 'travel_to_javascript' RSpec.describe 'SampleFeatureSpec', type: :feature do include TravelToJavascript it 'sample spec' do # NOTE: Use a JavaScript enabled driver. page = Capybara::Session.new(:headless_chrome, TestApp) travel_to_javascript(page, DateTime.parse('2000-01-01 1:11:11.111+9:00')) do page.execute_script('console.error(Date.now(), new Date())') pp page.driver.browser.manage.logs.get(:browser).map(&:message) # locks time by args in block. # => ["console-api 2:32 946656671111 Sat Jan 01 2000 01:11:11 GMT+0900"] end page.execute_script('console.error(Date.now(), new Date())') pp page.driver.browser.manage.logs.get(:browser).map(&:message) # restore time outside block. # => ["console-api 2:32 1586652460142 Sun Apr 12 2020 09:47:40 GMT+0900"] end end
Minitestでも使えます。
require 'test_helper' require 'travel_to_javascript' class SampleFeatureTest < Minitest::Test include TravelToJavascript def test_sample # NOTE: Use a JavaScript enabled driver. page = Capybara::Session.new(:headless_chrome, TestApp) travel_to_javascript(page, DateTime.parse('2000-01-01 1:11:11.111+9:00')) do page.execute_script('console.error(Date.now(), new Date())') pp page.driver.browser.manage.logs.get(:browser).map(&:message) # locks time by args in block. # => ["console-api 2:32 946656671111 Sat Jan 01 2000 01:11:11 GMT+0900"] end page.execute_script('console.error(Date.now(), new Date())') pp page.driver.browser.manage.logs.get(:browser).map(&:message) # restore time outside block. # => ["console-api 2:32 1586652460142 Sun Apr 12 2020 09:47:40 GMT+0900"] end end
仕組み
このGemの仕組みは渡された時間をiso8601
形式に変換してJavaScript上のDate
とDate.now
を渡された時間を返すようにオーバーライドします。
※このときにJavaScript上に引数を渡していた場合は固定せずに、そのままの時間を返すようにしています。
その後yield
でblockに渡された処理を実行して、時間を元に戻しています🕛
def travel_to_javascript(page, datetime) page.execute_script time_stop_javascript(datetime) yield page.execute_script time_undo_javsctipt end def time_stop_javascript(rb_datetime) <<~JS originDate = Date; Date = #{time_stop_js_function_for_date(rb_datetime)}; Date.now = #{time_stop_js_function_for_date_now(rb_datetime)}; JS end def time_stop_js_function_for_date(rb_datetime) <<~JS function (datetime) { if (datetime) { return new originDate(datetime); } else { return new originDate("#{rb_datetime.iso8601(6)}"); } } JS end def time_stop_js_function_for_date_now(rb_datetime) <<~JS function (datetime) { if (datetime) { return new originDate(datetime).getTime(); } else { return new originDate("#{rb_datetime.iso8601(6)}").getTime(); } } JS
travel_to_javascript/travel_to_javascript.rb at master · Madogiwa0124/travel_to_javascript · GitHub
出来ないこと
下記のような場合にはこのgemだと対応出来ません😢
travel_to_javascriptの中でvisit等をしてページ跨ぎで時間を止めるようなことはページがリロードされたタイミングでJavaScriptもリロードされてしまうので出来ません。
ページ読み込み時にJavaScript側で判定するような場合にはexecute_scriptの実行前に判定が行われる可能性があるので効かなそうです。
おわりに
今回は以前に作ったHelperをgemとして使いやすくしてみました💎対応出来るケースはちょっと限られているかもですが、JavaScript上で時間を固定したいときには使えるかもしれないです🙇♂️