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 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: