capybara-lockstep 0.2.1 → 0.3.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cccdbf8f6eb6582b123a366febeb67f863ac3801cf91f42f7a48f685e96d1860
4
- data.tar.gz: a3683b8f821c8dd706eff573fb9c0e5aa730a29dd9205d2441a3ab5a52d8f0b8
3
+ metadata.gz: d0d733ed210e1ba465d9d612fcf461618ee91c88a71b09f9c0020271e4ac28ef
4
+ data.tar.gz: 5f30713c033ebd57b30d37f27e545c2a4547f6d40cd013c15f4ebba50a1f3893
5
5
  SHA512:
6
- metadata.gz: e9fc97b54f2b797809c85d45d87b1d34a687ae18617bbf17bdad57d060da96eb876283610e5442cebf876ba520333e501ca635bd48436870359e0b806cfeabc0
7
- data.tar.gz: 157ae6e39f58f9437f0121fc3836d2bf3ac0c767b3dff2ff59f5f9b04516fdf4586df04ecc0bbd9c021615551878735edb910fdb393b005cfda59f906832b2f2
6
+ metadata.gz: d8fcf72c9736d98a80661f282527f0f53affb22b5aa2b537f7e7fbc4ea246ca2886e4f798343654c984321ca3d7dfec25043fa8e70d4341558a9073ea69911fe
7
+ data.tar.gz: c1f5144ed73a92f33e6ac36a0838401efae44d26ec199857f0e85a99b07afcd780d558ee6f3e859ac393db11649d186c8d56f72ddd6c2629fcddd68c2af907c0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- capybara-lockstep (0.2.1)
4
+ capybara-lockstep (0.3.2)
5
5
  activesupport (>= 3.2)
6
6
  capybara (>= 2.0)
7
7
  selenium-webdriver (>= 3)
@@ -9,11 +9,12 @@ PATH
9
9
  GEM
10
10
  remote: https://rubygems.org/
11
11
  specs:
12
- activesupport (5.2.4.3)
12
+ activesupport (6.1.3)
13
13
  concurrent-ruby (~> 1.0, >= 1.0.2)
14
- i18n (>= 0.7, < 2)
15
- minitest (~> 5.1)
16
- tzinfo (~> 1.1)
14
+ i18n (>= 1.6, < 2)
15
+ minitest (>= 5.1)
16
+ tzinfo (~> 2.0)
17
+ zeitwerk (~> 2.3)
17
18
  addressable (2.7.0)
18
19
  public_suffix (>= 2.0.2, < 5.0)
19
20
  capybara (3.35.3)
@@ -25,13 +26,15 @@ GEM
25
26
  regexp_parser (>= 1.5, < 3.0)
26
27
  xpath (~> 3.2)
27
28
  childprocess (3.0.0)
28
- concurrent-ruby (1.1.7)
29
+ concurrent-ruby (1.1.8)
29
30
  diff-lcs (1.3)
30
- i18n (1.8.2)
31
+ i18n (1.8.9)
31
32
  concurrent-ruby (~> 1.0)
32
33
  mini_mime (1.0.2)
33
- minitest (5.14.1)
34
- nokogiri (1.11.1-x86_64-linux)
34
+ mini_portile2 (2.5.0)
35
+ minitest (5.14.4)
36
+ nokogiri (1.11.1)
37
+ mini_portile2 (~> 2.5.0)
35
38
  racc (~> 1.4)
36
39
  public_suffix (4.0.6)
37
40
  racc (1.5.2)
@@ -53,15 +56,15 @@ GEM
53
56
  diff-lcs (>= 1.2.0, < 2.0)
54
57
  rspec-support (~> 3.7.0)
55
58
  rspec-support (3.7.0)
56
- rubyzip (1.3.0)
59
+ rubyzip (2.3.0)
57
60
  selenium-webdriver (3.142.7)
58
61
  childprocess (>= 0.5, < 4.0)
59
62
  rubyzip (>= 1.2.2)
