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 +4 -4
- data/Gemfile.lock +16 -13
- data/README.md +38 -39
- data/capybara-lockstep.gemspec +1 -1
- data/lib/capybara-lockstep.rb +1 -1
- data/lib/capybara-lockstep/capybara_ext.rb +62 -30
- data/lib/capybara-lockstep/configuration.rb +4 -0
- data/lib/capybara-lockstep/helper.js +29 -6
- data/lib/capybara-lockstep/lockstep.rb +66 -98
- data/lib/capybara-lockstep/logging.rb +18 -0
- data/lib/capybara-lockstep/version.rb +1 -1
- metadata +7 -7
- 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: d0d733ed210e1ba465d9d612fcf461618ee91c88a71b09f9c0020271e4ac28ef
|
4
|
+
data.tar.gz: 5f30713c033ebd57b30d37f27e545c2a4547f6d40cd013c15f4ebba50a1f3893
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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 (
|
12
|
+
activesupport (6.1.3)
|
13
13
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
14
|
-
i18n (>=
|
15
|
-
minitest (
|
16
|
-
tzinfo (~>
|
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.
|
29
|
+
concurrent-ruby (1.1.8)
|
29
30
|
diff-lcs (1.3)
|
30
|
-
i18n (1.8.
|
31
|
+
i18n (1.8.9)
|
31
32
|
concurrent-ruby (~> 1.0)
|
32
33
|
mini_mime (1.0.2)
|
33
|
-
|
34
|
-
|
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 (
|
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
|
-
|
61
|
-
|
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-
|
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
|
-
|
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
|
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]
|
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
|
-
|
274
|
-
CapybaraLockstep.awaitIdle(callback)
|
275
|
-
```
|
298
|
+
This will run the given callback once the browser is considered to be idle:
|
276
299
|
|
277
|
-
|
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/
|
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).
|
data/capybara-lockstep.gemspec
CHANGED
@@ -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://
|
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
|
|
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'
|
@@ -1,26 +1,44 @@
|
|
1
1
|
module Capybara
|
2
2
|
module Lockstep
|
3
3
|
module VisitWithWaiting
|
4
|
-
def visit(*args,
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
19
|
-
|
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.
|
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::
|
69
|
+
extend Capybara::Lockstep::UnsychronizeAfter
|
56
70
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
@@ -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,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
|
-
|
8
|
-
return unless enabled?
|
7
|
+
attr_accessor :synchronized
|
9
8
|
|
10
|
-
|
11
|
-
|
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
|
-
|
16
|
-
CapybaraLockstep
|
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
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
125
|
-
|
126
|
-
|
127
|
-
|
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
|
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
|
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-
|
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/
|
80
|
+
- lib/capybara-lockstep/logging.rb
|
81
81
|
- lib/capybara-lockstep/version.rb
|
82
|
-
homepage: https://
|
82
|
+
homepage: https://github.com/makandra/capybara-lockstep
|
83
83
|
licenses:
|
84
84
|
- MIT
|
85
85
|
metadata:
|
86
|
-
homepage_uri: https://
|
87
|
-
source_code_uri: https://
|
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.
|
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
|