capybara-lockstep 0.4.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/.github/workflows/test.yml +50 -0
 - data/.gitignore +1 -0
 - data/CHANGELOG.md +69 -0
 - data/Gemfile +4 -0
 - data/Gemfile.lock +31 -8
 - data/README.md +142 -106
 - data/Rakefile +8 -0
 - data/capybara-lockstep.gemspec +1 -0
 - data/lib/capybara-lockstep/capybara_ext.rb +64 -9
 - data/lib/capybara-lockstep/configuration.rb +60 -22
 - data/lib/capybara-lockstep/errors.rb +1 -1
 - data/lib/capybara-lockstep/helper.js +140 -78
 - data/lib/capybara-lockstep/helper.rb +12 -3
 - data/lib/capybara-lockstep/lockstep.rb +33 -6
 - data/lib/capybara-lockstep/version.rb +1 -1
 - metadata +18 -2
 
    
        data/Rakefile
    CHANGED
    
    | 
         @@ -6,3 +6,11 @@ require "rspec/core/rake_task" 
     | 
|
| 
       6 
6 
     | 
    
         
             
            RSpec::Core::RakeTask.new(:spec)
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
       8 
8 
     | 
    
         
             
            task default: :spec
         
     | 
| 
      
 9 
     | 
    
         
            +
            require 'jasmine'
         
     | 
| 
      
 10 
     | 
    
         
            +
            load 'jasmine/tasks/jasmine.rake'
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            begin
         
     | 
| 
      
 13 
     | 
    
         
            +
              require 'gemika/tasks'
         
     | 
| 
      
 14 
     | 
    
         
            +
            rescue LoadError
         
     | 
| 
      
 15 
     | 
    
         
            +
              puts 'Run `gem install gemika` for additional tasks'
         
     | 
| 
      
 16 
     | 
    
         
            +
            end
         
     | 
    
        data/capybara-lockstep.gemspec
    CHANGED
    
    | 
         @@ -27,6 +27,7 @@ Gem::Specification.new do |spec| 
     | 
|
| 
       27 
27 
     | 
    
         
             
              spec.add_dependency "capybara", ">= 2.0"
         
     | 
| 
       28 
28 
     | 
    
         
             
              spec.add_dependency "selenium-webdriver", ">= 3"
         
     | 
| 
       29 
29 
     | 
    
         
             
              spec.add_dependency "activesupport", ">= 3.2"
         
     | 
| 
      
 30 
     | 
    
         
            +
              spec.add_dependency "ruby2_keywords"
         
     | 
| 
       30 
31 
     | 
    
         | 
| 
       31 
32 
     | 
    
         
             
              # For more information and examples about making a new gem, checkout our
         
     | 
| 
       32 
33 
     | 
    
         
             
              # guide at: https://bundler.io/guides/creating_gem.html
         
     | 
| 
         @@ -1,7 +1,9 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'ruby2_keywords'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            module Capybara
         
     | 
| 
       2 
4 
     | 
    
         
             
              module Lockstep
         
     | 
| 
       3 
5 
     | 
    
         
             
                module VisitWithWaiting
         
     | 
| 
       4 
     | 
    
         
            -
                  def visit(*args, &block)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  ruby2_keywords def visit(*args, &block)
         
     | 
| 
       5 
7 
     | 
    
         
             
                    url = args[0]
         
     | 
| 
       6 
8 
     | 
    
         
             
                    # Some of our apps have a Cucumber step that changes drivers mid-scenario.
         
     | 
| 
       7 
9 
     | 
    
         
             
                    # It works by creating a new Capybara session and re-visits the URL from the
         
     | 
| 
         @@ -12,13 +14,18 @@ module Capybara 
     | 
|
| 
       12 
14 
     | 
    
         | 
| 
       13 
15 
     | 
    
         
             
                    if visiting_remote_url
         
     | 
| 
       14 
16 
     | 
    
         
             
                      # We're about to leave this screen, killing all in-flight requests.
         
     | 
| 
       15 
     | 
    
         
            -
                       
     | 
| 
      
 17 
     | 
    
         
            +
                      # Give pending form submissions etc. a chance to finish before we tear down
         
     | 
| 
      
 18 
     | 
    
         
            +
                      # the browser environment.
         
     | 
| 
      
 19 
     | 
    
         
            +
                      #
         
     | 
| 
      
 20 
     | 
    
         
            +
                      # We force a non-lazy synchronization so we pick up all client-side changes
         
     | 
| 
      
 21 
     | 
    
         
            +
                      # that have not been caused by Capybara commands.
         
     | 
| 
      
 22 
     | 
    
         
            +
                      Lockstep.synchronize(lazy: false)
         
     | 
| 
       16 
23 
     | 
    
         
             
                    end
         
     | 
| 
       17 
24 
     | 
    
         | 
| 
       18 
25 
     | 
    
         
             
                    super(*args, &block).tap do
         
     | 
| 
       19 
26 
     | 
    
         
             
                      if visiting_remote_url
         
     | 
| 
       20 
     | 
    
         
            -
                        #  
     | 
| 
       21 
     | 
    
         
            -
                         
     | 
| 
      
 27 
     | 
    
         
            +
                        # We haven't yet synchronized the new screen.
         
     | 
| 
      
 28 
     | 
    
         
            +
                        Lockstep.synchronized = false
         
     | 
| 
       22 
29 
     | 
    
         
             
                      end
         
     | 
| 
       23 
30 
     | 
    
         
             
                    end
         
     | 
| 
       24 
31 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -26,11 +33,57 @@ module Capybara 
     | 
|
| 
       26 
33 
     | 
    
         
             
              end
         
     | 
| 
       27 
34 
     | 
    
         
             
            end
         
     | 
| 
       28 
35 
     | 
    
         | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
36 
     | 
    
         
             
            Capybara::Session.class_eval do
         
     | 
| 
       31 
37 
     | 
    
         
             
              prepend Capybara::Lockstep::VisitWithWaiting
         
     | 
| 
       32 
38 
     | 
    
         
             
            end
         
     | 
| 
       33 
39 
     | 
    
         | 
| 
      
 40 
     | 
    
         
            +
            module Capybara
         
     | 
