capybara-lockstep 1.1.0 → 1.2.1
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/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:
|