Skip to content

Instantly share code, notes, and snippets.

@jmgarnier
Last active July 3, 2018 12:52
Show Gist options
  • Save jmgarnier/3b006a46b4d78d527edd1c0ee2dab079 to your computer and use it in GitHub Desktop.
Save jmgarnier/3b006a46b4d78d527edd1c0ee2dab079 to your computer and use it in GitHub Desktop.
Ruby on Rails with React, Wait for ajax - 💪🏻version
module WaitForAjax
def click_ajax_link(link, timeout: Capybara.default_max_wait_time)
with_ajax(timeout: timeout) { click_on link }
end
def wait_for_ajax
wait_for_ajax_status(:inactive)
end
def wait_for_ajax_status(expected_status, wait_time = Capybara.default_max_wait_time)
Timeout.timeout(wait_time) do
loop do
condition = %[('jQuery' in window && jQuery.active == 1) || ('reactjQuery' in window && reactjQuery.active == 1)]
jquery_status = page.evaluate_script(condition) ? :active : :inactive
break if expected_status == jquery_status
end
end
end
def visit(path)
super(path)
wait_for_ajax
end
def wait_for_human(message = nil)
`say #{message}` if message
page.execute_script('window.wait_for_human = 1')
loop do
page.execute_script(
<<~JS
if (!window.hasOwnProperty("wait_for_human")) {
window.wait_for_human = 1;
}
if (!window.hasOwnProperty("humanDone")) {
window.humanDone = function() { window.wait_for_human = 0; }
console.log('Human, call humanDone() when you are done');
}
JS
)
break if page.evaluate_script('window.hasOwnProperty("wait_for_human") ? window.wait_for_human : 1').zero?
sleep 1
end
end
def with_ajax(timeout: Capybara.default_max_wait_time, &block)
wait_for_ajax_status(:inactive, timeout)
WithAjax.new(timeout: timeout, page: page).call(&block)
end
end
class WithAjax
def initialize(timeout: Capybara.default_max_wait_time, page:)
@timeout = timeout
@page = page
end
def call
register_ajax_handler
yield
wait_for_ajax_event
unregister_ajax_handler
print_warnings
end
private
attr_reader :timeout, :page, :time_spent
def register_ajax_handler
page.execute_script(
<<~JS
window.__ajaxWaitingIsOver = false;
window.__allPendingAjaxRequestsAreDone = function() {
window.__ajaxWaitingIsOver = true;
}
jQuery(document).on('ajaxStop', window.__allPendingAjaxRequestsAreDone);
if (window.reactjQuery !== undefined) {
reactjQuery(document).on('ajaxStop', window.__allPendingAjaxRequestsAreDone);
}
JS
)
end
def wait_for_ajax_event
waiting_started_at = Time.zone.now
Timeout.timeout(timeout) do
loop do
break if page.evaluate_script('window.__ajaxWaitingIsOver')
sleep 0.05
end
end
@time_spent = ((Time.zone.now - waiting_started_at).to_f * 1000).to_i
end
def unregister_ajax_handler
page.execute_script(
<<~JS
jQuery(document).off('ajaxStop', window.__allPendingAjaxRequestsAreDone);
if (window.reactjQuery !== undefined) {
reactjQuery(document).off('ajaxStop', window.__allPendingAjaxRequestsAreDone);
}
JS
)
end
def print_warnings
return if ENV['CI']
if remaining_time < 100
puts HighLine.color("WITH_AJAX: Waiting for ajax almost failed with only #{remaining_time} msec left", :yellow)
puts HighLine.color(current_test_location, :yellow)
end
if remaining_time > 1000 && timeout != Capybara.default_max_wait_time
puts HighLine.color("WITH_AJAX: #{timeout}.seconds seems to be too high (#{(timeout - remaining_time / 1000.0).round(3)} needed)", :yellow)
puts HighLine.color(current_test_location, :yellow)
end
end
def remaining_time
(timeout * 1000 - time_spent).to_i
end
def current_test_location
spec_location = caller_locations(0, 5).reject { |location| location.path =~ /ajax\.rb$/ }.first
"./#{Pathname.new(spec_location.path).relative_path_from(Rails.root)}:#{spec_location.lineno}"
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment