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