| 
      
 41 
     | 
    
         
            +
              module Lockstep
         
     | 
| 
      
 42 
     | 
    
         
            +
                module SynchronizeAroundScriptMethod
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  def synchronize_around_script_method(meth)
         
     | 
| 
      
 45 
     | 
    
         
            +
                    mod = Module.new do
         
     | 
| 
      
 46 
     | 
    
         
            +
                      define_method meth do |script, *args, &block|
         
     | 
| 
      
 47 
     | 
    
         
            +
                        # Synchronization uses execute_script itself, so don't synchronize when
         
     | 
| 
      
 48 
     | 
    
         
            +
                        # we're already synchronizing.
         
     | 
| 
      
 49 
     | 
    
         
            +
                        if !Lockstep.synchronizing?
         
     | 
| 
      
 50 
     | 
    
         
            +
                          # It's generally a good idea to synchronize before a JavaScript wants
         
     | 
| 
      
 51 
     | 
    
         
            +
                          # to access or observe an earlier state change.
         
     | 
| 
      
 52 
     | 
    
         
            +
                          #
         
     | 
| 
      
 53 
     | 
    
         
            +
                          # In case the given script navigates away (with `location.href = url`,
         
     | 
| 
      
 54 
     | 
    
         
            +
                          # `history.back()`, etc.) we would kill all in-flight requests. For this case
         
     | 
| 
      
 55 
     | 
    
         
            +
                          # we force a non-lazy synchronization so we pick up all client-side changes
         
     | 
| 
      
 56 
     | 
    
         
            +
                          # that have not been caused by Capybara commands.
         
     | 
| 
      
 57 
     | 
    
         
            +
                          script_may_navigate_away = script =~ /\b(location|history)\b/
         
     | 
| 
      
 58 
     | 
    
         
            +
                          Lockstep.auto_synchronize(lazy: !script_may_navigate_away, log: "Synchronizing before script: #{script}")
         
     | 
| 
      
 59 
     | 
    
         
            +
                        end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                        super(script, *args, &block).tap do
         
     | 
| 
      
 62 
     | 
    
         
            +
                          if !Lockstep.synchronizing?
         
     | 
| 
      
 63 
     | 
    
         
            +
                            # We haven't yet synchronized with whatever changes the JavaScript
         
     | 
| 
      
 64 
     | 
    
         
            +
                            # did on the frontend.
         
     | 
| 
      
 65 
     | 
    
         
            +
                            Lockstep.synchronized = false
         
     | 
| 
      
 66 
     | 
    
         
            +
                          end
         
     | 
| 
      
 67 
     | 
    
         
            +
                        end
         
     | 
| 
      
 68 
     | 
    
         
            +
                      end
         
     | 
| 
      
 69 
     | 
    
         
            +
                      ruby2_keywords meth
         
     | 
| 
      
 70 
     | 
    
         
            +
                    end
         
     | 
| 
      
 71 
     | 
    
         
            +
                    prepend(mod)
         
     | 
| 
      
 72 
     | 
    
         
            +
                  end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                end
         
     | 
| 
      
 75 
     | 
    
         
            +
              end
         
     | 
| 
      
 76 
     | 
    
         
            +
            end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
            Capybara::Session.class_eval do
         
     | 
| 
      
 79 
     | 
    
         
            +
              extend Capybara::Lockstep::SynchronizeAroundScriptMethod
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
              synchronize_around_script_method :execute_script
         
     | 
| 
      
 82 
     | 
    
         
            +
              synchronize_around_script_method :evaluate_async_script
         
     | 
| 
      
 83 
     | 
    
         
            +
              # Don't synchronize around evaluate_script. It calls execute_script
         
     | 
| 
      
 84 
     | 
    
         
            +
              # internally and we don't want to synchronize multiple times.
         
     | 
| 
      
 85 
     | 
    
         
            +
            end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
       34 
87 
     | 
    
         
             
            module Capybara
         
     | 
| 
       35 
88 
     | 
    
         
             
              module Lockstep
         
     | 
| 
       36 
89 
     | 
    
         
             
                module UnsychronizeAfter
         
     | 
| 
         @@ -38,9 +91,10 @@ module Capybara 
     | 
|
| 
       38 
91 
     | 
    
         
             
                    mod = Module.new do
         
     | 
| 
       39 
92 
     | 
    
         
             
                      define_method meth do |*args, &block|
         
     | 
| 
       40 
93 
     | 
    
         
             
                        super(*args, &block).tap do
         
     | 
| 
       41 
     | 
    
         
            -
                           
     | 
| 
      
 94 
     | 
    
         
            +
                          Lockstep.synchronized = false
         
     | 
| 
       42 
95 
     | 
    
         
             
                        end
         
     | 
| 
       43 
96 
     | 
    
         
             
                      end
         
     | 
| 
      
 97 
     | 
    
         
            +
                      ruby2_keywords meth
         
     | 
| 
       44 
98 
     | 
    
         
             
                    end
         
     | 
| 
       45 
99 
     | 
    
         
             
                    prepend(mod)
         
     | 
| 
       46 
100 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -87,10 +141,11 @@ end 
     | 
|
| 
       87 
141 
     | 
    
         
             
            module Capybara
         
     | 
| 
       88 
142 
     | 
    
         
             
              module Lockstep
         
     | 
| 
       89 
143 
     | 
    
         
             
                module SynchronizeWithCatchUp
         
     | 
| 
       90 
     | 
    
         
            -
                  def synchronize(*args, &block)
         
     | 
| 
       91 
     | 
    
         
            -
                    # This method is called  
     | 
| 
      
 144 
     | 
    
         
            +
                  ruby2_keywords def synchronize(*args, &block)
         
     | 
| 
      
 145 
     | 
    
         
            +
                    # This method is called by Capybara before most interactions with
         
     | 
| 
      
 146 
     | 
    
         
            +
                    # the browser. It is a different method than Capybara::Lockstep.synchronize!
         
     | 
| 
       92 
147 
     | 
    
         
             
                    # We use the { lazy } option to only synchronize when we're out of sync.
         
     | 