60
- thread_safe (0.3.6)
61
- tzinfo (1.2.7)
62
- thread_safe (~> 0.1)
63
+ tzinfo (2.0.4)
64
+ concurrent-ruby (~> 1.0)
63
65
  xpath (3.2.0)
64
66
  nokogiri (~> 1.8)
67
+ zeitwerk (2.4.2)
65
68
 
66
69
  PLATFORMS
67
70
  ruby
data/README.md CHANGED
@@ -59,7 +59,7 @@ Installation
59
59
  Check if your application satisfies all requirements for capybara-lockstep:
60
60
 
61
61
  - Capybara 2 or higher.
62
- - Your Capybara driver must use [selenium-webdriver](https://rubygems.org/gems/selenium-webdriver/). capybara-headless deactivates itself for any other driver.
62
+ - Your Capybara driver must use [selenium-webdriver](https://rubygems.org/gems/selenium-webdriver/). capybara-lockstep deactivates itself for any other driver.
63
63
  - This gem was only tested with a Selenium-controlled Chrome browser. [Chrome in headless mode](https://makandracards.com/makandra/492109-running-capybara-tests-in-headless-chrome) is recommended, but not required.
64
64
  - This gem was only tested with Rails, but there's no Rails dependency.
65
65
 
@@ -170,9 +170,13 @@ app.directive('body', function() {
170
170
 
171
171
  capybara-lockstep will automatically patch Capybara to wait for the browser after every command.
172
172
 
173
- Run your test suite to see if integration was successful and whether stability improves.
173
+ Run your test suite to see if integration was successful and whether stability improves. During validation we recommend to activate `Capybara::Lockstep.debug = true` in your `spec_helper.rb` (RSpec) or `env.rb` (Cucumber). You should see messages like this in your console:
174
174
 
175
- When you run into issues or don't see an effect, try activating `Capybara::Lockstep.debug = true` in your `spec_helper.rb` (RSpec) or `env.rb` (Cucumber).
175
+ ```text
176
+ [Capybara::Lockstep] Synchronizing
177
+ [Capybara::Lockstep] Finished waiting for JavaScript
178
+ [Capybara::Lockstep] Synchronized successfully
179
+ ```
176
180
 
177
181
  Note that you may see some failures from tests with wrong assertions, which sometimes passed due to lucky timing.
178
182
 
@@ -183,7 +187,7 @@ capybara-lockstep may or may not impact the runtime of your test suite. It depen
183
187
 
184
188
  While waiting for the browser to be idle does take a few milliseconds, Capybara no longer needs to retry failed commands. You will also save time from not needing to re-run failed tests.
185
189
 
186
- In casual testing I experienced a negative performance impact between 0% and 10%.
190
+ In casual testing I experienced a performance impact between +/- 10%.
187
191
 
188
192
 
189
193
  ## Debugging log
@@ -197,7 +201,9 @@ Capybara::Lockstep.debug = true
197
201
  You should now see messages like this during your test runs:
198
202
 
199
203
  ```
200
- [Capybara::Lockstep] JavaScript or AJAX requests are running
204
+ [Capybara::Lockstep] Synchronizing
205
+ [Capybara::Lockstep] Finished waiting for JavaScript
206
+ [Capybara::Lockstep] Synchronized successfully
201
207
  ```
202
208
 
203
209
  You may also configure logging to an existing logger object:
@@ -220,7 +226,6 @@ ensure
220
226
  end
221
227
  ```
222
228
 
223
-
224
229
  ## Timeout
225
230
 
226
231
  By default capybara-lockstep will wait up to 10 seconds for the page initialize and for JavaScript and AJAX request to finish.
@@ -231,8 +236,28 @@ You can configure a different timeout:
231
236
  Capybara::Lockstep.timeout = 5 # seconds
232
237
  ```
233
238
 
239
+ ## Ruby API
240
+
241
+ capybara-lockstep will automatically patch Capybara to wait for the browser after every command. **This should be enough for most test suites**.
242
+
243
+ For additional edge cases you may interact with capybara-lockstep from your Ruby code.
244
+
245
+
246
+ ### Waiting until the browser is idle
234
247
 
248
+ This will block until the document was loaded, the DOM has been hydrated and all AJAX requests have concluded:
235
249
 
250
+ ```ruby
251
+ Capybara::Lockstep.synchronize
252
+ ```
253
+
254
+ An example use case is a Cucumber step that explicitely waits for JavaScript to finish, in the rare occasion where capybara-lockstep hasn't picked up an event or request:
255
+
256
+ ```gherkin
257
+ When 'I wait for the page to load' do
258
+ Capybara::Lockstep.synchronize
259
+ end
260
+ ```
236
261
 
237
262
  ## JavaScript API
238
263
 
@@ -270,55 +295,29 @@ CapybaraLockstep.isIdle() // => true
270
295
 
271
296
  ### Waiting until the browser is idle
272
297
 
273
- ```js
274
- CapybaraLockstep.awaitIdle(callback)
275
- ```
298
+ This will run the given callback once the browser is considered to be idle:
276
299
 
277
- ## Ruby API
278
-
279
- capybara-lockstep will automatically patch Capybara to wait for the browser after every command. **This should be enough for most test suites**.
280
-
281
- For additional edge cases you may interact with capybara-lockstep from your Ruby code.
282
-
283
-
284
- ### Waiting until the browser is idle
285
-
286
- This will block until the document was loaded and the DOM has been hydrated:
287
-
288
- ```ruby
289
- Capybara::Lockstep.await_initialized
290
- ```
291
-
292
- This will block while the browser is busy with JavaScript and AJAX requests:
293
-
294
- ```ruby
295
- Capybara::Lockstep.await_idle
296
- ```
297
-
298
- ### Checking if the browser is busy
299
-
300
- You can query capybara-lockstep whether it considers the browser to be busy or idle:
301
-
302
- ```ruby
303
- Capybara::Lockstep.idle? # => true
304
- Capybara::Lockstep.busy? # => false
300
+ ```js
301
+ CapybaraLockstep.synchronize(callback)
305
302
  ```
306
303
 
307
-
308
304
  ## Development
309
305
 
310
306
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
311
307
 
312
308
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
313
309
 
310
+
314
311
  ## Contributing
315
312
 
316
- Pull requests are welcome on GitHub at <https://github.com/makandra/capistrano-lockstep>.
313
+ Pull requests are welcome on GitHub at <https://github.com/makandra/capybara-lockstep>.
314
+
317
315
 
318
316
  ## License
319
317
 
320
318
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
321
319
 
320
+
322
321
  ## Credits
323
322
 
324
323
  Henning Koch ([@triskweline](https://twitter.com/triskweline)) from [makandra](https://makandra.com).
@@ -7,7 +7,7 @@ Gem::Specification.new do |spec|
7
7
  spec.email = ["henning.koch@makandra.de"]
8
8
 
9
9
  spec.summary = "Synchronize Capybara commands with client-side JavaScript and AJAX requests"
10
- spec.homepage = "https://rubygems.org/gems/capybara-lockstep"
10
+ spec.homepage = "https://github.com/makandra/capybara-lockstep"
11
11
  spec.license = "MIT"
12
12
  spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
13
13
 
@@ -10,8 +10,8 @@ end
10
10
 
11
11
  require_relative 'capybara-lockstep/version'
12
12
  require_relative 'capybara-lockstep/errors'
13
- require_relative 'capybara-lockstep/patiently'
14
13
  require_relative 'capybara-lockstep/configuration'
14
+ require_relative 'capybara-lockstep/logging'
15
15
  require_relative 'capybara-lockstep/lockstep'
16
16
  require_relative 'capybara-lockstep/capybara_ext'
17
17
  require_relative 'capybara-lockstep/helper'
@@ -1,26 +1,44 @@
1
1
  module Capybara
2
2
  module Lockstep
3
3
  module VisitWithWaiting
4
- def visit(*args, **kwargs, &block)
5
- super(*args, **kwargs, &block).tap do
6
- # There is a step that changes drivers mid-scenario.
7
- # It works by creating a new Capybara session and re-visits the
8
- # URL from the previous session. If this happens before a URL is ever
9
- # loaded, it re-visits the URL "data:", which will never "finish"
10
- # initializing.
11
- unless args[0].start_with?('data:')
12
- Capybara::Lockstep.await_initialized
4
+ def visit(*args, &block)
5
+ url = args[0]
6
+ # Some of our apps have a Cucumber step that changes drivers mid-scenario.
7
+ # It works by creating a new Capybara session and re-visits the URL from the
8
+ # previous session. If this happens before a URL is ever loaded,
9
+ # it re-visits the URL "data:", which will never "finish" initializing.
10
+ # Also when opening a new tab via Capybara, the initial URL is about:blank.
11
+ visiting_remote_url = !(url.start_with?('data:') || url.start_with?('about:'))
12
+
13
+ if visiting_remote_url
14
+ # We're about to leave this screen, killing all in-flight requests.
15
+ Capybara::Lockstep.synchronize
16
+ end
17
+
18
+ super(*args, &block).tap do
19
+ if visiting_remote_url
20
+ # puts "After visit: unsynchronizing"
21
+ Capybara::Lockstep.synchronized = false
13
22
  end
14
23
  end
15
24
  end
16
25
  end
26
+ end
27
+ end
28
+
17
29
 
18
- module AwaitIdle
19
- def await_idle(meth)
30
+ Capybara::Session.class_eval do
31
+ prepend Capybara::Lockstep::VisitWithWaiting
32
+ end
33
+
34
+ module Capybara
35
+ module Lockstep
36
+ module UnsychronizeAfter
37
+ def unsychronize_after(meth)
20
38
  mod = Module.new do
21
39
  define_method meth do |*args, &block|
22
40
  super(*args, &block).tap do
23
- Capybara::Lockstep.await_idle
41
+ Capybara::Lockstep.synchronized = false
24
42
  end
25
43
  end
26
44
  end
@@ -30,10 +48,6 @@ module Capybara
30
48
  end
31
49
  end
32
50
 
33
- Capybara::Session.class_eval do
34
- prepend Capybara::Lockstep::VisitWithWaiting
35
- end
36
-
37
51
  # Capybara 3 has driver-specific Node classes which sometimes
38
52
  # super to Capybara::Selenium::Node, but not always.
39
53
  node_classes = [
@@ -52,20 +66,38 @@ end
52
66
 
53
67
  node_classes.each do |node_class|
54
68
  node_class.class_eval do
55
- extend Capybara::Lockstep::AwaitIdle
69
+ extend Capybara::Lockstep::UnsychronizeAfter
56
70
 
57
- await_idle :set
58
- await_idle :select_option
59
- await_idle :unselect_option
60
- await_idle :click
61
- await_idle :right_click
62
- await_idle :double_click
63
- await_idle :send_keys
64
- await_idle :hover
65
- await_idle :drag_to
66
- await_idle :drop
67
- await_idle :scroll_by
68
- await_idle :scroll_to
69
- await_idle :trigger
71
+ unsychronize_after :set
72
+ unsychronize_after :select_option
73
+ unsychronize_after :unselect_option
74
+ unsychronize_after :click
75
+ unsychronize_after :right_click
76
+ unsychronize_after :double_click
77
+ unsychronize_after :send_keys
78
+ unsychronize_after :hover
79
+ unsychronize_after :drag_to
80
+ unsychronize_after :drop
81
+ unsychronize_after :scroll_by
82
+ unsychronize_after :scroll_to
83
+ unsychronize_after :trigger
70
84
  end
71
85
  end
86
+
87
+ module Capybara
88
+ module Lockstep
89
+ module SynchronizeWithCatchUp
90
+ def synchronize(*args, &block)
91
+ # This method is called very frequently by capybara.
92
+ # We use the { lazy } option to only synchronize when we're out of sync.
93
+ Capybara::Lockstep.synchronize(lazy: true)
94
+
95
+ super(*args, &block)
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ Capybara::Node::Base.class_eval do
102
+ prepend Capybara::Lockstep::SynchronizeWithCatchUp
103
+ end
@@ -30,6 +30,10 @@ module Capybara
30
30
  @enabled = enabled
31
31
  end
32
32
 
33
+ def disabled?
34
+ !enabled?
35
+ end
36
+
33
37
  private
34
38
 
35
39
  def javascript_driver?
@@ -37,7 +37,7 @@ window.CapybaraLockstep = (function() {
37
37
 
38
38
  if (isIdle()) {
39
39
  idleCallbacks.forEach(function(callback) {
40
- callback('JavaScript has finished')
40
+ callback('Finished waiting for JavaScript')
41
41
  })
42
42
  idleCallbacks = []
43
43
  }
@@ -116,7 +116,7 @@ window.CapybaraLockstep = (function() {
116
116
  // Dynamic imports or analytics snippets may insert a <script src>
117
117
  // tag that loads and executes additional JavaScript. We want to be isBusy()
118
118
  // until such scripts have loaded or errored.
119
- var observer = new MutationObserver(onMutated)
119
+ var observer = new MutationObserver(onAnyElementChanged)
120
120
  observer.observe(document, { subtree: true, childList: true })
121
121
  }
122
122
 
@@ -139,6 +139,28 @@ window.CapybaraLockstep = (function() {
139
139
  })
140
140
  }
141
141
 
142
+ var INITIALIZING_ATTRIBUTE = 'data-initializing'
143
+
144
+ function trackHydration() {
145
+ // Until we have a body on which we can observe [data-initializing]
146
+ // we consider ourselves busy.
147
+ startWork()
148
+ whenReady(function() {
149
+ stopWork()
150
+ if (document.body.hasAttribute(INITIALIZING_ATTRIBUTE)) {
151
+ startWork()
152
+ var observer = new MutationObserver(onInitializingAttributeChanged)
153
+ observer.observe(document.body, { attributes: true, attributeFilter: [INITIALIZING_ATTRIBUTE] })
154
+ }
155
+ })
156
+ }
157
+
158
+ function onInitializingAttributeChanged() {
159
+ if (!document.body.hasAttribute(INITIALIZING_ATTRIBUTE)) {
160
+ stopWork()
161
+ }
162
+ }
163
+
142
164
  function isRemoteScript(node) {
143
165
  if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'SCRIPT') {
144
166
  var src = node.getAttribute('src')
@@ -155,7 +177,7 @@ window.CapybaraLockstep = (function() {
155
177
  script.addEventListener('error', stopWork)
156
178
  }
157
179
 
158
- function onMutated(changes) {
180
+ function onAnyElementChanged(changes) {
159
181
  changes.forEach(function(change) {
160
182
  change.addedNodes.forEach(function(addedNode) {
161
183
  if (isRemoteScript(addedNode)) {
@@ -168,7 +190,7 @@ window.CapybaraLockstep = (function() {
168
190
  function whenReady(callback) {
169
191
  // Values are "loading", "interactive" and "completed".
170
192
  // https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState
171
- if (document.readyState != 'loading') {
193
+ if (document.readyState !== 'loading') {
172
194
  callback()
173
195
  } else {
174
196
  document.addEventListener('DOMContentLoaded', callback)
@@ -182,9 +204,10 @@ window.CapybaraLockstep = (function() {
182
204
  trackHistory()
183
205
  trackDynamicScripts()
184
206
  trackJQuery()
207
+ trackHydration()
185
208
  }
186
209
 
187
- function awaitIdle(callback) {
210
+ function synchronize(callback) {
188
211
  if (isIdle()) {
189
212
  callback()
190
213
  } else {
@@ -196,7 +219,7 @@ window.CapybaraLockstep = (function() {
196
219
  track: track,
197
220
  startWork: startWork,
198
221
  stopWork: stopWork,
199
- awaitIdle: awaitIdle,
222
+ synchronize: synchronize,
200
223
  isIdle: isIdle,
201
224
  isBusy: isBusy
202
225
  }
@@ -1,116 +1,91 @@
1
1
  module Capybara
2
2
  module Lockstep
3
3
  class << self
4
- include Patiently
5
4
  include Configuration
5
+ include Logging
6
6
 
7
- def await_idle
8
- return unless enabled?
7
+ attr_accessor :synchronized
9
8
 
10
- ignoring_alerts do
11
- # evaluate_async_script also times out after Capybara.default_max_wait_time
9
+ def synchronized?
10
+ value = page.instance_variable_get(:@lockstep_synchronized)
11
+ # We consider a new Capybara session to be synchronized.
12
+ # This will be set to false after our first visit().
13
+ value.nil? ? true : value
14
+ end
15
+
16
+ def synchronized=(value)
17
+ page.instance_variable_set(:@lockstep_synchronized, value)
18
+ end
19
+
20
+ ERROR_SNIPPET_MISSING = 'Cannot synchronize: Capybara::Lockstep JavaScript snippet is missing on page'
21
+ ERROR_PAGE_MISSING = 'Cannot synchronize before initial Capybara visit'
22
+ ERROR_ALERT_OPEN = 'Cannot synchronize while an alert is open'
23
+
24
+ def synchronize(lazy: false)
25
+ if (lazy && synchronized?) || @synchronizing || disabled?
26
+ return
27
+ end
28
+
29
+ @synchronizing = true
30
+
31
+ log 'Synchronizing'
32
+
33
+ begin
12
34
  with_max_wait_time(timeout) do
13
35
  message_from_js = evaluate_async_script(<<~JS)
14
36
  let done = arguments[0]
15
- if (window.CapybaraLockstep) {
16
- CapybaraLockstep.awaitIdle(done)
37
+ let synchronize = () => {
38
+ if (window.CapybaraLockstep) {
39
+ CapybaraLockstep.synchronize(done)
40
+ } else {
41
+ done(#{ERROR_SNIPPET_MISSING.to_json})
42
+ }
43
+ }
44
+ let protocol = location.protocol
45
+ if (protocol === 'data:' || protocol == 'about:') {
46
+ done(#{ERROR_PAGE_MISSING.to_json})
47
+ } else if (document.readyState === 'complete') {
48
+ synchronize()
17
49
  } else {
18
- done('Cannot synchronize: Capybara::Lockstep was not included in page')
50
+ window.addEventListener('load', synchronize)
19
51
  }
20
52
  JS
21
- log(message_from_js)
22
- end
23
- end
24
- end
25
-
26
- def await_initialized
27
- return unless enabled?
28
53
 
29
- # We're retrying the initialize check every few ms.
30
- # Don't clutter the log with dozens of identical messages.
31
- last_logged_reason = nil
32
-
33
- patiently(timeout) do
34
- if (reason = initialize_reason)
35
- if reason != last_logged_reason
36
- log(reason)
37
- last_logged_reason = reason
54
+ case message_from_js
55
+ when ERROR_PAGE_MISSING
56
+ log(message_from_js)
57
+ self.synchronized = false
58
+ when ERROR_SNIPPET_MISSING
59
+ log(message_from_js)
60
+ self.synchronized = false
61
+ else
62
+ log message_from_js
63
+ log "Synchronized successfully"
64
+ self.synchronized = true
38
65
  end
39
-
40
- # Raise an exception that will be retried by `patiently`
41
- raise Busy, reason
42
66
  end
67
+ rescue ::Selenium::WebDriver::Error::UnexpectedAlertOpenError
68
+ log ERROR_ALERT_OPEN
69
+ @synchronized = false
70
+ # Don't raise an error, this will happen in an innocent test.
71
+ # We will retry on the next Capybara synchronize call.
72
+ rescue StandardError => e
73
+ log "#{e.class.name} while synchronizing: #{e.message}"
74
+ @synchronized = false
75
+ raise e
76
+ ensure
77
+ @synchronizing = false
43
78
  end
44
79
  end
45
80
 
46
- def idle?
47
- unless enabled?
48
- return true
49
- end
50
-
51
- result = execute_script(<<~JS)
52
- if (window.CapybaraLockstep) {
53
- return CapybaraLockstep.isIdle()
54
- } else {
55
- return 'Cannot check busy state: Capybara::Lockstep was not included in page'
56
- }
57
- JS
58
-
59
- if result.is_a?(String)
60
- log(result)
61
- # When the snippet is missing we assume that the browser is idle.
62
- # Otherwise we would wait forever.
63
- true
64
- else
65
- result
66
- end
67
- end
68
-
69
- def busy?
70
- !idle?
71
- end
72
-
73
81
  private
74
82
 
75
- def initialize_reason
76
- ignoring_alerts do
77
- execute_script(<<~JS)
78
- if (location.href.indexOf('data:') == 0) {
79
- return 'Requesting initial page'
80
- }
81
-
82
- if (document.readyState !== "complete") {
83
- return 'Document is loading'
84
- }
85
-
86
- // The application layouts render a <body data-initializing>.
87
- // The [data-initializing] attribute is removed by an Angular directive or Unpoly compiler (frontend).
88
- // to signal that all elements have been activated.
89
- if (document.querySelector('body[data-initializing]')) {
90
- return 'DOM is being hydrated'
91
- }
92
-
93
- if (window.CapybaraLockstep && CapybaraLockstep.isBusy()) {
94
- return 'JavaScript or AJAX requests are running'
95
- }
96
-
97
- return false
98
- JS
99
- end
100
- end
101
-
102
83
  def page
103
84
  Capybara.current_session
104
85
  end
105
86
 
106
87
  delegate :evaluate_script, :evaluate_async_script, :execute_script, :driver, to: :page
107
88
 
108
- def ignoring_alerts(&block)
109
- block.call
110
- rescue ::Selenium::WebDriver::Error::UnexpectedAlertOpenError
111
- # noop
112
- end
113
-
114
89
  def with_max_wait_time(seconds, &block)
115
90
  old_max_wait_time = Capybara.default_max_wait_time
116
91
  Capybara.default_max_wait_time = seconds
@@ -121,17 +96,10 @@ module Capybara
121
96
  end
122
97
  end
123
98
 
124
- def log(message)
125
- if debug? && message.present?
126
- message = "[Capybara::Lockstep] #{message}"
127
- if @debug.respond_to?(:debug)
128
- # If someone set Capybara::Lockstep to a logger, use that
129
- @debug.debug(message)
130
- else
131
- # Otherwise print to STDOUT
132
- puts message
133
- end
134
- end
99
+ def ignoring_alerts(&block)
100
+ block.call
101
+ rescue ::Selenium::WebDriver::Error::UnexpectedAlertOpenError
102
+ # no-op
135
103
  end
136
104
 
137
105
  end
@@ -0,0 +1,18 @@
1
+ module Capybara
2
+ module Lockstep
3
+ module Logging
4
+ def log(message)
5
+ if debug? && message.present?
6
+ message = "[Capybara::Lockstep] #{message}"
7
+ if @debug.respond_to?(:debug)
8
+ # If someone set Capybara::Lockstep to a logger, use that
9
+ @debug.debug(message)
10
+ else
11
+ # Otherwise print to STDOUT
12
+ puts message
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,5 +1,5 @@
1
1
  module Capybara
2
2
  module Lockstep
3
- VERSION = "0.2.1"
3
+ VERSION = "0.3.2"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capybara-lockstep
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Henning Koch
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-03 00:00:00.000000000 Z
11
+ date: 2021-03-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: capybara
@@ -77,14 +77,14 @@ files:
77
77
  - lib/capybara-lockstep/helper.js
78
78
  - lib/capybara-lockstep/helper.rb
79
79
  - lib/capybara-lockstep/lockstep.rb
80
- - lib/capybara-lockstep/patiently.rb
80
+ - lib/capybara-lockstep/logging.rb
81
81
  - lib/capybara-lockstep/version.rb
82
- homepage: https://rubygems.org/gems/capybara-lockstep
82
+ homepage: https://github.com/makandra/capybara-lockstep
83
83
  licenses:
84
84
  - MIT
85
85
  metadata:
86
- homepage_uri: https://rubygems.org/gems/capybara-lockstep
87
- source_code_uri: https://rubygems.org/gems/capybara-lockstep
86
+ homepage_uri: https://github.com/makandra/capybara-lockstep
87
+ source_code_uri: https://github.com/makandra/capybara-lockstep
88
88
  post_install_message:
89
89
  rdoc_options: []
90
90
  require_paths:
@@ -100,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
100
100
  - !ruby/object:Gem::Version
101
101
  version: '0'
102
102
  requirements: []
103
- rubygems_version: 3.2.6
103
+ rubygems_version: 3.2.5
104
104
  signing_key:
105
105
  specification_version: 4
106
106
  summary: Synchronize Capybara commands with client-side JavaScript and AJAX requests
@@ -1,58 +0,0 @@
1
- module Capybara
2
- module Lockstep
3
- # Ported from https://github.com/makandra/spreewald/blob/master/lib/spreewald_support/tolerance_for_selenium_sync_issues.rb
4
- module Patiently
5
-
6
- RETRY_ERRORS = %w[
7
- Capybara::Lockstep::Busy
8
- Capybara::ElementNotFound
9
- Spec::Expectations::ExpectationNotMetError
10
- RSpec::Expectations::ExpectationNotMetError
11
- Minitest::Assertion
12
- Capybara::Poltergeist::ClickFailed
13
- Capybara::ExpectationNotMet
14
- Selenium::WebDriver::Error::StaleElementReferenceError
15
- Selenium::WebDriver::Error::NoAlertPresentError
16
- Selenium::WebDriver::Error::ElementNotVisibleError
17
- Selenium::WebDriver::Error::NoSuchFrameError
18
- Selenium::WebDriver::Error::NoAlertPresentError
19
- Selenium::WebDriver::Error::JavascriptError
20
- Selenium::WebDriver::Error::UnknownError
21
- Selenium::WebDriver::Error::NoSuchAlertError
22
- ]
23
-
24
- # evaluate_script latency is ~ 0.025s
25
- WAIT_PERIOD = 0.03
26
-
27
- def patiently(timeout = Capybara.default_max_wait_time, &block)
28
- started = monotonic_time
29
- tries = 0
30
- begin
31
- tries += 1
32
- block.call
33
- rescue Exception => e
34
- raise e unless retryable_error?(e)
35
- raise e if (monotonic_time - started > timeout && tries >= 2)
36
- sleep(WAIT_PERIOD)
37
- if monotonic_time == started
38
- raise Capybara::FrozenInTime, "time appears to be frozen, Capybara does not work with libraries which freeze time, consider using time travelling instead"
39
- end
40
- retry
41
- end
42
- end
43
-
44
- private
45
-
46
- def monotonic_time
47
- # We use the system clock (i.e. seconds since boot) to calculate the time,
48
- # because Time.now may be frozen
49
- Process.clock_gettime(Process::CLOCK_MONOTONIC)
50
- end
51
-
52
- def retryable_error?(e)
53
- RETRY_ERRORS.include?(e.class.name)
54
- end
55
-
56
- end
57
- end
58
- end