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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ece5e0daeb6883b02791ed06f6ff2afca446e5aa5635f22d3ec5b94d0e96aa7
4
- data.tar.gz: 5e4d53a8c59f71071aa48fee8ec87fa62e5c189e026b5cac563fd9427a082c59
3
+ metadata.gz: e9207a7671ab6f25999a138df922afb783ec3267257eadfab1b179f3c26eb88d
4
+ data.tar.gz: 2ddfe3a9a10143894afdc2eb2517ddeeaf76311e3365693606359282004a598d
5
5
  SHA512:
6
- metadata.gz: 7e68bf91ab8b5ecf91b145159697e7990dbbbb3a5497955d50d91ea83d802c50195f803ae1fd029b2b76dc18b407bd1995b03aa292e30873cd35f8746dfa509e
7
- data.tar.gz: 8fb8d9b19fc47b5769208d36125b6cc2fb40375a8d5b46216ec318a3fd8459ca26f6bf22f352efcf350b7d7cbfe89f7bb866633fb4e096812b7d31513d4c7bae
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
@@ -14,3 +14,5 @@ gem 'chrome_remote'
14
14
 
15
15
  gem 'byebug'
16
16
  gem 'gemika'
17
+
18
+ gem 'activesupport', '~> 6.0'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- capybara-lockstep (1.1.0)
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.3.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.7.0)
20
- public_suffix (>= 2.0.2, < 5.0)
19
+ addressable (2.8.1)
20
+ public_suffix (>= 2.0.2, < 6.0)
21
21
  byebug (11.1.3)
22
- capybara (3.35.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 (3.0.0)
31
+ childprocess (4.1.0)
31
32
  chrome_remote (0.3.0)
32
33
  websocket-driver (~> 0.6)
33
- concurrent-ruby (1.1.8)
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.8.10)
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
- mini_mime (1.1.0)
47
- minitest (5.14.4)
48
- nokogiri (1.11.3-x86_64-linux)
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 (4.0.6)
52
- racc (1.5.2)
53
+ public_suffix (5.0.0)
54
+ racc (1.6.0)
53
55
  rack (2.2.3)
54
- rack-test (1.1.0)
55
- rack (>= 1.0, < 3)
56
+ rack-test (2.0.2)
57
+ rack (>= 1.3)
56
58
  rake (13.0.1)
57
- regexp_parser (2.1.1)
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.4)
72
- rubyzip (2.3.0)
73
- selenium-webdriver (3.142.7)
74
- childprocess (>= 0.5, < 4.0)
75
- rubyzip (>= 1.2.2)
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.4)
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.4.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.15
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
- Before Capybara simulates a user interaction (clicking, typing, etc.) or before it visits a new URL:
61
+ capybara-lockstep synchronizes before:
62
62
 
63
- - capybara-lockstep waits for all document resources to load (images, CSS, fonts, frames).
64
- - capybara-lockstep waits for any AJAX requests to finish.
65
- - capybara-lockstep waits for client-side JavaScript to render or hydrate DOM elements.
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 Rails.env.test? %>
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 blocks all async work that
155
+ By default capybara-lockstep waits until resources have loaded, AJAX requests have finished and their callbacks have been called.
150
156
 
151
- If for some reason you want capybara-lockstep to consider additional asynchronous work as "busy", you can do so:
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
- CapybaraLockstep.startWork('Eject warp core')
155
- doAsynchronousWork().then(function() {
156
- CapybaraLockstep.stopWork('Eject warp core')
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: Eject warp core [1 jobs]
164
- [capybara-lockstep] Finished work: Eject warp core [0 jobs]
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 these promises to signal that a request is done.
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
- $.ajax('/foo').then(function() {
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. 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:
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
- ```js
322
- Capybara:Lockstep.wait_tasks = 1
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()` chains in your code, you may need to configure a higher number of tasks to wait.
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
- This will have a negative performance impact on your test suite.
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
@@ -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
- ruby2_keywords def visit(*args, &block)
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
- visiting_remote_url = !(url.start_with?('data:') || url.start_with?('about:'))
76
+ visiting_real_url = !(url.start_with?('data:') || url.start_with?('about:'))
14
77
 
15
- if visiting_remote_url
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.synchronize(lazy: false)
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 visiting_remote_url
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).tap do
62
- if !Lockstep.synchronizing?
63
- # We haven't yet synchronized with whatever changes the JavaScript
64
- # did on the frontend.
65
- Lockstep.synchronized = false
66
- end
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
- ruby2_keywords def synchronize(*args, &block)
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
- Capybara::Lockstep.auto_synchronize(lazy: true)
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
- let idleCallback
81
- while (isIdle() && (idleCallback = idleCallbacks.shift())) {
82
- idleCallback('Finished waiting for browser')
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 before initial Capybara visit'
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
- # Allow passing a log message that is only logged
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 'Synchronizing'
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
- let protocol = location.protocol
68
- if (protocol === 'data:' || protocol == 'about:') {
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(),
@@ -1,5 +1,5 @@
1
1
  module Capybara
2
2
  module Lockstep
3
- VERSION = "1.1.0"
3
+ VERSION = "1.2.1"
4
4
  end
5
5
  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: 1.1.0
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: 2021-07-08 00:00:00.000000000 Z
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: