capybara-lockstep 0.2.3 → 0.3.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/Gemfile.lock +1 -1
- data/README.md +26 -33
- data/lib/capybara-lockstep.rb +1 -1
- data/lib/capybara-lockstep/capybara_ext.rb +33 -27
- data/lib/capybara-lockstep/configuration.rb +4 -0
- data/lib/capybara-lockstep/helper.js +29 -6
- data/lib/capybara-lockstep/lockstep.rb +60 -121
- data/lib/capybara-lockstep/logging.rb +18 -0
- data/lib/capybara-lockstep/version.rb +1 -1
- metadata +3 -3
- data/lib/capybara-lockstep/patiently.rb +0 -58
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6e993a9ec82edc1bbea78694f16ae5b3e171255db9eb7e14064922b153d1838d
|
4
|
+
data.tar.gz: 1086e7149ca202b105fd0472c839451845079e05c777a0dba839483fe05c473b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e54e30ae12effa97dcdbd73d0f07b465b90270efc3ddd3ad83fcde7a4d305164ac0380f7ab3128a37870e78862483659eb01ff6bcdf8fade32cd52c3024d5c31
|
7
|
+
data.tar.gz: c067bf3f9e0be7d6a84668ee7412ea7d93d450f109e8018940e72392277bcc0f5d0ef92717a1692dee8e0443509bc9317a7b403620b6eb62bdb78539ac036098
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -220,7 +220,6 @@ ensure
|
|
220
220
|
end
|
221
221
|
```
|
222
222
|
|
223
|
-
|
224
223
|
## Timeout
|
225
224
|
|
226
225
|
By default capybara-lockstep will wait up to 10 seconds for the page initialize and for JavaScript and AJAX request to finish.
|
@@ -231,8 +230,28 @@ You can configure a different timeout:
|
|
231
230
|
Capybara::Lockstep.timeout = 5 # seconds
|
232
231
|
```
|
233
232
|
|
233
|
+
## Ruby API
|
234
|
+
|
235
|
+
capybara-lockstep will automatically patch Capybara to wait for the browser after every command. **This should be enough for most test suites**.
|
236
|
+
|
237
|
+
For additional edge cases you may interact with capybara-lockstep from your Ruby code.
|
238
|
+
|
239
|
+
|
240
|
+
### Waiting until the browser is idle
|
241
|
+
|
242
|
+
This will block until the document was loaded, the DOM has been hydrated and all AJAX requests have concluded:
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
Capybara::Lockstep.synchronize
|
246
|
+
```
|
234
247
|
|
248
|
+
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:
|
235
249
|
|
250
|
+
```gherkin
|
251
|
+
When 'I wait for the page to load' do
|
252
|
+
Capybara::Lockstep.synchronize
|
253
|
+
end
|
254
|
+
```
|
236
255
|
|
237
256
|
## JavaScript API
|
238
257
|
|
@@ -270,55 +289,29 @@ CapybaraLockstep.isIdle() // => true
|
|
270
289
|
|
271
290
|
### Waiting until the browser is idle
|
272
291
|
|
273
|
-
|
274
|
-
CapybaraLockstep.awaitIdle(callback)
|
275
|
-
```
|
276
|
-
|
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:
|
292
|
+
This will run the given callback once the browser is considered to be idle:
|
301
293
|
|
302
|
-
```
|
303
|
-
|
304
|
-
Capybara::Lockstep.busy? # => false
|
294
|
+
```js
|
295
|
+
CapybaraLockstep.synchronize(callback)
|
305
296
|
```
|
306
297
|
|
307
|
-
|
308
298
|
## Development
|
309
299
|
|
310
300
|
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
301
|
|
312
302
|
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
303
|
|
304
|
+
|
314
305
|
## Contributing
|
315
306
|
|
316
307
|
Pull requests are welcome on GitHub at <https://github.com/makandra/capybara-lockstep>.
|
317
308
|
|
309
|
+
|
318
310
|
## License
|
319
311
|
|
320
312
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
321
313
|
|
314
|
+
|
322
315
|
## Credits
|
323
316
|
|
324
317
|
Henning Koch ([@triskweline](https://twitter.com/triskweline)) from [makandra](https://makandra.com).
|
data/lib/capybara-lockstep.rb
CHANGED
@@ -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'
|
@@ -2,18 +2,23 @@ module Capybara
|
|
2
2
|
module Lockstep
|
3
3
|
module VisitWithWaiting
|
4
4
|
def visit(*args, &block)
|
5
|
-
|
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:'))
|
6
12
|
|
7
|
-
|
13
|
+
if visiting_remote_url
|
14
|
+
# We're about to leave this screen, killing all in-flight requests.
|
15
|
+
Capybara::Lockstep.synchronize
|
16
|
+
end
|
8
17
|
|
9
18
|
super(*args, &block).tap do
|
10
|
-
# There is a step that changes drivers mid-scenario.
|
11
|
-
# It works by creating a new Capybara session and re-visits the
|
12
|
-
# URL from the previous session. If this happens before a URL is ever
|
13
|
-
# loaded, it re-visits the URL "data:", which will never "finish"
|
14
|
-
# initializing.
|
15
19
|
if visiting_remote_url
|
16
|
-
|
20
|
+
# puts "After visit: unsynchronizing"
|
21
|
+
Capybara::Lockstep.synchronized = false
|
17
22
|
end
|
18
23
|
end
|
19
24
|
end
|
@@ -28,13 +33,12 @@ end
|
|
28
33
|
|
29
34
|
module Capybara
|
30
35
|
module Lockstep
|
31
|
-
module
|
32
|
-
def
|
36
|
+
module UnsychronizeAfter
|
37
|
+
def unsychronize_after(meth)
|
33
38
|
mod = Module.new do
|
34
39
|
define_method meth do |*args, &block|
|
35
|
-
Capybara::Lockstep.catch_up
|
36
40
|
super(*args, &block).tap do
|
37
|
-
Capybara::Lockstep.
|
41
|
+
Capybara::Lockstep.synchronized = false
|
38
42
|
end
|
39
43
|
end
|
40
44
|
end
|
@@ -62,21 +66,21 @@ end
|
|
62
66
|
|
63
67
|
node_classes.each do |node_class|
|
64
68
|
node_class.class_eval do
|
65
|
-
extend Capybara::Lockstep::
|
69
|
+
extend Capybara::Lockstep::UnsychronizeAfter
|
66
70
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
80
84
|
end
|
81
85
|
end
|
82
86
|
|
@@ -84,7 +88,9 @@ module Capybara
|
|
84
88
|
module Lockstep
|
85
89
|
module SynchronizeWithCatchUp
|
86
90
|
def synchronize(*args, &block)
|
87
|
-
|
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)
|
88
94
|
|
89
95
|
super(*args, &block)
|
90
96
|
end
|
@@ -37,7 +37,7 @@ window.CapybaraLockstep = (function() {
|
|
37
37
|
|
38
38
|
if (isIdle()) {
|
39
39
|
idleCallbacks.forEach(function(callback) {
|
40
|
-
callback('
|
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(
|
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
|
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
|
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
|
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
|
-
|
222
|
+
synchronize: synchronize,
|
200
223
|
isIdle: isIdle,
|
201
224
|
isBusy: isBusy
|
202
225
|
}
|
@@ -1,133 +1,79 @@
|
|
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
|
-
|
8
|
-
@delay_await_idle = false
|
9
|
-
return unless enabled?
|
10
|
-
|
11
|
-
with_max_wait_time(timeout) do
|
12
|
-
message_from_js = evaluate_async_script(<<~JS)
|
13
|
-
let done = arguments[0]
|
14
|
-
if (window.CapybaraLockstep) {
|
15
|
-
CapybaraLockstep.awaitIdle(done)
|
16
|
-
} else {
|
17
|
-
done('Cannot synchronize: Capybara::Lockstep was not included in page')
|
18
|
-
}
|
19
|
-
JS
|
20
|
-
log(message_from_js)
|
21
|
-
end
|
22
|
-
rescue ::Selenium::WebDriver::Error::UnexpectedAlertOpenError
|
23
|
-
log 'Cannot synchronize: Alert is open'
|
24
|
-
@delay_await_idle = true
|
25
|
-
end
|
7
|
+
attr_accessor :synchronized
|
26
8
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
31
15
|
|
32
|
-
|
33
|
-
|
34
|
-
|
16
|
+
def synchronized=(value)
|
17
|
+
page.instance_variable_set(:@lockstep_synchronized, value)
|
18
|
+
end
|
35
19
|
|
36
|
-
|
37
|
-
|
38
|
-
if reason != last_logged_reason
|
39
|
-
log(reason)
|
40
|
-
last_logged_reason = reason
|
41
|
-
end
|
20
|
+
ERROR_SNIPPET_MISSING = 'Cannot synchronize: Capybara::Lockstep JavaScript snippet is missing on page'
|
21
|
+
ERROR_PAGE_MISSING = 'Cannot synchronize before initial Capybara visit'
|
42
22
|
|
43
|
-
|
44
|
-
|
45
|
-
|
23
|
+
def synchronize(lazy: false)
|
24
|
+
if (lazy && synchronized?) || @synchronizing || disabled?
|
25
|
+
return
|
46
26
|
end
|
47
|
-
rescue ::Selenium::WebDriver::Error::UnexpectedAlertOpenError
|
48
|
-
log 'Cannot synchronize: Alert is open'
|
49
|
-
@delay_await_initialized = true
|
50
|
-
end
|
51
27
|
|
52
|
-
|
53
|
-
|
28
|
+
@synchronizing = true
|
29
|
+
|
30
|
+
log 'Synchronizing'
|
54
31
|
|
55
32
|
begin
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
33
|
+
with_max_wait_time(timeout) do
|
34
|
+
message_from_js = evaluate_async_script(<<~JS)
|
35
|
+
let done = arguments[0]
|
36
|
+
let synchronize = () => {
|
37
|
+
if (window.CapybaraLockstep) {
|
38
|
+
CapybaraLockstep.synchronize(done)
|
39
|
+
} else {
|
40
|
+
done(#{ERROR_SNIPPET_MISSING.to_json})
|
41
|
+
}
|
42
|
+
}
|
43
|
+
let protocol = location.protocol
|
44
|
+
if (protocol === 'data:' || protocol == 'about:') {
|
45
|
+
done(#{ERROR_PAGE_MISSING.to_json})
|
46
|
+
} else if (document.readyState === 'complete') {
|
47
|
+
synchronize()
|
48
|
+
} else {
|
49
|
+
window.addEventListener('load', synchronize)
|
50
|
+
}
|
51
|
+
JS
|
52
|
+
|
53
|
+
case message_from_js
|
54
|
+
when ERROR_PAGE_MISSING
|
55
|
+
log(message_from_js)
|
56
|
+
self.synchronized = false
|
57
|
+
when ERROR_SNIPPET_MISSING
|
58
|
+
log(message_from_js)
|
59
|
+
self.synchronized = false
|
60
|
+
else
|
61
|
+
log message_from_js
|
62
|
+
log "Synchronized sucessfully"
|
63
|
+
self.synchronized = true
|
64
|
+
end
|
66
65
|
end
|
66
|
+
rescue StandardError => e
|
67
|
+
log "#{e.class.name} while synchronizing: #{e.message}"
|
68
|
+
@synchronized = false
|
69
|
+
raise e
|
67
70
|
ensure
|
68
|
-
@
|
71
|
+
@synchronizing = false
|
69
72
|
end
|
70
73
|
end
|
71
74
|
|
72
|
-
def idle?
|
73
|
-
unless enabled?
|
74
|
-
return true
|
75
|
-
end
|
76
|
-
|
77
|
-
result = execute_script(<<~JS)
|
78
|
-
if (window.CapybaraLockstep) {
|
79
|
-
return CapybaraLockstep.isIdle()
|
80
|
-
} else {
|
81
|
-
return 'Cannot check busy state: Capybara::Lockstep was not included in page'
|
82
|
-
}
|
83
|
-
JS
|
84
|
-
|
85
|
-
if result.is_a?(String)
|
86
|
-
log(result)
|
87
|
-
# When the snippet is missing we assume that the browser is idle.
|
88
|
-
# Otherwise we would wait forever.
|
89
|
-
true
|
90
|
-
else
|
91
|
-
result
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def busy?
|
96
|
-
!idle?
|
97
|
-
end
|
98
|
-
|
99
75
|
private
|
100
76
|
|
101
|
-
def browser_made_full_page_load?
|
102
|
-
# Page change without visit()
|
103
|
-
page.has_css?('body[data-hydrating]')
|
104
|
-
end
|
105
|
-
|
106
|
-
def initialize_reason
|
107
|
-
execute_script(<<~JS)
|
108
|
-
if (location.href.indexOf('data:') == 0) {
|
109
|
-
return 'Requesting initial page'
|
110
|
-
}
|
111
|
-
|
112
|
-
if (document.readyState !== "complete") {
|
113
|
-
return 'Document is loading'
|
114
|
-
}
|
115
|
-
|
116
|
-
// The application layouts render a <body data-initializing>.
|
117
|
-
// The [data-initializing] attribute is removed by an Angular directive or Unpoly compiler (frontend).
|
118
|
-
// to signal that all elements have been activated.
|
119
|
-
if (document.querySelector('body[data-initializing]')) {
|
120
|
-
return 'DOM is being hydrated'
|
121
|
-
}
|
122
|
-
|
123
|
-
if (window.CapybaraLockstep && CapybaraLockstep.isBusy()) {
|
124
|
-
return 'JavaScript or AJAX requests are running'
|
125
|
-
}
|
126
|
-
|
127
|
-
return false
|
128
|
-
JS
|
129
|
-
end
|
130
|
-
|
131
77
|
def page
|
132
78
|
Capybara.current_session
|
133
79
|
end
|
@@ -144,17 +90,10 @@ module Capybara
|
|
144
90
|
end
|
145
91
|
end
|
146
92
|
|
147
|
-
def
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
# If someone set Capybara::Lockstep to a logger, use that
|
152
|
-
@debug.debug(message)
|
153
|
-
else
|
154
|
-
# Otherwise print to STDOUT
|
155
|
-
puts message
|
156
|
-
end
|
157
|
-
end
|
93
|
+
def ignoring_alerts(&block)
|
94
|
+
block.call
|
95
|
+
rescue ::Selenium::WebDriver::Error::UnexpectedAlertOpenError
|
96
|
+
# no-op
|
158
97
|
end
|
159
98
|
|
160
99
|
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
|
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.
|
4
|
+
version: 0.3.0
|
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-
|
11
|
+
date: 2021-03-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: capybara
|
@@ -77,7 +77,7 @@ 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/
|
80
|
+
- lib/capybara-lockstep/logging.rb
|
81
81
|
- lib/capybara-lockstep/version.rb
|
82
82
|
homepage: https://github.com/makandra/capybara-lockstep
|
83
83
|
licenses:
|
@@ -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
|