module Capybara::Lockstep

Constants

ERROR_ALERT_OPEN
ERROR_NAVIGATED_AWAY
ERROR_PAGE_MISSING
ERROR_SNIPPET_MISSING
VERSION

Attributes

synchronizing[RW]
synchronizing?[RW]

Public Class Methods

auto_synchronize(**options) click to toggle source
# File lib/capybara-lockstep/lockstep.rb, line 40
def auto_synchronize(**options)
  if mode == :auto
    synchronize(**options)
  end
end
synchronize(lazy: false, log: nil) click to toggle source
# File lib/capybara-lockstep/lockstep.rb, line 26
def synchronize(lazy: false, log: nil)
  if (lazy && synchronized?) || synchronizing? || mode == :off
    return
  end

  # Allow passing a log message that is only logged
  # when we're actually synchronizing.
  if log
    self.log(log)
  end

  synchronize_now
end
synchronized=(value) click to toggle source
# File lib/capybara-lockstep/lockstep.rb, line 22
def synchronized=(value)
  page.instance_variable_set(:@lockstep_synchronized, value)
end
synchronized?() click to toggle source
# File lib/capybara-lockstep/lockstep.rb, line 15
def synchronized?
  value = page.instance_variable_get(:@lockstep_synchronized)
  # We consider a new Capybara session to be synchronized.
  # This will be set to false after our first visit().
  value.nil? ? true : value
end

Private Class Methods

current_seconds() click to toggle source
# File lib/capybara-lockstep/lockstep.rb, line 150
def current_seconds
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
ignoring_alerts(&block) click to toggle source
# File lib/capybara-lockstep/lockstep.rb, line 144
def ignoring_alerts(&block)
  block.call
rescue ::Selenium::WebDriver::Error::UnexpectedAlertOpenError
  # no-op
end
page() click to toggle source
# File lib/capybara-lockstep/lockstep.rb, line 128
def page
  Capybara.current_session
end
synchronize_now() click to toggle source
# File lib/capybara-lockstep/lockstep.rb, line 48
      def synchronize_now
        self.synchronizing = true
        self.synchronized = false

        log 'Synchronizing'

        start_time = current_seconds

        begin
          with_max_wait_time(timeout) do
            message_from_js = evaluate_async_script(<<~JS)
              let done = arguments[0]
              let synchronize = () => {
                if (window.CapybaraLockstep) {
                  CapybaraLockstep.synchronize(done)
                } else {
                  done(#{ERROR_SNIPPET_MISSING.to_json})
                }
              }
              let protocol = location.protocol
              if (protocol === 'data:' || protocol == 'about:') {
                done(#{ERROR_PAGE_MISSING.to_json})
              } else if (document.readyState === 'complete') {
                // WebDriver always waits for the `load` event after a visit(),
                // unless a different page load strategy was configured.
                synchronize()
              } else {
                window.addEventListener('load', synchronize)
              }
            JS

            case message_from_js
            when ERROR_PAGE_MISSING
              log(message_from_js)
            when ERROR_SNIPPET_MISSING
              log(message_from_js)
            else
              log message_from_js
              end_time = current_seconds
              ms_elapsed = ((end_time.to_f - start_time) * 1000).round
              log "Synchronized successfully [#{ms_elapsed} ms]"
              self.synchronized = true
            end
          end
        rescue ::Selenium::WebDriver::Error::ScriptTimeoutError
          timeout_message = "Could not synchronize within #{timeout} seconds"
          log timeout_message
          if timeout_with == :error
            raise Timeout, timeout_message
          else
            # Don't raise an error, this may happen if the server is slow to respond.
            # We will retry on the next Capybara synchronize call.
          end
        rescue ::Selenium::WebDriver::Error::UnexpectedAlertOpenError
          log ERROR_ALERT_OPEN
          # Don't raise an error, this will happen in an innocent test.
          # We will retry on the next Capybara synchronize call.
        rescue ::Selenium::WebDriver::Error::JavascriptError => e
          # When the URL changes while a script is running, my current selenium-webdriver
          # raises a Selenium::WebDriver::Error::JavascriptError with the message:
          # "javascript error: document unloaded while waiting for result".
          # We will retry on the next Capybara synchronize call, by then we should see
          # the new page.
          if e.message.include?('unload')
            log ERROR_NAVIGATED_AWAY
          else
            unhandled_synchronize_error(e)
          end
        rescue StandardError => e
          unhandled_synchronize_error(e)
        ensure
          self.synchronizing = false
        end
      end
unhandled_synchronize_error(e) click to toggle source
# File lib/capybara-lockstep/lockstep.rb, line 123
def unhandled_synchronize_error(e)
  log "#{e.class.name} while synchronizing: #{e.message}"
  raise e
end
with_max_wait_time(seconds, &block) click to toggle source
# File lib/capybara-lockstep/lockstep.rb, line 134
def with_max_wait_time(seconds, &block)
  old_max_wait_time = Capybara.default_max_wait_time
  Capybara.default_max_wait_time = seconds
  begin
    block.call
  ensure
    Capybara.default_max_wait_time = old_max_wait_time
  end
end