capybara-lockstep 1.1.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +31 -24
- data/README.md +39 -21
- data/capybara-lockstep.gemspec +4 -0
- data/lib/capybara-lockstep/capybara_ext.rb +81 -31
- data/lib/capybara-lockstep/helper.js +10 -3
- data/lib/capybara-lockstep/lockstep.rb +33 -12
- data/lib/capybara-lockstep/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e9207a7671ab6f25999a138df922afb783ec3267257eadfab1b179f3c26eb88d
|
4
|
+
data.tar.gz: 2ddfe3a9a10143894afdc2eb2517ddeeaf76311e3365693606359282004a598d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dffa87e401d189b8afea6bb72b49bb14a6f9865d306fc7a838e50b4fb4dfc715fe8930502b9e3e2b4139c214b45eefe189c0ddb8e3e867d30d3021331ba76ea3
|
7
|
+
data.tar.gz: b311ee487b65ae42dc5dcc114cdd2aca2ff39ed49953fe43959ecf8578b3691740ebd2019aeae95fc87334cc5b87835f18b1f816ed2ad0a234e6475b549baad8
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,48 @@ All notable changes to this project will be documented in this file.
|
|
2
2
|
|
3
3
|
This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
4
4
|
|
5
|
+
|
6
|
+
## 1.2.1 - 2022-09-12
|
7
|
+
|
8
|
+
- Synchronize with pages constructed from non-empty [data URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs)
|
9
|
+
|
10
|
+
## 1.2.0 - 2022-09-12
|
11
|
+
|
12
|
+
### Synchronization around history navigation
|
13
|
+
|
14
|
+
We now synchronize before and after history navigation using the following Capybara methods:
|
15
|
+
|
16
|
+
- `page.refresh`
|
17
|
+
- `page.go_back`
|
18
|
+
- `page.go_forward`
|
19
|
+
|
20
|
+
We also synchronize before `current_url` in case running a JavaScript task wants to update the URL when done.
|
21
|
+
|
22
|
+
### Support for tests with multiple tabs or frames
|
23
|
+
|
24
|
+
capybara-lockstep now supports test that work with [multiple frames](https://makandracards.com/makandra/34015-use-capybara-commands-inside-an-iframe) or [multiple tabs or windows](https://github.com/teamcapybara/capybara#working-with-windows).
|
25
|
+
We now synchronize before and after the following Capybara methods:
|
26
|
+
|
27
|
+
- `switch_to_frame`
|
28
|
+
- `within_frame`
|
29
|
+
- `switch_to_window`
|
30
|
+
- `within_window`
|
31
|
+
|
32
|
+
### Improved logging
|
33
|
+
|
34
|
+
- Only log when we're actually synchronizing
|
35
|
+
- Log the reason why we're synchronizing (e.g. before node access)
|
36
|
+
- Log which browser work we're waiting for (e.g. XHR request, image load)
|
37
|
+
|
38
|
+
### Various changes
|
39
|
+
|
40
|
+
- Synchronize before accessing `page.html`.
|
41
|
+
|
42
|
+
|
43
|
+
## 1.1.1 - 2022-03-16
|
44
|
+
|
45
|
+
- Activate rubygems MFA
|
46
|
+
|
5
47
|
## 1.1.0
|
6
48
|
|
7
49
|
- Stop handling of `[data-initializing]` attribute. Apps that have late initialization after the `load` event can just use `CapybaraLockstep.startWork()`.
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
capybara-lockstep (1.1
|
4
|
+
capybara-lockstep (1.2.1)
|
5
5
|
activesupport (>= 3.2)
|
6
6
|
capybara (>= 2.0)
|
7
7
|
ruby2_keywords
|
@@ -10,32 +10,33 @@ PATH
|
|
10
10
|
GEM
|
11
11
|
remote: https://rubygems.org/
|
12
12
|
specs:
|
13
|
-
activesupport (6.1.
|
13
|
+
activesupport (6.1.7)
|
14
14
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
15
15
|
i18n (>= 1.6, < 2)
|
16
16
|
minitest (>= 5.1)
|
17
17
|
tzinfo (~> 2.0)
|
18
18
|
zeitwerk (~> 2.3)
|
19
|
-
addressable (2.
|
20
|
-
public_suffix (>= 2.0.2, <
|
19
|
+
addressable (2.8.1)
|
20
|
+
public_suffix (>= 2.0.2, < 6.0)
|
21
21
|
byebug (11.1.3)
|
22
|
-
capybara (3.
|
22
|
+
capybara (3.37.1)
|
23
23
|
addressable
|
24
|
+
matrix
|
24
25
|
mini_mime (>= 0.1.3)
|
25
26
|
nokogiri (~> 1.8)
|
26
27
|
rack (>= 1.6.0)
|
27
28
|
rack-test (>= 0.6.3)
|
28
29
|
regexp_parser (>= 1.5, < 3.0)
|
29
30
|
xpath (~> 3.2)
|
30
|
-
childprocess (
|
31
|
+
childprocess (4.1.0)
|
31
32
|
chrome_remote (0.3.0)
|
32
33
|
websocket-driver (~> 0.6)
|
33
|
-
concurrent-ruby (1.1.
|
34
|
+
concurrent-ruby (1.1.10)
|
34
35
|
daemons (1.3.1)
|
35
36
|
diff-lcs (1.3)
|
36
37
|
eventmachine (1.2.7)
|
37
38
|
gemika (0.6.0)
|
38
|
-
i18n (1.
|
39
|
+
i18n (1.12.0)
|
39
40
|
concurrent-ruby (~> 1.0)
|
40
41
|
jasmine (3.6.0)
|
41
42
|
jasmine-core (~> 3.6.0)
|
@@ -43,18 +44,20 @@ GEM
|
|
43
44
|
rack (>= 1.2.1)
|
44
45
|
rake
|
45
46
|
jasmine-core (3.6.0)
|
46
|
-
|
47
|
-
|
48
|
-
|
47
|
+
matrix (0.4.2)
|
48
|
+
mini_mime (1.1.2)
|
49
|
+
minitest (5.16.3)
|
50
|
+
nokogiri (1.13.8-x86_64-linux)
|
49
51
|
racc (~> 1.4)
|
50
52
|
phantomjs (2.1.1.0)
|
51
|
-
public_suffix (
|
52
|
-
racc (1.
|
53
|
+
public_suffix (5.0.0)
|
54
|
+
racc (1.6.0)
|
53
55
|
rack (2.2.3)
|
54
|
-
rack-test (
|
55
|
-
rack (>= 1.
|
56
|
+
rack-test (2.0.2)
|
57
|
+
rack (>= 1.3)
|
56
58
|
rake (13.0.1)
|
57
|
-
regexp_parser (2.
|
59
|
+
regexp_parser (2.5.0)
|
60
|
+
rexml (3.2.5)
|
58
61
|
rspec (3.7.0)
|
59
62
|
rspec-core (~> 3.7.0)
|
60
63
|
rspec-expectations (~> 3.7.0)
|
@@ -68,28 +71,32 @@ GEM
|
|
68
71
|
diff-lcs (>= 1.2.0, < 2.0)
|
69
72
|
rspec-support (~> 3.7.0)
|
70
73
|
rspec-support (3.7.0)
|
71
|
-
ruby2_keywords (0.0.
|
72
|
-
rubyzip (2.3.
|
73
|
-
selenium-webdriver (
|
74
|
-
childprocess (>= 0.5, <
|
75
|
-
|
74
|
+
ruby2_keywords (0.0.5)
|
75
|
+
rubyzip (2.3.2)
|
76
|
+
selenium-webdriver (4.4.0)
|
77
|
+
childprocess (>= 0.5, < 5.0)
|
78
|
+
rexml (~> 3.2, >= 3.2.5)
|
79
|
+
rubyzip (>= 1.2.2, < 3.0)
|
80
|
+
websocket (~> 1.0)
|
76
81
|
thin (1.8.0)
|
77
82
|
daemons (~> 1.0, >= 1.0.9)
|
78
83
|
eventmachine (~> 1.0, >= 1.0.4)
|
79
84
|
rack (>= 1, < 3)
|
80
|
-
tzinfo (2.0.
|
85
|
+
tzinfo (2.0.5)
|
81
86
|
concurrent-ruby (~> 1.0)
|
87
|
+
websocket (1.2.9)
|
82
88
|
websocket-driver (0.7.3)
|
83
89
|
websocket-extensions (>= 0.1.0)
|
84
90
|
websocket-extensions (0.1.5)
|
85
91
|
xpath (3.2.0)
|
86
92
|
nokogiri (~> 1.8)
|
87
|
-
zeitwerk (2.
|
93
|
+
zeitwerk (2.6.0)
|
88
94
|
|
89
95
|
PLATFORMS
|
90
96
|
ruby
|
91
97
|
|
92
98
|
DEPENDENCIES
|
99
|
+
activesupport (~> 6.0)
|
93
100
|
byebug
|
94
101
|
capybara-lockstep!
|
95
102
|
chrome_remote
|
@@ -100,4 +107,4 @@ DEPENDENCIES
|
|
100
107
|
thin
|
101
108
|
|
102
109
|
BUNDLED WITH
|
103
|
-
2.2.
|
110
|
+
2.2.32
|
data/README.md
CHANGED
@@ -58,11 +58,17 @@ How capybara-lockstep helps
|
|
58
58
|
|
59
59
|
capybara-lockstep waits until the browser is idle before moving on to the next Capybara command. This greatly relieves the pressure on [Capybara's retry logic](https://github.com/teamcapybara/capybara#asynchronous-javascript-ajax-and-friends).
|
60
60
|
|
61
|
-
|
61
|
+
capybara-lockstep synchronizes before:
|
62
62
|
|
63
|
-
-
|
64
|
-
-
|
65
|
-
-
|
63
|
+
- Capybara simulates a user interaction (clicking, typing, etc.)
|
64
|
+
- Capybara visits a new URL
|
65
|
+
- Capybara executes JavaScript
|
66
|
+
|
67
|
+
When capybara-lockstep synchronizes it will:
|
68
|
+
|
69
|
+
- wait for all document resources to load (images, CSS, fonts, frames).
|
70
|
+
- wait for client-side JavaScript to render or hydrate DOM elements.
|
71
|
+
- wait for any pending AJAX requests to finish and their callbacks to be called.
|
66
72
|
- capybara-lockstep waits for dynamically inserted `<script>`s to load (e.g. from [dynamic imports](https://webpack.js.org/guides/code-splitting/#dynamic-imports) or Analytics snippets).
|
67
73
|
- capybara-lockstep waits for dynamically inserted `<img>` or `<iframe>` elements to load.
|
68
74
|
|
@@ -111,7 +117,7 @@ capybara-lockstep requires a JavaScript snippet to be embedded by the applicatio
|
|
111
117
|
**If you're using Rails** you can use the `capybara_lockstep` helper to insert the snippet into your application layouts:
|
112
118
|
|
113
119
|
```erb
|
114
|
-
<%= capybara_lockstep if
|
120
|
+
<%= capybara_lockstep if defined?(Capybara::Lockstep) %>
|
115
121
|
```
|
116
122
|
|
117
123
|
Ideally the snippet should be included in the `<head>` before any other `<script>` tags.
|
@@ -146,22 +152,24 @@ Note that you may see some failures from tests with wrong assertions, which prev
|
|
146
152
|
|
147
153
|
## Signaling asynchronous work
|
148
154
|
|
149
|
-
By default capybara-lockstep
|
155
|
+
By default capybara-lockstep waits until resources have loaded, AJAX requests have finished and their callbacks have been called.
|
150
156
|
|
151
|
-
|
157
|
+
You can configure capybara-lockstep to wait for other async work that does not involve the network. Let's say we have an animation that fades in a new element over 2 seconds. The following will prevent Capybara from observing the page while the animation is running:
|
152
158
|
|
153
159
|
```js
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
160
|
+
async function fadeIn(element) {
|
161
|
+
CapybaraLockstep.startWork('Animation')
|
162
|
+
startAnimation(element, 'fade-in')
|
163
|
+
await waitForAnimationEnd(element)
|
164
|
+
CapybaraLockstep.stopWork('Animation')
|
165
|
+
}
|
158
166
|
```
|
159
167
|
|
160
168
|
The string argument is used for logging (when logging is enabled). It does **not** need to be unique per job. In this case you should see messages like this in your browser's JavaScript console:
|
161
169
|
|
162
170
|
```text
|
163
|
-
[capybara-lockstep] Started work:
|
164
|
-
[capybara-lockstep] Finished work:
|
171
|
+
[capybara-lockstep] Started work: Animation [1 jobs]
|
172
|
+
[capybara-lockstep] Finished work: Animation [0 jobs]
|
165
173
|
```
|
166
174
|
|
167
175
|
You may omit the string argument, in which case nothing will be logged, but the work will still be tracked.
|
@@ -177,6 +185,12 @@ if (window.CapybaraLockstep) {
|
|
177
185
|
}
|
178
186
|
```
|
179
187
|
|
188
|
+
If you can use ES6 you may also use [optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) to only call a function if `window.CapybaraLockstep` is defined:
|
189
|
+
|
190
|
+
```js
|
191
|
+
window.CapybaraLockstep?.startWork('Work')
|
192
|
+
```
|
193
|
+
|
180
194
|
|
181
195
|
## Performance impact
|
182
196
|
|
@@ -295,7 +309,7 @@ end
|
|
295
309
|
|
296
310
|
In the `:manual` mode you may still force synchronization by calling `Capybara::Lockstep.synchronize` manually.
|
297
311
|
|
298
|
-
To completely disable synchronization:
|
312
|
+
To completely disable synchronization, even when `Capybara::Lockstep.synchronize` is called:
|
299
313
|
|
300
314
|
```ruby
|
301
315
|
Capybara::Lockstep.mode = :off
|
@@ -306,25 +320,29 @@ Capybara::Lockstep.synchronize # will not synchronize
|
|
306
320
|
|
307
321
|
## Handling legacy promises
|
308
322
|
|
309
|
-
Legacy promise implementations (like jQuery's `$.Deferred` and AngularJS' `$q`) work using tasks instead of microtasks. Their AJAX implementations (like `$.ajax()` and `$http`) use
|
323
|
+
Legacy promise implementations (like jQuery's `$.Deferred` and AngularJS' `$q`) work using [tasks instead of microtasks](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/). Their AJAX implementations (like `$.ajax()` and `$http`) use task-based promises to signal that a request is done.
|
310
324
|
|
311
325
|
This means there is a time window in which all AJAX requests have finished, but their callbacks have not yet run:
|
312
326
|
|
313
327
|
```js
|
314
|
-
|
328
|
+
$http.get('/foo').then(function() {
|
315
329
|
// This callback runs one task after the response was received
|
316
330
|
})
|
317
331
|
```
|
318
332
|
|
319
|
-
It is theoretically possible that your test will observe the browser in that window, and expect content that has not been rendered yet.
|
333
|
+
It is theoretically possible that your test will observe the browser in that window, and expect content that has not been rendered yet. Affected code must call `then()` on a task-based promise **or** use `setTimeout()` to push work into the next task.
|
320
334
|
|
321
|
-
|
322
|
-
|
335
|
+
Any issues caused by this will usually be mitigated by Capybara's retry logic. **If** you think that this is an issue for your test suite, you can configure capybara-headless to wait additional tasks before it considers the browser to be idle:
|
336
|
+
|
337
|
+
```ruby
|
338
|
+
Capybara::Lockstep.wait_tasks = 1
|
323
339
|
```
|
324
340
|
|
325
|
-
If you see longer `then()`
|
341
|
+
If you see longer chains of `then()` or nested `setTimeout()` calls in your code, you may need to configure a higher number of tasks to wait.
|
342
|
+
|
343
|
+
Waiting additional tasks will have a negative performance impact on your test suite.
|
326
344
|
|
327
|
-
|
345
|
+
> **Note:** When capybara-lockstep detects jQuery on the page, it will automatically patch [`$.ajax()`](https://api.jquery.com/jQuery.ajax/) to wait an additional task after the response was received. If your only concern is callbacks to `$.ajax()` you do not need so set `Capybara::Lockstep.wait_tasks`.
|
328
346
|
|
329
347
|
|
330
348
|
## Contributing
|
data/capybara-lockstep.gemspec
CHANGED
@@ -14,6 +14,10 @@ Gem::Specification.new do |spec|
|
|
14
14
|
spec.metadata["homepage_uri"] = spec.homepage
|
15
15
|
spec.metadata["source_code_uri"] = spec.homepage
|
16
16
|
|
17
|
+
spec.metadata["bug_tracker_uri"] = "https://github.com/makandra/capybara-lockstep/issues"
|
18
|
+
spec.metadata["changelog_uri"] = "https://github.com/makandra/capybara-lockstep/blob/master/CHANGELOG.md"
|
19
|
+
spec.metadata["rubygems_mfa_required"] = 'true'
|
20
|
+
|
17
21
|
# Specify which files should be added to the gem when it is released.
|
18
22
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
19
23
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
@@ -1,34 +1,99 @@
|
|
1
1
|
require 'ruby2_keywords'
|
2
2
|
|
3
|
+
module Capybara
|
4
|
+
module Lockstep
|
5
|
+
module UnsychronizeAfter
|
6
|
+
def unsychronize_after(meth)
|
7
|
+
mod = Module.new do
|
8
|
+
define_method meth do |*args, &block|
|
9
|
+
super(*args, &block)
|
10
|
+
ensure
|
11
|
+
Lockstep.synchronized = false
|
12
|
+
end
|
13
|
+
|
14
|
+
ruby2_keywords meth
|
15
|
+
end
|
16
|
+
|
17
|
+
prepend(mod)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module Capybara
|
24
|
+
module Lockstep
|
25
|
+
module SynchronizeBefore
|
26
|
+
def synchronize_before(meth, lazy:)
|
27
|
+
mod = Module.new do
|
28
|
+
define_method meth do |*args, &block|
|
29
|
+
Lockstep.auto_synchronize(lazy: lazy, log: "Synchronizing before ##{meth}")
|
30
|
+
super(*args, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
ruby2_keywords meth
|
34
|
+
end
|
35
|
+
|
36
|
+
prepend(mod)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
Capybara::Session.class_eval do
|
43
|
+
extend Capybara::Lockstep::SynchronizeBefore
|
44
|
+
extend Capybara::Lockstep::UnsychronizeAfter
|
45
|
+
|
46
|
+
synchronize_before :html, lazy: true # wait until running JavaScript has updated the DOM
|
47
|
+
|
48
|
+
synchronize_before :current_url, lazy: true # wait until running JavaScript has updated the URL
|
49
|
+
|
50
|
+
synchronize_before :refresh, lazy: false # wait until running JavaScript has updated the URL
|
51
|
+
unsychronize_after :refresh # new document is no longer synchronized
|
52
|
+
|
53
|
+
synchronize_before :go_back, lazy: false # wait until running JavaScript has updated the URL
|
54
|
+
unsychronize_after :go_back # new document is no longer synchronized
|
55
|
+
|
56
|
+
synchronize_before :go_forward, lazy: false # wait until running JavaScript has updated the URL
|
57
|
+
unsychronize_after :go_forward # new document is no longer synchronized
|
58
|
+
|
59
|
+
synchronize_before :switch_to_frame, lazy: true # wait until the current frame is done processing
|
60
|
+
unsychronize_after :switch_to_frame # now that we've switched into the new frame, we don't know the document's synchronization state.
|
61
|
+
|
62
|
+
synchronize_before :switch_to_window, lazy: true # wait until the current frame is done processing
|
63
|
+
unsychronize_after :switch_to_window # now that we've switched to the new window, we don't know the document's synchronization state.
|
64
|
+
end
|
65
|
+
|
3
66
|
module Capybara
|
4
67
|
module Lockstep
|
5
68
|
module VisitWithWaiting
|
6
|
-
|
69
|
+
def visit(*args, &block)
|
7
70
|
url = args[0]
|
8
71
|
# Some of our apps have a Cucumber step that changes drivers mid-scenario.
|
9
72
|
# It works by creating a new Capybara session and re-visits the URL from the
|
10
73
|
# previous session. If this happens before a URL is ever loaded,
|
11
74
|
# it re-visits the URL "data:", which will never "finish" initializing.
|
12
75
|
# Also when opening a new tab via Capybara, the initial URL is about:blank.
|
13
|
-
|
76
|
+
visiting_real_url = !(url.start_with?('data:') || url.start_with?('about:'))
|
14
77
|
|
15
|
-
if
|
78
|
+
if visiting_real_url
|
16
79
|
# We're about to leave this screen, killing all in-flight requests.
|
17
80
|
# Give pending form submissions etc. a chance to finish before we tear down
|
18
81
|
# the browser environment.
|
19
82
|
#
|
20
83
|
# We force a non-lazy synchronization so we pick up all client-side changes
|
21
84
|
# that have not been caused by Capybara commands.
|
22
|
-
Lockstep.
|
85
|
+
Lockstep.auto_synchronize(lazy: false, log: "Synchronizing before visiting #{url}")
|
23
86
|
end
|
24
87
|
|
25
88
|
super(*args, &block).tap do
|
26
|
-
if
|
89
|
+
if visiting_real_url
|
27
90
|
# We haven't yet synchronized the new screen.
|
28
91
|
Lockstep.synchronized = false
|
29
92
|
end
|
30
93
|
end
|
31
94
|
end
|
95
|
+
|
96
|
+
ruby2_keywords :visit
|
32
97
|
end
|
33
98
|
end
|
34
99
|
end
|
@@ -58,14 +123,15 @@ module Capybara
|
|
58
123
|
Lockstep.auto_synchronize(lazy: !script_may_navigate_away, log: "Synchronizing before script: #{script}")
|
59
124
|
end
|
60
125
|
|
61
|
-
super(script, *args, &block)
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
126
|
+
super(script, *args, &block)
|
127
|
+
ensure
|
128
|
+
if !Lockstep.synchronizing?
|
129
|
+
# We haven't yet synchronized with whatever changes the JavaScript
|
130
|
+
# did on the frontend.
|
131
|
+
Lockstep.synchronized = false
|
67
132
|
end
|
68
133
|
end
|
134
|
+
|
69
135
|
ruby2_keywords meth
|
70
136
|
end
|
71
137
|
prepend(mod)
|
@@ -84,24 +150,6 @@ Capybara::Session.class_eval do
|
|
84
150
|
# internally and we don't want to synchronize multiple times.
|
85
151
|
end
|
86
152
|
|
87
|
-
module Capybara
|
88
|
-
module Lockstep
|
89
|
-
module UnsychronizeAfter
|
90
|
-
def unsychronize_after(meth)
|
91
|
-
mod = Module.new do
|
92
|
-
define_method meth do |*args, &block|
|
93
|
-
super(*args, &block).tap do
|
94
|
-
Lockstep.synchronized = false
|
95
|
-
end
|
96
|
-
end
|
97
|
-
ruby2_keywords meth
|
98
|
-
end
|
99
|
-
prepend(mod)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
153
|
# Capybara 3 has driver-specific Node classes which sometimes
|
106
154
|
# super to Capybara::Selenium::Node, but not always.
|
107
155
|
node_classes = [
|
@@ -141,14 +189,16 @@ end
|
|
141
189
|
module Capybara
|
142
190
|
module Lockstep
|
143
191
|
module SynchronizeWithCatchUp
|
144
|
-
|
192
|
+
def synchronize(*args, &block)
|
145
193
|
# This method is called by Capybara before most interactions with
|
146
194
|
# the browser. It is a different method than Capybara::Lockstep.synchronize!
|
147
195
|
# We use the { lazy } option to only synchronize when we're out of sync.
|
148
|
-
|
196
|
+
Lockstep.auto_synchronize(lazy: true, log: 'Synchronizing before node access')
|
149
197
|
|
150
198
|
super(*args, &block)
|
151
199
|
end
|
200
|
+
|
201
|
+
ruby2_keywords :synchronize
|
152
202
|
end
|
153
203
|
end
|
154
204
|
end
|
@@ -3,12 +3,14 @@ window.CapybaraLockstep = (function() {
|
|
3
3
|
let debug
|
4
4
|
let jobCount
|
5
5
|
let idleCallbacks
|
6
|
+
let finishedWorkTags
|
6
7
|
let waitTasks
|
7
8
|
reset()
|
8
9
|
|
9
10
|
function reset() {
|
10
11
|
jobCount = 0
|
11
12
|
idleCallbacks = []
|
13
|
+
finishedWorkTags = []
|
12
14
|
waitTasks = 0
|
13
15
|
debug = false
|
14
16
|
}
|
@@ -74,12 +76,17 @@ window.CapybaraLockstep = (function() {
|
|
74
76
|
jobCount--
|
75
77
|
|
76
78
|
if (tag) {
|
79
|
+
finishedWorkTags.push(tag)
|
77
80
|
logPositive('Finished work: %s [%d jobs]', tag, jobCount)
|
78
81
|
}
|
79
82
|
|
80
|
-
|
81
|
-
|
82
|
-
idleCallback
|
83
|
+
if (isIdle()) {
|
84
|
+
let idleCallback
|
85
|
+
while ((idleCallback = idleCallbacks.shift())) {
|
86
|
+
idleCallback("Finished waiting for " + finishedWorkTags.join(', '))
|
87
|
+
}
|
88
|
+
|
89
|
+
finishedWorkTags = []
|
83
90
|
}
|
84
91
|
}
|
85
92
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Capybara
|
2
2
|
module Lockstep
|
3
3
|
ERROR_SNIPPET_MISSING = 'Cannot synchronize: capybara-lockstep JavaScript snippet is missing'
|
4
|
-
ERROR_PAGE_MISSING = 'Cannot synchronize
|
4
|
+
ERROR_PAGE_MISSING = 'Cannot synchronize with empty page'
|
5
5
|
ERROR_ALERT_OPEN = 'Cannot synchronize while an alert is open'
|
6
6
|
ERROR_NAVIGATED_AWAY = "Browser navigated away while synchronizing"
|
7
7
|
|
@@ -13,7 +13,17 @@ module Capybara
|
|
13
13
|
alias synchronizing? synchronizing
|
14
14
|
|
15
15
|
def synchronized?
|
16
|
+
# The synchronized flag is per-session (page == Capybara.current_session).
|
17
|
+
# This enables tests that use more than one browser, e.g. to test multi-user interaction:
|
18
|
+
# https://makandracards.com/makandra/474480-how-to-make-a-cucumber-test-work-with-multiple-browser-sessions
|
19
|
+
#
|
20
|
+
# Ideally the synchronized flag would also be per-tab, per-frame and per-document.
|
21
|
+
# We haven't found a way to patch this into Capybara, as there does not seem to be
|
22
|
+
# a persistent object representing a document. Capybara::Node::Document just seems to
|
23
|
+
# be a proxy accessing whatever is the current document. The way we work around this
|
24
|
+
# is that we synchronize before switching tabs or frames.
|
16
25
|
value = page.instance_variable_get(:@lockstep_synchronized)
|
26
|
+
|
17
27
|
# We consider a new Capybara session to be synchronized.
|
18
28
|
# This will be set to false after our first visit().
|
19
29
|
value.nil? ? true : value
|
@@ -24,19 +34,30 @@ module Capybara
|
|
24
34
|
end
|
25
35
|
|
26
36
|
def synchronize(lazy: false, log: nil)
|
37
|
+
# The { lazy } option is a performance optimization that will prevent capybara-lockstep
|
38
|
+
# from synchronizing multiple times in expressions like `page.find('.foo').find('.bar')`.
|
39
|
+
# The { lazy } option has nothing todo with :auto mode.
|
40
|
+
#
|
41
|
+
# With { lazy: true } we only synchronize when the Ruby-side thinks we're out of sync.
|
42
|
+
# This saves us an expensive execute_script() roundtrip that goes to the browser and back.
|
43
|
+
# However the knowledge of the Ruby-side is limited: We only assume that we're out of sync
|
44
|
+
# after a page load or after a Capybara command. There may be additional client-side work
|
45
|
+
# that the Ruby-side is not aware of, e.g. an AJAX call scheduled by a timeout.
|
46
|
+
#
|
47
|
+
# With { lazy: false } we force synchronization with the browser, whether the Ruby-side
|
48
|
+
# thinks we're in sync or not. This always makes an execute_script() rountrip, but picks up
|
49
|
+
# non-lazy synchronization so we pick up client-side work that have not been caused
|
50
|
+
# by Capybara commands.
|
27
51
|
if (lazy && synchronized?) || synchronizing? || mode == :off
|
28
52
|
return
|
29
53
|
end
|
30
54
|
|
31
|
-
|
32
|
-
# when we're actually synchronizing.
|
33
|
-
if log
|
34
|
-
self.log(log)
|
35
|
-
end
|
36
|
-
|
37
|
-
synchronize_now
|
55
|
+
synchronize_now(log: log)
|
38
56
|
end
|
39
57
|
|
58
|
+
# Automatic synchronization from within the capybara-lockstep should always call #auto_synchronize.
|
59
|
+
# This only synchronizes IFF in :auto mode, i.e. the user has not explicitly disabled automatic syncing.
|
60
|
+
# The :auto mode has nothing to do with the { lazy } option.
|
40
61
|
def auto_synchronize(**options)
|
41
62
|
if mode == :auto
|
42
63
|
synchronize(**options)
|
@@ -45,11 +66,11 @@ module Capybara
|
|
45
66
|
|
46
67
|
private
|
47
68
|
|
48
|
-
def synchronize_now
|
69
|
+
def synchronize_now(log: 'Synchronizing')
|
49
70
|
self.synchronizing = true
|
50
71
|
self.synchronized = false
|
51
72
|
|
52
|
-
log
|
73
|
+
self.log(log)
|
53
74
|
|
54
75
|
start_time = current_seconds
|
55
76
|
|
@@ -64,8 +85,8 @@ module Capybara
|
|
64
85
|
done(#{ERROR_SNIPPET_MISSING.to_json})
|
65
86
|
}
|
66
87
|
}
|
67
|
-
|
68
|
-
if (
|
88
|
+
const emptyDataURL = /^data:[^,]*,?$/
|
89
|
+
if (emptyDataURL.test(location.href) || location.protocol === 'about:') {
|
69
90
|
done(#{ERROR_PAGE_MISSING.to_json})
|
70
91
|
} else if (document.readyState === 'complete') {
|
71
92
|
// WebDriver always waits for the `load` event after a visit(),
|
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: 1.1
|
4
|
+
version: 1.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Henning Koch
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-09-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: capybara
|
@@ -101,6 +101,9 @@ licenses:
|
|
101
101
|
metadata:
|
102
102
|
homepage_uri: https://github.com/makandra/capybara-lockstep
|
103
103
|
source_code_uri: https://github.com/makandra/capybara-lockstep
|
104
|
+
bug_tracker_uri: https://github.com/makandra/capybara-lockstep/issues
|
105
|
+
changelog_uri: https://github.com/makandra/capybara-lockstep/blob/master/CHANGELOG.md
|
106
|
+
rubygems_mfa_required: 'true'
|
104
107
|
post_install_message:
|
105
108
|
rdoc_options: []
|
106
109
|
require_paths:
|