capybara-lockstep 0.2.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
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