| 
       93 
     | 
    
         
            -
                    Capybara::Lockstep. 
     | 
| 
      
 148 
     | 
    
         
            +
                    Capybara::Lockstep.auto_synchronize(lazy: true)
         
     | 
| 
       94 
149 
     | 
    
         | 
| 
       95 
150 
     | 
    
         
             
                    super(*args, &block)
         
     | 
| 
       96 
151 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -10,48 +10,71 @@ module Capybara 
     | 
|
| 
       10 
10 
     | 
    
         
             
                    @timeout = seconds
         
     | 
| 
       11 
11 
     | 
    
         
             
                  end
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
      
 13 
     | 
    
         
            +
                  def timeout_with
         
     | 
| 
      
 14 
     | 
    
         
            +
                    @timeout_with.nil? ? :log : @timeout_with
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  def timeout_with=(action)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    @timeout_with = action&.to_sym
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
       13 
21 
     | 
    
         
             
                  def debug?
         
     | 
| 
       14 
22 
     | 
    
         
             
                    # @debug may also be a Logger object, so convert it to a boolean
         
     | 
| 
       15 
23 
     | 
    
         
             
                    @debug.nil? ? false : !!@debug
         
     | 
| 
       16 
24 
     | 
    
         
             
                  end
         
     | 
| 
       17 
25 
     | 
    
         | 
