capybara-lockstep 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -4
- data/README.md +66 -17
- data/lib/capybara-lockstep/configuration.rb +36 -16
- data/lib/capybara-lockstep/helper.js +116 -38
- data/lib/capybara-lockstep/helper.rb +12 -3
- data/lib/capybara-lockstep/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 29e90825f3d0526be3d7780f7a291eddb9094b271f53ea5dcd4331a4123cda01
|
4
|
+
data.tar.gz: 1985baa9704e28b4a0b8a38c53af53b133adc68db14b5ec1fc613cd2adc0f21c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 761be246131d0fce67dd2fb4d3ac8c7bddb0775e477ebe02b7c36139dc7e6152c0dea579cd02343b2e7f5ba3dd85ce2127fc21b323e1216584e3ebd16867a145
|
7
|
+
data.tar.gz: c0c4ad6b8bb83bf6493276bc2388446a3f6ff07b6ae8c924189851e77e9f767a551ce85ba9f04c6ebe285c0904fe3eb44ed1761f704e0ebaee1a7a638c49e26e
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
capybara-lockstep (0.
|
4
|
+
capybara-lockstep (0.5.0)
|
5
5
|
activesupport (>= 3.2)
|
6
6
|
capybara (>= 2.0)
|
7
7
|
selenium-webdriver (>= 3)
|
@@ -32,10 +32,8 @@ GEM
|
|
32
32
|
i18n (1.8.9)
|
33
33
|
concurrent-ruby (~> 1.0)
|
34
34
|
mini_mime (1.0.2)
|
35
|
-
mini_portile2 (2.5.0)
|
36
35
|
minitest (5.14.4)
|
37
|
-
nokogiri (1.11.1)
|
38
|
-
mini_portile2 (~> 2.5.0)
|
36
|
+
nokogiri (1.11.1-x86_64-linux)
|
39
37
|
racc (~> 1.4)
|
40
38
|
public_suffix (4.0.6)
|
41
39
|
racc (1.5.2)
|
data/README.md
CHANGED
@@ -38,17 +38,13 @@ How capybara-lockstep helps
|
|
38
38
|
|
39
39
|
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.
|
40
40
|
|
41
|
-
Whenever Capybara visits a new URL:
|
41
|
+
Whenever Capybara visits a new URL or simulates a user interaction (clicking, typing, etc.):
|
42
42
|
|
43
43
|
- capybara-lockstep waits for all document resources to load.
|
44
44
|
- capybara-lockstep waits for client-side JavaScript to render or hydrate DOM elements.
|
45
45
|
- capybara-lockstep waits for any AJAX requests.
|
46
46
|
- 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).
|
47
|
-
|
48
|
-
Whenever Capybara simulates a user interaction (clicking, typing, etc.):
|
49
|
-
|
50
|
-
- capybara-lockstep waits for any AJAX requests.
|
51
|
-
- 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).
|
47
|
+
- capybara-lockstep waits for dynamically `<img>` or `<iframe>` elements to load.
|
52
48
|
|
53
49
|
|
54
50
|
Installation
|
@@ -102,9 +98,9 @@ If you're not using Rails you can `include Capybara::Lockstep::Helper` and acces
|
|
102
98
|
|
103
99
|
### Signaling the end of page initialization
|
104
100
|
|
105
|
-
Most web applications run some JavaScript after
|
101
|
+
Most web applications run some JavaScript after a document has initially loaded. Such JavaScript usually enhances existing DOM elements ("hydration") or renders additional element into the DOM.
|
106
102
|
|
107
|
-
capybara-lockstep
|
103
|
+
capybara-lockstep will synchronize more reliably if you signal when your JavaScript is done rendering the initial document. After the initial rendering, capybara-lockstep will automatically detect when the browser is busy, even if content is changed dynamically later.
|
108
104
|
|
109
105
|
To signal that JavaScript is still initializing, your application layouts should render the `<body>` element with an `[data-initializing]` attribute:
|
110
106
|
|
@@ -112,10 +108,14 @@ To signal that JavaScript is still initializing, your application layouts should
|
|
112
108
|
<body data-initializing>
|
113
109
|
```
|
114
110
|
|
115
|
-
Your application JavaScript should remove the `[data-initializing]` attribute when it is done
|
111
|
+
Your application JavaScript should remove the `[data-initializing]` attribute when it is done rendering the initial page.
|
116
112
|
|
117
113
|
More precisely, the attribute should be removed in the same [JavaScript task](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/) ("tick") that will finish initializing. capybara-lockstep will assume that the page will be initialized by the end of this task.
|
118
114
|
|
115
|
+
**After the initial rendering, capybara-lockstep will automatically detect when the browser is busy, even if content is changed dynamically later. After the initial page load you no longer need to add or remove the `[data-initializing]` attribute.**
|
116
|
+
|
117
|
+
#### Example: Vanilla JS
|
118
|
+
|
119
119
|
If all your initializing JavaScript runs synchronously on `DOMContentLoaded`, you can remove `[data-initializing]` in an event handler:
|
120
120
|
|
121
121
|
```js
|
@@ -125,26 +125,42 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
125
125
|
})
|
126
126
|
```
|
127
127
|
|
128
|
-
If you
|
128
|
+
If you call libraries during initialization, you may need to check the library code to see whether it finishes synchronously or asynchronously. Ideally a library offers a callback to notify you when it is done rendering:
|
129
129
|
|
130
130
|
```js
|
131
131
|
document.addEventListener('DOMContentLoaded', function() {
|
132
|
-
|
133
|
-
|
134
|
-
|
132
|
+
Libary.initialize({
|
133
|
+
onFinished: function() {
|
134
|
+
document.body.removeAttribute('data-initializing')
|
135
|
+
})
|
135
136
|
})
|
137
|
+
setTimeout(function() { document.body.removeAttribute('data-initializing') })
|
136
138
|
})
|
137
139
|
```
|
138
140
|
|
139
|
-
|
141
|
+
When a library offers no such callback, but you see in its code that the library delays work for a task, you must also wait another task to remove `[data-initializing]`:
|
140
142
|
|
141
143
|
```js
|
142
144
|
document.addEventListener('DOMContentLoaded', function() {
|
143
|
-
Libary.
|
145
|
+
Libary.initialize()
|
144
146
|
setTimeout(function() { document.body.removeAttribute('data-initializing') })
|
145
147
|
})
|
146
148
|
```
|
147
149
|
|
150
|
+
If your initialization code lazy-loads another script, you should only remove `[data-initializing]` once that is done:
|
151
|
+
|
152
|
+
```js
|
153
|
+
document.addEventListener('DOMContentLoaded', function() {
|
154
|
+
import('huge-library').then(function({ HugeLibrary }) {
|
155
|
+
HugeLibrary.initialize()
|
156
|
+
document.body.removeAttribute('data-initializing')
|
157
|
+
})
|
158
|
+
})
|
159
|
+
```
|
160
|
+
|
161
|
+
|
162
|
+
#### Example: Unpoly
|
163
|
+
|
148
164
|
When you're using [Unpoly](https://unpoly.com/) initializing will usually happen synchronously in [compilers](https://unpoly.com/up.compiler). Hence a compiler is a good place to remove `[data-initializing]`:
|
149
165
|
|
150
166
|
```js
|
@@ -153,6 +169,8 @@ up.compiler('body', function(body) {
|
|
153
169
|
})
|
154
170
|
```
|
155
171
|
|
172
|
+
#### Example: AngularJS 1
|
173
|
+
|
156
174
|
When you're using [AngularJS 1](https://unpoly.com/) initializing will usually happen synchronously in [directives](https://docs.angularjs.org/guide/directive). Hence a directive is a good place to remove `[data-initializing]`:
|
157
175
|
|
158
176
|
```js
|
@@ -224,6 +242,14 @@ You may also configure logging to an existing logger object:
|
|
224
242
|
Capybara::Lockstep.debug = Rails.logger
|
225
243
|
```
|
226
244
|
|
245
|
+
### Logging in the browser only
|
246
|
+
|
247
|
+
To enable logging in the browser console (but not STDOUT), include the snippet with `{ debug: true }`:
|
248
|
+
|
249
|
+
```
|
250
|
+
capybara_lockstep(debug: true)
|
251
|
+
```
|
252
|
+
|
227
253
|
|
228
254
|
## Disabling synchronization
|
229
255
|
|
@@ -286,7 +312,7 @@ doAsynchronousWork().then(function() {
|
|
286
312
|
})
|
287
313
|
```
|
288
314
|
|
289
|
-
The string argument is used for logging (when logging is enabled). In this case you should see messages like this in your browser's JavaScript console:
|
315
|
+
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:
|
290
316
|
|
291
317
|
```text
|
292
318
|
[capybara-lockstep] Started work: Eject warp core [1 jobs]
|
@@ -300,12 +326,35 @@ You may omit the string argument, in which case nothing will be logged, but the
|
|
300
326
|
|
301
327
|
If you only load capybara-lockstep in tests you, should check for the `CapybaraLockstep` global to be defined before you interact with the JavaScript API.
|
302
328
|
|
303
|
-
```
|
329
|
+
```js
|
304
330
|
if (window.CapybaraLockstep) {
|
305
331
|
// interact with CapybaraLockstep
|
306
332
|
}
|
307
333
|
```
|
308
334
|
|
335
|
+
## Handling legacy promises
|
336
|
+
|
337
|
+
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.
|
338
|
+
|
339
|
+
This means there is a time window in which all AJAX requests have finished, but their callbacks have not yet run:
|
340
|
+
|
341
|
+
```js
|
342
|
+
$.ajax('/foo').then(function() {
|
343
|
+
// This callback runs one task after the response was received
|
344
|
+
})
|
345
|
+
```
|
346
|
+
|
347
|
+
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:
|
348
|
+
|
349
|
+
```js
|
350
|
+
Capybara:Lockstep.wait_tasks = 1
|
351
|
+
```
|
352
|
+
|
353
|
+
If you see longer `then()` chains in your code, you may need to configure a higher number of tasks to wait.
|
354
|
+
|
355
|
+
This will have a negative performance impact on your test suite.
|
356
|
+
|
357
|
+
|
309
358
|
## Development
|
310
359
|
|
311
360
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -15,25 +15,16 @@ module Capybara
|
|
15
15
|
@debug.nil? ? false : !!@debug
|
16
16
|
end
|
17
17
|
|
18
|
-
def debug=(
|
19
|
-
@debug =
|
20
|
-
if
|
21
|
-
target_prose = (is_logger?(
|
18
|
+
def debug=(value)
|
19
|
+
@debug = value
|
20
|
+
if value
|
21
|
+
target_prose = (is_logger?(value) ? 'Ruby logger' : 'STDOUT')
|
22
22
|
log "Logging to #{target_prose} and browser console"
|
23
23
|
end
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
if (window.CapybaraLockstep) {
|
29
|
-
CapybaraLockstep.setDebug(#{debug.to_json})
|
30
|
-
}
|
31
|
-
JS
|
32
|
-
end
|
33
|
-
rescue StandardError => e
|
34
|
-
log "#{e.class.name} while enabling logs in browser: #{e.message}"
|
35
|
-
# Don't fail. The next page load will include the snippet with debugging enabled.
|
36
|
-
end
|
25
|
+
send_config_to_browser(<<~JS)
|
26
|
+
CapybaraLockstep.debug = #{value.to_json}
|
27
|
+
JS
|
37
28
|
|
38
29
|
@debug
|
39
30
|
end
|
@@ -50,6 +41,20 @@ module Capybara
|
|
50
41
|
@enabled = enabled
|
51
42
|
end
|
52
43
|
|
44
|
+
def wait_tasks
|
45
|
+
@wait_tasks
|
46
|
+
end
|
47
|
+
|
48
|
+
def wait_tasks=(value)
|
49
|
+
@wait_tasks = value
|
50
|
+
|
51
|
+
send_config_to_browser(<<~JS)
|
52
|
+
CapybaraLockstep.waitTasks = #{value.to_json}
|
53
|
+
JS
|
54
|
+
|
55
|
+
@wait_tasks
|
56
|
+
end
|
57
|
+
|
53
58
|
def disabled?
|
54
59
|
!enabled?
|
55
60
|
end
|
@@ -60,6 +65,21 @@ module Capybara
|
|
60
65
|
driver.is_a?(Capybara::Selenium::Driver)
|
61
66
|
end
|
62
67
|
|
68
|
+
def send_config_to_browser(js)
|
69
|
+
begin
|
70
|
+
with_max_wait_time(2) do
|
71
|
+
page.execute_script(<<~JS)
|
72
|
+
if (window.CapybaraLockstep) {
|
73
|
+
#{js}
|
74
|
+
}
|
75
|
+
JS
|
76
|
+
end
|
77
|
+
rescue StandardError => e
|
78
|
+
log "#{e.class.name} while configuring capybara-lockstep in browser: #{e.message}"
|
79
|
+
# Don't fail. The next page load will include the snippet with the new config.
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
63
83
|
end
|
64
84
|
end
|
65
85
|
end
|
@@ -1,13 +1,14 @@
|
|
1
1
|
window.CapybaraLockstep = (function() {
|
2
|
-
let
|
2
|
+
let jobCount = 0
|
3
3
|
let idleCallbacks = []
|
4
4
|
let debug = false
|
5
|
+
let waitTasks = 0
|
5
6
|
|
6
7
|
function isIdle() {
|
7
8
|
// Can't check for document.readyState or body.initializing here,
|
8
9
|
// since the user might navigate away from the page before it finishes
|
9
10
|
// initializing.
|
10
|
-
return
|
11
|
+
return jobCount === 0
|
11
12
|
}
|
12
13
|
|
13
14
|
function isBusy() {
|
@@ -33,9 +34,9 @@ window.CapybaraLockstep = (function() {
|
|
33
34
|
}
|
34
35
|
|
35
36
|
function startWork(tag) {
|
36
|
-
|
37
|
+
jobCount++
|
37
38
|
if (tag) {
|
38
|
-
logNegative('Started work: %s [%d jobs]', tag,
|
39
|
+
logNegative('Started work: %s [%d jobs]', tag, jobCount)
|
39
40
|
}
|
40
41
|
}
|
41
42
|
|
@@ -44,23 +45,31 @@ window.CapybaraLockstep = (function() {
|
|
44
45
|
promise.then(stopWork, stopWork)
|
45
46
|
}
|
46
47
|
|
47
|
-
function
|
48
|
-
|
49
|
-
|
48
|
+
function stopWork(tag) {
|
49
|
+
let tasksElapsed = 0
|
50
|
+
|
51
|
+
let check = function() {
|
52
|
+
if (tasksElapsed < waitTasks) {
|
53
|
+
tasksElapsed++
|
54
|
+
setTimeout(check)
|
55
|
+
} else {
|
56
|
+
stopWorkNow(tag)
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
check()
|
50
61
|
}
|
51
62
|
|
52
|
-
function
|
53
|
-
|
63
|
+
function stopWorkNow(tag) {
|
64
|
+
jobCount--
|
54
65
|
|
55
66
|
if (tag) {
|
56
|
-
logPositive('Finished work: %s [%d jobs]', tag,
|
67
|
+
logPositive('Finished work: %s [%d jobs]', tag, jobCount)
|
57
68
|
}
|
58
69
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
})
|
63
|
-
idleCallbacks = []
|
70
|
+
let idleCallback
|
71
|
+
while (isIdle() && (idleCallback = idleCallbacks.shift())) {
|
72
|
+
idleCallback('Finished waiting for JavaScript')
|
64
73
|
}
|
65
74
|
}
|
66
75
|
|
@@ -118,12 +127,16 @@ window.CapybaraLockstep = (function() {
|
|
118
127
|
}
|
119
128
|
|
120
129
|
function onInteraction(event) {
|
121
|
-
|
122
|
-
//
|
123
|
-
|
130
|
+
startWork()
|
131
|
+
// (1) We wait until the end of this microtask, assuming that any callback that
|
132
|
+
// would queue an AJAX request or load additional scripts will run by then.
|
133
|
+
// (2) For performance reasons we don't wait for `waitTasks` here.
|
134
|
+
// Whatever was queued by an event handler should call us again, and then
|
135
|
+
// we do wait for additional tasks.
|
136
|
+
Promise.resolve().then(stopWorkNow)
|
124
137
|
}
|
125
138
|
|
126
|
-
function
|
139
|
+
function trackRemoteElements() {
|
127
140
|
if (!window.MutationObserver) {
|
128
141
|
return
|
129
142
|
}
|
@@ -138,13 +151,16 @@ window.CapybaraLockstep = (function() {
|
|
138
151
|
function trackJQuery() {
|
139
152
|
// jQuery may be loaded after us, so we wait until DOMContentReady.
|
140
153
|
whenReady(function() {
|
141
|
-
if (!window.jQuery) {
|
154
|
+
if (!window.jQuery || waitTasks > 0) {
|
142
155
|
return
|
143
156
|
}
|
144
157
|
|
145
158
|
// Although $.ajax() uses XHR internally, it also uses $.Deferred() which does
|
146
159
|
// not resolve in the next microtask but in the next *task* (it makes itself
|
147
160
|
// async using setTimoeut()). Hence we need to wait for it in addition to XHR.
|
161
|
+
//
|
162
|
+
// If user code also uses $.Deferred(), it is also recommended to set
|
163
|
+
// CapybaraLockdown.waitTasks = 1 or higher.
|
148
164
|
let oldAjax = window.jQuery.ajax
|
149
165
|
window.jQuery.ajax = function() {
|
150
166
|
let promise = oldAjax.apply(this, arguments)
|
@@ -161,7 +177,7 @@ window.CapybaraLockstep = (function() {
|
|
161
177
|
// we consider ourselves busy.
|
162
178
|
startWork()
|
163
179
|
whenReady(function() {
|
164
|
-
|
180
|
+
stopWorkNow()
|
165
181
|
if (document.body.hasAttribute(INITIALIZING_ATTRIBUTE)) {
|
166
182
|
startWork('Page initialization')
|
167
183
|
let observer = new MutationObserver(onInitializingAttributeChanged)
|
@@ -176,34 +192,97 @@ window.CapybaraLockstep = (function() {
|
|
176
192
|
}
|
177
193
|
}
|
178
194
|
|
179
|
-
function isRemoteScript(
|
180
|
-
if (
|
181
|
-
let src =
|
182
|
-
let type =
|
195
|
+
function isRemoteScript(element) {
|
196
|
+
if (element.tagName === 'SCRIPT') {
|
197
|
+
let src = element.getAttribute('src')
|
198
|
+
let type = element.getAttribute('type')
|
199
|
+
|
200
|
+
return src && (!type || /javascript/i.test(type))
|
201
|
+
}
|
202
|
+
}
|
203
|
+
|
204
|
+
function isRemoteImage(element) {
|
205
|
+
if (element.tagName === 'IMG' && !element.complete) {
|
206
|
+
let src = element.getAttribute('src')
|
207
|
+
let srcSet = element.getAttribute('srcset')
|
208
|
+
|
209
|
+
let localSrcPattern = /^data:/
|
210
|
+
let localSrcSetPattern = /(^|\s)data:/
|
211
|
+
|
212
|
+
let hasLocalSrc = src && localSrcPattern.test(src)
|
213
|
+
let hasLocalSrcSet = srcSet && localSrcSetPattern.test(srcSet)
|
214
|
+
|
215
|
+
return (src && !hasLocalSrc) || (srcSet && !hasLocalSrcSet)
|
216
|
+
}
|
217
|
+
}
|
183
218
|
|
184
|
-
|
219
|
+
function isRemoteInlineFrame(element) {
|
220
|
+
if (element.tagName === 'IFRAME') {
|
221
|
+
let src = element.getAttribute('src')
|
222
|
+
let localSrcPattern = /^data:/
|
223
|
+
let hasLocalSrc = src && localSrcPattern.test(src)
|
224
|
+
return (src && !hasLocalSrc)
|
185
225
|
}
|
186
226
|
}
|
187
227
|
|
188
|
-
function
|
189
|
-
|
228
|
+
function trackRemoteElement(element, condition, workTag) {
|
229
|
+
if (!condition(element)) {
|
230
|
+
return
|
231
|
+
}
|
232
|
+
|
233
|
+
let stopped = false
|
234
|
+
|
190
235
|
startWork(workTag)
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
236
|
+
|
237
|
+
let doStop = function() {
|
238
|
+
stopped = true
|
239
|
+
element.removeEventListener('load', doStop)
|
240
|
+
element.removeEventListener('error', doStop)
|
241
|
+
stopWork(workTag)
|
242
|
+
}
|
243
|
+
|
244
|
+
let checkCondition = function() {
|
245
|
+
if (stopped) {
|
246
|
+
// A `load` or `error` event has fired.
|
247
|
+
// We can stop here. No need to schedule another check.
|
248
|
+
return
|
249
|
+
} else if (isDetached(element) || !condition(element)) {
|
250
|
+
// If it is detached or if its `[src]` attribute changes to a data: URL
|
251
|
+
// we may never get a `load` or `error` event.
|
252
|
+
doStop()
|
253
|
+
} else {
|
254
|
+
scheduleCheckCondition()
|
255
|
+
}
|
256
|
+
}
|
257
|
+
|
258
|
+
let scheduleCheckCondition = function() {
|
259
|
+
setTimeout(checkCondition, 200)
|
260
|
+
}
|
261
|
+
|
262
|
+
element.addEventListener('load', doStop)
|
263
|
+
element.addEventListener('error', doStop)
|
264
|
+
|
265
|
+
// We periodically check whether we still think the element will
|
266
|
+
// produce a `load` or `error` event.
|
267
|
+
scheduleCheckCondition()
|
195
268
|
}
|
196
269
|
|
197
270
|
function onAnyElementChanged(changes) {
|
198
271
|
changes.forEach(function(change) {
|
199
272
|
change.addedNodes.forEach(function(addedNode) {
|
200
|
-
if (
|
201
|
-
|
273
|
+
if (addedNode.nodeType === Node.ELEMENT_NODE) {
|
274
|
+
trackRemoteElement(addedNode, isRemoteScript, 'Script')
|
275
|
+
trackRemoteElement(addedNode, isRemoteImage, 'Image')
|
276
|
+
trackRemoteElement(addedNode, isRemoteInlineFrame, 'Inline frame')
|
202
277
|
}
|
203
278
|
})
|
204
279
|
})
|
205
280
|
}
|
206
281
|
|
282
|
+
function isDetached(element) {
|
283
|
+
return !document.contains(element)
|
284
|
+
}
|
285
|
+
|
207
286
|
function whenReady(callback) {
|
208
287
|
// Values are "loading", "interactive" and "completed".
|
209
288
|
// https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState
|
@@ -218,7 +297,7 @@ window.CapybaraLockstep = (function() {
|
|
218
297
|
trackFetch()
|
219
298
|
trackXHR()
|
220
299
|
trackInteraction()
|
221
|
-
|
300
|
+
trackRemoteElements()
|
222
301
|
trackJQuery()
|
223
302
|
trackHydration()
|
224
303
|
}
|
@@ -236,9 +315,8 @@ window.CapybaraLockstep = (function() {
|
|
236
315
|
startWork: startWork,
|
237
316
|
stopWork: stopWork,
|
238
317
|
synchronize: synchronize,
|
239
|
-
set debug(
|
240
|
-
|
241
|
-
}
|
318
|
+
set debug(value) { debug = value },
|
319
|
+
set waitTasks(value) { waitTasks = value }
|
242
320
|
}
|
243
321
|
})()
|
244
322
|
|
@@ -17,13 +17,22 @@ module Capybara
|
|
17
17
|
tag_options[:nonce] = options.fetch(:nonce, true)
|
18
18
|
end
|
19
19
|
|
20
|
-
|
20
|
+
js = capybara_lockstep_js + capybara_lockstep_config_js(options)
|
21
|
+
javascript_tag(js, tag_options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def capybara_lockstep_config_js(options = {})
|
25
|
+
js = ''
|
21
26
|
|
22
27
|
if (debug = options.fetch(:debug, Lockstep.debug?))
|
23
|
-
|
28
|
+
js += "\nCapybaraLockstep.debug = #{debug.to_json}"
|
29
|
+
end
|
30
|
+
|
31
|
+
if (wait_tasks = options.fetch(:wait_tasks, Lockstep.wait_tasks))
|
32
|
+
js += "\nCapybaraLockstep.waitTasks = #{wait_tasks.to_json}"
|
24
33
|
end
|
25
34
|
|
26
|
-
|
35
|
+
js
|
27
36
|
end
|
28
37
|
|
29
38
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: capybara-lockstep
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Henning Koch
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-03-
|
11
|
+
date: 2021-03-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: capybara
|