| 
       18 
     | 
    
         
            -
                  def debug=( 
     | 
| 
       19 
     | 
    
         
            -
                    @debug =  
     | 
| 
       20 
     | 
    
         
            -
                    if  
     | 
| 
       21 
     | 
    
         
            -
                      target_prose = (is_logger?( 
     | 
| 
      
 26 
     | 
    
         
            +
                  def debug=(value)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    @debug = value
         
     | 
| 
      
 28 
     | 
    
         
            +
                    if value
         
     | 
| 
      
 29 
     | 
    
         
            +
                      target_prose = (is_logger?(value) ? 'Ruby logger' : 'STDOUT')
         
     | 
| 
       22 
30 
     | 
    
         
             
                      log "Logging to #{target_prose} and browser console"
         
     | 
| 
       23 
31 
     | 
    
         
             
                    end
         
     | 
| 
       24 
32 
     | 
    
         | 
| 
       25 
     | 
    
         
            -
                     
     | 
| 
       26 
     | 
    
         
            -
                       
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
                          if (window.CapybaraLockstep) {
         
     | 
| 
       29 
     | 
    
         
            -
                            CapybaraLockstep.setDebug(#{debug.to_json})
         
     | 
| 
       30 
     | 
    
         
            -
                          }
         
     | 
| 
       31 
     | 
    
         
            -
                        JS
         
     | 
| 
       32 
     | 
    
         
            -
                      end
         
     | 
| 
       33 
     | 
    
         
            -
                    rescue StandardError => e
         
     | 
| 
       34 
     | 
    
         
            -
                      log "#{e.class.name} while enabling logs in browser: #{e.message}"
         
     | 
| 
       35 
     | 
    
         
            -
                      # Don't fail. The next page load will include the snippet with debugging enabled.
         
     | 
| 
       36 
     | 
    
         
            -
                    end
         
     | 
| 
      
 33 
     | 
    
         
            +
                    send_config_to_browser(<<~JS)
         
     | 
| 
      
 34 
     | 
    
         
            +
                      CapybaraLockstep.debug = #{value.to_json}
         
     | 
| 
      
 35 
     | 
    
         
            +
                    JS
         
     | 
| 
       37 
36 
     | 
    
         | 
| 
       38 
37 
     | 
    
         
             
                    @debug
         
     | 
| 
       39 
38 
     | 
    
         
             
                  end
         
     | 
| 
       40 
39 
     | 
    
         | 
| 
       41 
     | 
    
         
            -
                  def  
     | 
| 
      
 40 
     | 
    
         
            +
                  def mode
         
     | 
| 
       42 
41 
     | 
    
         
             
                    if javascript_driver?
         
     | 
| 
       43 
     | 
    
         
            -
                      @ 
     | 
| 
      
 42 
     | 
    
         
            +
                      @mode.nil? ? :auto : @mode
         
     | 
| 
       44 
43 
     | 
    
         
             
                    else
         
     | 
| 
       45 
     | 
    
         
            -
                       
     | 
| 
      
 44 
     | 
    
         
            +
                      :off
         
     | 
| 
       46 
45 
     | 
    
         
             
                    end
         
     | 
| 
       47 
46 
     | 
    
         
             
                  end
         
     | 
| 
       48 
47 
     | 
    
         | 
| 
      
 48 
     | 
    
         
            +
                  def mode=(mode)
         
     | 
| 
      
 49 
     | 
    
         
            +
                    @mode = mode&.to_sym
         
     | 
| 
      
 50 
     | 
    
         
            +
                  end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
       49 
52 
     | 
    
         
             
                  def enabled=(enabled)
         
     | 
| 
       50 
     | 
    
         
            -
                     
     | 
| 
      
 53 
     | 
    
         
            +
                    case enabled
         
     | 
| 
      
 54 
     | 
    
         
            +
                    when true
         
     | 
| 
      
 55 
     | 
    
         
            +
                      log "Setting `Capybara::Lockstep.enabled = true` is deprecated. Set `Capybara::Lockstep.mode = :auto` instead."
         
     | 
| 
      
 56 
     | 
    
         
            +
                      self.mode = :auto
         
     | 
| 
      
 57 
     | 
    
         
            +
                    when false
         
     | 
| 
      
 58 
     | 
    
         
            +
                      log "Setting `Capybara::Lockstep.enabled = false` is deprecated. Set `Capybara::Lockstep.mode = :manual` or `Capybara::Lockstep.mode = :off` instead."
         
     | 
| 
      
 59 
     | 
    
         
            +
                      self.mode = :manual
         
     | 
| 
      
 60 
     | 
    
         
            +
                    when nil
         
     | 
| 
      
 61 
     | 
    
         
            +
                      # Reset to default
         
     | 
| 
      
 62 
     | 
    
         
            +
                      self.mode = nil
         
     | 
| 
      
 63 
     | 
    
         
            +
                    end
         
     | 
| 
       51 
64 
     | 
    
         
             
                  end
         
     | 
| 
       52 
65 
     | 
    
         | 
| 
       53 
     | 
    
         
            -
                  def  
     | 
| 
       54 
     | 
    
         
            -
                     
     | 
| 
      
 66 
     | 
    
         
            +
                  def wait_tasks
         
     | 
| 
      
 67 
     | 
    
         
            +
                    @wait_tasks
         
     | 
| 
      
 68 
     | 
    
         
            +
                  end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                  def wait_tasks=(value)
         
     | 
| 
      
 71 
     | 
    
         
            +
                    @wait_tasks = value
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                    send_config_to_browser(<<~JS)
         
     | 
| 
      
 74 
     | 
    
         
            +
                      CapybaraLockstep.waitTasks = #{value.to_json}
         
     | 
| 
      
 75 
     | 
    
         
            +
                    JS
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                    @wait_tasks
         
     | 
| 
       55 
78 
     | 
    
         
             
                  end
         
     | 
| 
       56 
79 
     | 
    
         | 
| 
       57 
80 
     | 
    
         
             
                  private
         
     | 
| 
         @@ -60,6 +83,21 @@ module Capybara 
     | 
|
| 
       60 
83 
     | 
    
         
             
                    driver.is_a?(Capybara::Selenium::Driver)
         
     | 
| 
       61 
84 
     | 
    
         
             
                  end
         
     | 
| 
       62 
85 
     | 
    
         | 
| 
      
 86 
     | 
    
         
            +
                  def send_config_to_browser(js)
         
     | 
| 
      
 87 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 88 
     | 
    
         
            +
                      with_max_wait_time(2) do
         
     | 
| 
      
 89 
     | 
    
         
            +
                        page.execute_script(<<~JS)
         
     | 
| 
      
 90 
     | 
    
         
            +
                          if (window.CapybaraLockstep) {
         
     | 
| 
      
 91 
     | 
    
         
            +
                            #{js}
         
     | 
| 
      
 92 
     | 
    
         
            +
                          }
         
     | 
| 
      
 93 
     | 
    
         
            +
                        JS
         
     | 
| 
      
 94 
     | 
    
         
            +
                      end
         
     | 
| 
      
 95 
     | 
    
         
            +
                    rescue StandardError => e
         
     | 
| 
      
 96 
     | 
    
         
            +
                      log "#{e.class.name} while configuring capybara-lockstep in browser: #{e.message}"
         
     | 
| 
      
 97 
     | 
    
         
            +
                      # Don't fail. The next page load will include the snippet with the new config.
         
     | 
| 
      
 98 
     | 
    
         
            +
                    end
         
     | 
| 
      
 99 
     | 
    
         
            +
                  end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
       63 
101 
     | 
    
         
             
                end
         
     | 
| 
       64 
102 
     | 
    
         
             
              end
         
     | 
| 
       65 
103 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,13 +1,23 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            window.CapybaraLockstep = (function() {
         
     | 
| 
       2 
     | 
    
         
            -
               
     | 
| 
       3 
     | 
    
         
            -
              let  
     | 
| 
       4 
     | 
    
         
            -
              let  
     | 
| 
      
 2 
     | 
    
         
            +
              // State and configuration
         
     | 
| 
      
 3 
     | 
    
         
            +
              let debug
         
     | 
| 
      
 4 
     | 
    
         
            +
              let jobCount
         
     | 
| 
      
 5 
     | 
    
         
            +
              let idleCallbacks
         
     | 
| 
      
 6 
     | 
    
         
            +
              let waitTasks
         
     | 
| 
      
 7 
     | 
    
         
            +
              reset()
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              function reset() {
         
     | 
| 
      
 10 
     | 
    
         
            +
                jobCount = 0
         
     | 
| 
      
 11 
     | 
    
         
            +
                idleCallbacks = []
         
     | 
| 
      
 12 
     | 
    
         
            +
                waitTasks = 0
         
     | 
| 
      
 13 
     | 
    
         
            +
                debug = false
         
     | 
| 
      
 14 
     | 
    
         
            +
              }
         
     | 
| 
       5 
15 
     | 
    
         | 
| 
       6 
16 
     | 
    
         
             
              function isIdle() {
         
     | 
| 
       7 
17 
     | 
    
         
             
                // Can't check for document.readyState or body.initializing here,
         
     | 
| 
       8 
18 
     | 
    
         
             
                // since the user might navigate away from the page before it finishes
         
     | 
| 
       9 
19 
     | 
    
         
             
                // initializing.
         
     | 
| 
       10 
     | 
    
         
            -
                return  
     | 
| 
      
 20 
     | 
    
         
            +
                return jobCount === 0
         
     | 
| 
       11 
21 
     | 
    
         
             
              }
         
     | 
| 
       12 
22 
     | 
    
         | 
| 
       13 
23 
     | 
    
         
             
              function isBusy() {
         
     | 
| 
         @@ -33,34 +43,43 @@ window.CapybaraLockstep = (function() { 
     | 
|
| 
       33 
43 
     | 
    
         
             
              }
         
     | 
| 
       34 
44 
     | 
    
         | 
| 
       35 
45 
     | 
    
         
             
              function startWork(tag) {
         
     | 
| 
       36 
     | 
    
         
            -
                 
     | 
| 
      
 46 
     | 
    
         
            +
                jobCount++
         
     | 
| 
       37 
47 
     | 
    
         
             
                if (tag) {
         
     | 
| 
       38 
     | 
    
         
            -
                  logNegative('Started work: %s [%d jobs]', tag,  
     | 
| 
      
 48 
     | 
    
         
            +
                  logNegative('Started work: %s [%d jobs]', tag, jobCount)
         
     | 
| 
       39 
49 
     | 
    
         
             
                }
         
     | 
| 
       40 
50 
     | 
    
         
             
              }
         
     | 
| 
       41 
51 
     | 
    
         | 
| 
       42 
52 
     | 
    
         
             
              function startWorkUntil(promise, tag) {
         
     | 
| 
       43 
53 
     | 
    
         
             
                startWork(tag)
         
     | 
| 
       44 
     | 
    
         
            -
                 
     | 
| 
      
 54 
     | 
    
         
            +
                let taggedStopWork = stopWork.bind(this, tag)
         
     | 
| 
      
 55 
     | 
    
         
            +
                promise.then(taggedStopWork, taggedStopWork)
         
     | 
| 
       45 
56 
     | 
    
         
             
              }
         
     | 
| 
       46 
57 
     | 
    
         | 
| 
       47 
     | 
    
         
            -
              function  
     | 
| 
       48 
     | 
    
         
            -
                 
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
      
 58 
     | 
    
         
            +
              function stopWork(tag) {
         
     | 
| 
      
 59 
     | 
    
         
            +
                let tasksElapsed = 0
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                let check = function() {
         
     | 
| 
      
 62 
     | 
    
         
            +
                  if (tasksElapsed < waitTasks) {
         
     | 
| 
      
 63 
     | 
    
         
            +
                    tasksElapsed++
         
     | 
| 
      
 64 
     | 
    
         
            +
                    setTimeout(check)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  } else {
         
     | 
| 
      
 66 
     | 
    
         
            +
                    stopWorkNow(tag)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  }
         
     | 
| 
      
 68 
     | 
    
         
            +
                }
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                check()
         
     | 
| 
       50 
71 
     | 
    
         
             
              }
         
     | 
| 
       51 
72 
     | 
    
         | 
| 
       52 
     | 
    
         
            -
              function  
     | 
| 
       53 
     | 
    
         
            -
                 
     | 
| 
      
 73 
     | 
    
         
            +
              function stopWorkNow(tag) {
         
     | 
| 
      
 74 
     | 
    
         
            +
                jobCount--
         
     | 
| 
       54 
75 
     | 
    
         | 
| 
       55 
76 
     | 
    
         
             
                if (tag) {
         
     | 
| 
       56 
     | 
    
         
            -
                  logPositive('Finished work: %s [%d jobs]', tag,  
     | 
| 
      
 77 
     | 
    
         
            +
                  logPositive('Finished work: %s [%d jobs]', tag, jobCount)
         
     | 
| 
       57 
78 
     | 
    
         
             
                }
         
     | 
| 
       58 
79 
     | 
    
         | 
| 
       59 
     | 
    
         
            -
                 
     | 
| 
       60 
     | 
    
         
            -
             
     | 
| 
       61 
     | 
    
         
            -
             
     | 
| 
       62 
     | 
    
         
            -
                  })
         
     | 
| 
       63 
     | 
    
         
            -
                  idleCallbacks = []
         
     | 
| 
      
 80 
     | 
    
         
            +
                let idleCallback
         
     | 
| 
      
 81 
     | 
    
         
            +
                while (isIdle() && (idleCallback = idleCallbacks.shift())) {
         
     | 
| 
      
 82 
     | 
    
         
            +
                  idleCallback('Finished waiting for browser')
         
     | 
| 
       64 
83 
     | 
    
         
             
                }
         
     | 
| 
       65 
84 
     | 
    
         
             
              }
         
     | 
| 
       66 
85 
     | 
    
         | 
| 
         @@ -104,47 +123,32 @@ window.CapybaraLockstep = (function() { 
     | 
|
| 
       104 
123 
     | 
    
         
             
                }
         
     | 
| 
       105 
124 
     | 
    
         
             
              }
         
     | 
| 
       106 
125 
     | 
    
         | 
| 
       107 
     | 
    
         
            -
              function  
     | 
| 
       108 
     | 
    
         
            -
                // We already override all interaction methods in the Selenium browser nodes, so they
         
     | 
| 
       109 
     | 
    
         
            -
                // wait for an idle frame afterwards. However a test script might also dispatch synthetic
         
     | 
| 
       110 
     | 
    
         
            -
                // events with executate_script() to manipulate the browser in ways that are not possible
         
     | 
| 
       111 
     | 
    
         
            -
                // with the Capybara API. When we observe such an event we wait until the end of the microtask,
         
     | 
| 
       112 
     | 
    
         
            -
                // assuming any busy action will be queued by then.
         
     | 
| 
       113 
     | 
    
         
            -
                ['click',  'mousedown', 'keydown', 'change', 'input', 'submit', 'focusin', 'focusout', 'scroll'].forEach(function(eventType) {
         
     | 
| 
       114 
     | 
    
         
            -
                  // Use { useCapture: true } so we get the event before another listener
         
     | 
| 
       115 
     | 
    
         
            -
                  // can prevent it from bubbling up to the document.
         
     | 
| 
       116 
     | 
    
         
            -
                  document.addEventListener(eventType, onInteraction, { capture: true, passive: true })
         
     | 
| 
       117 
     | 
    
         
            -
                })
         
     | 
| 
       118 
     | 
    
         
            -
              }
         
     | 
| 
       119 
     | 
    
         
            -
             
     | 
| 
       120 
     | 
    
         
            -
              function onInteraction(event) {
         
     | 
| 
       121 
     | 
    
         
            -
                // We wait until the end of this microtask, assuming that any callback that
         
     | 
| 
       122 
     | 
    
         
            -
                // would queue an AJAX request or load additional scripts will run by then.
         
     | 
| 
       123 
     | 
    
         
            -
                startWorkForMicrotask()
         
     | 
| 
       124 
     | 
    
         
            -
              }
         
     | 
| 
       125 
     | 
    
         
            -
             
     | 
| 
       126 
     | 
    
         
            -
              function trackDynamicScripts() {
         
     | 
| 
      
 126 
     | 
    
         
            +
              function trackRemoteElements() {
         
     | 
| 
       127 
127 
     | 
    
         
             
                if (!window.MutationObserver) {
         
     | 
| 
       128 
128 
     | 
    
         
             
                  return
         
     | 
| 
       129 
129 
     | 
    
         
             
                }
         
     | 
| 
       130 
130 
     | 
    
         | 
| 
       131 
     | 
    
         
            -
                // Dynamic imports or analytics snippets may insert a  
     | 
| 
       132 
     | 
    
         
            -
                //  
     | 
| 
      
 131 
     | 
    
         
            +
                // Dynamic imports or analytics snippets may insert a script element
         
     | 
| 
      
 132 
     | 
    
         
            +
                // that loads and executes additional JavaScript. We want to be isBusy()
         
     | 
| 
       133 
133 
     | 
    
         
             
                // until such scripts have loaded or errored.
         
     | 
| 
       134 
134 
     | 
    
         
             
                let observer = new MutationObserver(onAnyElementChanged)
         
     | 
| 
       135 
135 
     | 
    
         
             
                observer.observe(document, { subtree: true, childList: true })
         
     | 
| 
       136 
136 
     | 
    
         
             
              }
         
     | 
| 
       137 
137 
     | 
    
         | 
| 
       138 
138 
     | 
    
         
             
              function trackJQuery() {
         
     | 
| 
       139 
     | 
    
         
            -
                //  
     | 
| 
      
 139 
     | 
    
         
            +
                // CapybaraLockstep.track() is called as the first script in the head.
         
     | 
| 
      
 140 
     | 
    
         
            +
                // jQuery will be loaded after us, so we wait until DOMContentReady.
         
     | 
| 
       140 
141 
     | 
    
         
             
                whenReady(function() {
         
     | 
| 
       141 
     | 
    
         
            -
                  if (!window.jQuery) {
         
     | 
| 
      
 142 
     | 
    
         
            +
                  if (!window.jQuery || waitTasks > 0) {
         
     | 
| 
       142 
143 
     | 
    
         
             
                    return
         
     | 
| 
       143 
144 
     | 
    
         
             
                  }
         
     | 
| 
       144 
145 
     | 
    
         | 
| 
       145 
146 
     | 
    
         
             
                  // Although $.ajax() uses XHR internally, it also uses $.Deferred() which does
         
     | 
| 
       146 
147 
     | 
    
         
             
                  // not resolve in the next microtask but in the next *task* (it makes itself
         
     | 
| 
       147 
148 
     | 
    
         
             
                  // async using setTimoeut()). Hence we need to wait for it in addition to XHR.
         
     | 
| 
      
 149 
     | 
    
         
            +
                  //
         
     | 
| 
      
 150 
     | 
    
         
            +
                  // If user code also uses $.Deferred(), it is also recommended to set
         
     | 
| 
      
 151 
     | 
    
         
            +
                  // CapybaraLockdown.waitTasks = 1 or higher.
         
     | 
| 
       148 
152 
     | 
    
         
             
                  let oldAjax = window.jQuery.ajax
         
     | 
| 
       149 
153 
     | 
    
         
             
                  window.jQuery.ajax = function() {
         
     | 
| 
       150 
154 
     | 
    
         
             
                    let promise = oldAjax.apply(this, arguments)
         
     | 
| 
         @@ -154,56 +158,97 @@ window.CapybaraLockstep = (function() { 
     | 
|
| 
       154 
158 
     | 
    
         
             
                })
         
     | 
| 
       155 
159 
     | 
    
         
             
              }
         
     | 
| 
       156 
160 
     | 
    
         | 
| 
       157 
     | 
    
         
            -
               
     | 
| 
      
 161 
     | 
    
         
            +
              function isRemoteScript(element) {
         
     | 
| 
      
 162 
     | 
    
         
            +
                if (element.tagName === 'SCRIPT') {
         
     | 
| 
      
 163 
     | 
    
         
            +
                  let src = element.getAttribute('src')
         
     | 
| 
      
 164 
     | 
    
         
            +
                  let type = element.getAttribute('type')
         
     | 
| 
       158 
165 
     | 
    
         | 
| 
       159 
     | 
    
         
            -
             
     | 
| 
       160 
     | 
    
         
            -
                 
     | 
| 
       161 
     | 
    
         
            -
                // we consider ourselves busy.
         
     | 
| 
       162 
     | 
    
         
            -
                startWork()
         
     | 
| 
       163 
     | 
    
         
            -
                whenReady(function() {
         
     | 
| 
       164 
     | 
    
         
            -
                  stopWork()
         
     | 
| 
       165 
     | 
    
         
            -
                  if (document.body.hasAttribute(INITIALIZING_ATTRIBUTE)) {
         
     | 
| 
       166 
     | 
    
         
            -
                    startWork('Page initialization')
         
     | 
| 
       167 
     | 
    
         
            -
                    let observer = new MutationObserver(onInitializingAttributeChanged)
         
     | 
| 
       168 
     | 
    
         
            -
                    observer.observe(document.body, { attributes: true, attributeFilter: [INITIALIZING_ATTRIBUTE] })
         
     | 
| 
       169 
     | 
    
         
            -
                  }
         
     | 
| 
       170 
     | 
    
         
            -
                })
         
     | 
| 
      
 166 
     | 
    
         
            +
                  return src && (!type || /javascript/i.test(type))
         
     | 
| 
      
 167 
     | 
    
         
            +
                }
         
     | 
| 
       171 
168 
     | 
    
         
             
              }
         
     | 
| 
       172 
169 
     | 
    
         | 
| 
       173 
     | 
    
         
            -
              function  
     | 
| 
       174 
     | 
    
         
            -
                if (! 
     | 
| 
       175 
     | 
    
         
            -
                   
     | 
| 
      
 170 
     | 
    
         
            +
              function isRemoteImage(element) {
         
     | 
| 
      
 171 
     | 
    
         
            +
                if (element.tagName === 'IMG' && !element.complete) {
         
     | 
| 
      
 172 
     | 
    
         
            +
                  let src = element.getAttribute('src')
         
     | 
| 
      
 173 
     | 
    
         
            +
                  let srcSet = element.getAttribute('srcset')
         
     | 
| 
      
 174 
     | 
    
         
            +
             
     | 
| 
      
 175 
     | 
    
         
            +
                  let localSrcPattern = /^data:/
         
     | 
| 
      
 176 
     | 
    
         
            +
                  let localSrcSetPattern = /(^|\s)data:/
         
     | 
| 
      
 177 
     | 
    
         
            +
             
     | 
| 
      
 178 
     | 
    
         
            +
                  let hasLocalSrc = src && localSrcPattern.test(src)
         
     | 
| 
      
 179 
     | 
    
         
            +
                  let hasLocalSrcSet = srcSet && localSrcSetPattern.test(srcSet)
         
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
      
 181 
     | 
    
         
            +
                  return (src && !hasLocalSrc) || (srcSet && !hasLocalSrcSet)
         
     | 
| 
       176 
182 
     | 
    
         
             
                }
         
     | 
| 
       177 
183 
     | 
    
         
             
              }
         
     | 
| 
       178 
184 
     | 
    
         | 
| 
       179 
     | 
    
         
            -
              function  
     | 
| 
       180 
     | 
    
         
            -
                if ( 
     | 
| 
       181 
     | 
    
         
            -
                  let src =  
     | 
| 
       182 
     | 
    
         
            -
                  let  
     | 
| 
       183 
     | 
    
         
            -
             
     | 
| 
       184 
     | 
    
         
            -
                  return (src &&  
     | 
| 
      
 185 
     | 
    
         
            +
              function isRemoteInlineFrame(element) {
         
     | 
| 
      
 186 
     | 
    
         
            +
                if (element.tagName === 'IFRAME') {
         
     | 
| 
      
 187 
     | 
    
         
            +
                  let src = element.getAttribute('src')
         
     | 
| 
      
 188 
     | 
    
         
            +
                  let localSrcPattern = /^data:/
         
     | 
| 
      
 189 
     | 
    
         
            +
                  let hasLocalSrc = src && localSrcPattern.test(src)
         
     | 
| 
      
 190 
     | 
    
         
            +
                  return (src && !hasLocalSrc)
         
     | 
| 
       185 
191 
     | 
    
         
             
                }
         
     | 
| 
       186 
192 
     | 
    
         
             
              }
         
     | 
| 
       187 
193 
     | 
    
         | 
| 
       188 
     | 
    
         
            -
              function  
     | 
| 
       189 
     | 
    
         
            -
                 
     | 
| 
      
 194 
     | 
    
         
            +
              function trackRemoteElement(element, condition, workTag) {
         
     | 
| 
      
 195 
     | 
    
         
            +
                if (!condition(element)) {
         
     | 
| 
      
 196 
     | 
    
         
            +
                  return
         
     | 
| 
      
 197 
     | 
    
         
            +
                }
         
     | 
| 
      
 198 
     | 
    
         
            +
             
     | 
| 
      
 199 
     | 
    
         
            +
                let stopped = false
         
     | 
| 
      
 200 
     | 
    
         
            +
             
     | 
| 
       190 
201 
     | 
    
         
             
                startWork(workTag)
         
     | 
| 
       191 
     | 
    
         
            -
             
     | 
| 
       192 
     | 
    
         
            -
                 
     | 
| 
       193 
     | 
    
         
            -
             
     | 
| 
       194 
     | 
    
         
            -
             
     | 
| 
      
 202 
     | 
    
         
            +
             
     | 
| 
      
 203 
     | 
    
         
            +
                let doStop = function() {
         
     | 
| 
      
 204 
     | 
    
         
            +
                  stopped = true
         
     | 
| 
      
 205 
     | 
    
         
            +
                  element.removeEventListener('load', doStop)
         
     | 
| 
      
 206 
     | 
    
         
            +
                  element.removeEventListener('error', doStop)
         
     | 
| 
      
 207 
     | 
    
         
            +
                  stopWork(workTag)
         
     | 
| 
      
 208 
     | 
    
         
            +
                }
         
     | 
| 
      
 209 
     | 
    
         
            +
             
     | 
| 
      
 210 
     | 
    
         
            +
                let checkCondition = function() {
         
     | 
| 
      
 211 
     | 
    
         
            +
                  if (stopped) {
         
     | 
| 
      
 212 
     | 
    
         
            +
                    // A `load` or `error` event has fired.
         
     | 
| 
      
 213 
     | 
    
         
            +
                    // We can stop here. No need to schedule another check.
         
     | 
| 
      
 214 
     | 
    
         
            +
                    return
         
     | 
| 
      
 215 
     | 
    
         
            +
                  } else if (isDetached(element) || !condition(element)) {
         
     | 
| 
      
 216 
     | 
    
         
            +
                    // If it is detached or if its `[src]` attribute changes to a data: URL
         
     | 
| 
      
 217 
     | 
    
         
            +
                    // we may never get a `load` or `error` event.
         
     | 
| 
      
 218 
     | 
    
         
            +
                    doStop()
         
     | 
| 
      
 219 
     | 
    
         
            +
                  } else {
         
     | 
| 
      
 220 
     | 
    
         
            +
                    scheduleCheckCondition()
         
     | 
| 
      
 221 
     | 
    
         
            +
                  }
         
     | 
| 
      
 222 
     | 
    
         
            +
                }
         
     | 
| 
      
 223 
     | 
    
         
            +
             
     | 
| 
      
 224 
     | 
    
         
            +
                let scheduleCheckCondition = function() {
         
     | 
| 
      
 225 
     | 
    
         
            +
                  setTimeout(checkCondition, 200)
         
     | 
| 
      
 226 
     | 
    
         
            +
                }
         
     | 
| 
      
 227 
     | 
    
         
            +
             
     | 
| 
      
 228 
     | 
    
         
            +
                element.addEventListener('load', doStop)
         
     | 
| 
      
 229 
     | 
    
         
            +
                element.addEventListener('error', doStop)
         
     | 
| 
      
 230 
     | 
    
         
            +
             
     | 
| 
      
 231 
     | 
    
         
            +
                // We periodically check whether we still think the element will
         
     | 
| 
      
 232 
     | 
    
         
            +
                // produce a `load` or `error` event.
         
     | 
| 
      
 233 
     | 
    
         
            +
                scheduleCheckCondition()
         
     | 
| 
       195 
234 
     | 
    
         
             
              }
         
     | 
| 
       196 
235 
     | 
    
         | 
| 
       197 
236 
     | 
    
         
             
              function onAnyElementChanged(changes) {
         
     | 
| 
       198 
237 
     | 
    
         
             
                changes.forEach(function(change) {
         
     | 
| 
       199 
238 
     | 
    
         
             
                  change.addedNodes.forEach(function(addedNode) {
         
     | 
| 
       200 
     | 
    
         
            -
                    if ( 
     | 
| 
       201 
     | 
    
         
            -
                       
     | 
| 
      
 239 
     | 
    
         
            +
                    if (addedNode.nodeType === Node.ELEMENT_NODE) {
         
     | 
| 
      
 240 
     | 
    
         
            +
                      trackRemoteElement(addedNode, isRemoteScript, 'Script')
         
     | 
| 
      
 241 
     | 
    
         
            +
                      trackRemoteElement(addedNode, isRemoteImage, 'Image')
         
     | 
| 
      
 242 
     | 
    
         
            +
                      trackRemoteElement(addedNode, isRemoteInlineFrame, 'Inline frame')
         
     | 
| 
       202 
243 
     | 
    
         
             
                    }
         
     | 
| 
       203 
244 
     | 
    
         
             
                  })
         
     | 
| 
       204 
245 
     | 
    
         
             
                })
         
     | 
| 
       205 
246 
     | 
    
         
             
              }
         
     | 
| 
       206 
247 
     | 
    
         | 
| 
      
 248 
     | 
    
         
            +
              function isDetached(element) {
         
     | 
| 
      
 249 
     | 
    
         
            +
                return !document.contains(element)
         
     | 
| 
      
 250 
     | 
    
         
            +
              }
         
     | 
| 
      
 251 
     | 
    
         
            +
             
     | 
| 
       207 
252 
     | 
    
         
             
              function whenReady(callback) {
         
     | 
| 
       208 
253 
     | 
    
         
             
                // Values are "loading", "interactive" and "completed".
         
     | 
| 
       209 
254 
     | 
    
         
             
                // https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState
         
     | 
| 
         @@ -214,13 +259,28 @@ window.CapybaraLockstep = (function() { 
     | 
|
| 
       214 
259 
     | 
    
         
             
                }
         
     | 
| 
       215 
260 
     | 
    
         
             
              }
         
     | 
| 
       216 
261 
     | 
    
         | 
| 
      
 262 
     | 
    
         
            +
              function trackOldUnpoly() {
         
     | 
| 
      
 263 
     | 
    
         
            +
                // CapybaraLockstep.track() is called as the first script in the head.
         
     | 
| 
      
 264 
     | 
    
         
            +
                // Unpoly will be loaded after us, so we wait until DOMContentReady.
         
     | 
| 
      
 265 
     | 
    
         
            +
                whenReady(function() {
         
     | 
| 
      
 266 
     | 
    
         
            +
                  // Unpoly 0.x would wait one task after DOMContentLoaded before booting.
         
     | 
| 
      
 267 
     | 
    
         
            +
                  // There's a slim chance that Capybara can observe the page before compilers have run.
         
     | 
| 
      
 268 
     | 
    
         
            +
                  // Unpoly 1.0+ runs compilers on DOMContentLoaded, so there's no issue.
         
     | 
| 
      
 269 
     | 
    
         
            +
                  if (window.up?.version?.startsWith('0.')) {
         
     | 
| 
      
 270 
     | 
    
         
            +
                    startWork('Old Unpoly')
         
     | 
| 
      
 271 
     | 
    
         
            +
                    setTimeout(function () {
         
     | 
| 
      
 272 
     | 
    
         
            +
                      stopWork('Old Unpoly')
         
     | 
| 
      
 273 
     | 
    
         
            +
                    })
         
     | 
| 
      
 274 
     | 
    
         
            +
                  }
         
     | 
| 
      
 275 
     | 
    
         
            +
                })
         
     | 
| 
      
 276 
     | 
    
         
            +
              }
         
     | 
| 
      
 277 
     | 
    
         
            +
             
     | 
| 
       217 
278 
     | 
    
         
             
              function track() {
         
     | 
| 
      
 279 
     | 
    
         
            +
                trackOldUnpoly()
         
     | 
| 
       218 
280 
     | 
    
         
             
                trackFetch()
         
     | 
| 
       219 
281 
     | 
    
         
             
                trackXHR()
         
     | 
| 
       220 
     | 
    
         
            -
                 
     | 
| 
       221 
     | 
    
         
            -
                trackDynamicScripts()
         
     | 
| 
      
 282 
     | 
    
         
            +
                trackRemoteElements()
         
     | 
| 
       222 
283 
     | 
    
         
             
                trackJQuery()
         
     | 
| 
       223 
     | 
    
         
            -
                trackHydration()
         
     | 
| 
       224 
284 
     | 
    
         
             
              }
         
     | 
| 
       225 
285 
     | 
    
         | 
| 
       226 
286 
     | 
    
         
             
              function synchronize(callback) {
         
     | 
| 
         @@ -233,12 +293,14 @@ window.CapybaraLockstep = (function() { 
     | 
|
| 
       233 
293 
     | 
    
         | 
| 
       234 
294 
     | 
    
         
             
              return {
         
     | 
| 
       235 
295 
     | 
    
         
             
                track: track,
         
     | 
| 
      
 296 
     | 
    
         
            +
                isBusy: isBusy,
         
     | 
| 
      
 297 
     | 
    
         
            +
                isIdle: isIdle,
         
     | 
| 
       236 
298 
     | 
    
         
             
                startWork: startWork,
         
     | 
| 
       237 
299 
     | 
    
         
             
                stopWork: stopWork,
         
     | 
| 
       238 
300 
     | 
    
         
             
                synchronize: synchronize,
         
     | 
| 
       239 
     | 
    
         
            -
                 
     | 
| 
       240 
     | 
    
         
            -
             
     | 
| 
       241 
     | 
    
         
            -
                }
         
     | 
| 
      
 301 
     | 
    
         
            +
                reset: reset,
         
     | 
| 
      
 302 
     | 
    
         
            +
                set debug(value) { debug = value },
         
     | 
| 
      
 303 
     | 
    
         
            +
                set waitTasks(value) { waitTasks = value }
         
     | 
| 
       242 
304 
     | 
    
         
             
              }
         
     | 
| 
       243 
305 
     | 
    
         
             
            })()
         
     | 
| 
       244 
306 
     | 
    
         |