capybara-lockstep 0.4.0 → 1.1.0

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: 783200fa3cbde0166b11c213bb677250021185b7c9898a3fd95594dd8433b89a
4
- data.tar.gz: 77efe617f3ce7f138da78e8b9db031b9a8f75f07d3afe8379ae903266a9fa389
3
+ metadata.gz: 4ece5e0daeb6883b02791ed06f6ff2afca446e5aa5635f22d3ec5b94d0e96aa7
4
+ data.tar.gz: 5e4d53a8c59f71071aa48fee8ec87fa62e5c189e026b5cac563fd9427a082c59
5
5
  SHA512:
6
- metadata.gz: 6ec80d343f193b6b5a166bae1bf5ab6be2e7ae2e3c82d903596e4c604985d96382cce5012df5abdb714da614d8741f062386d60a47a1a3340734c22e46814949
7
- data.tar.gz: 02e870bc8b8377be23e0ec97086219c7fd75a098f6f4eeda953dac86b7eb3a957d93984fb26265e8322c21989daa28cf4713ee3dc1772cd652f493e7999b6b57
6
+ metadata.gz: 7e68bf91ab8b5ecf91b145159697e7990dbbbb3a5497955d50d91ea83d802c50195f803ae1fd029b2b76dc18b407bd1995b03aa292e30873cd35f8746dfa509e
7
+ data.tar.gz: 8fb8d9b19fc47b5769208d36125b6cc2fb40375a8d5b46216ec318a3fd8459ca26f6bf22f352efcf350b7d7cbfe89f7bb866633fb4e096812b7d31513d4c7bae
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: Tests
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ pull_request:
8
+ branches:
9
+ - master
10
+ workflow_dispatch:
11
+ branches:
12
+ - master
13
+ jobs:
14
+ test:
15
+ runs-on: ubuntu-20.04
16
+ timeout-minutes: 3
17
+ strategy:
18
+ fail-fast: false
19
+ matrix:
20
+ include:
21
+ - ruby: 2.6.6
22
+ gemfile: Gemfile
23
+ - ruby: 2.7.2
24
+ gemfile: Gemfile
25
+ - ruby: 3.0.1
26
+ gemfile: Gemfile
27
+ env:
28
+ BUNDLE_GEMFILE: "${{ matrix.gemfile }}"
29
+ steps:
30
+ - uses: actions/checkout@v2
31
+ - name: Install Chrome
32
+ uses: browser-actions/setup-chrome@latest
33
+ - name: Show Chrome version
34
+ run: chrome --version
35
+ - name: Install ChromeDriver
36
+ uses: nanasess/setup-chromedriver@master
37
+ - name: Install ruby
38
+ uses: ruby/setup-ruby@v1
39
+ with:
40
+ ruby-version: "${{ matrix.ruby }}"
41
+ - name: Bundle
42
+ run: |
43
+ gem install bundler:2.2.15
44
+ bundle install --no-deployment
45
+ - name: Run tests
46
+ uses: nick-invision/retry@v2
47
+ with:
48
+ timeout_seconds: 30
49
+ max_attempts: 3
50
+ command: bundle exec rake spec
data/.gitignore CHANGED
@@ -6,6 +6,7 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ .idea
9
10
 
10
11
  # rspec failure tracking
11
12
  .rspec_status
data/CHANGELOG.md ADDED
@@ -0,0 +1,69 @@
1
+ All notable changes to this project will be documented in this file.
2
+
3
+ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
4
+
5
+ ## 1.1.0
6
+
7
+ - Stop handling of `[data-initializing]` attribute. Apps that have late initialization after the `load` event can just use `CapybaraLockstep.startWork()`.
8
+ - Remove useless tracking of interaction events like `"click"` or `"focus"`. If such an event handler would start an AJAX request, it is already tracked.
9
+ - On apps with Unpoly 0.x, wait for one more task after `DOMContentLoaded`. Please upgrade to Unpoly 1.x or 2.x, as this logic will be removed in a year or so.
10
+
11
+ ## 1.0.0
12
+
13
+ - First stable release.
14
+ - Replace option `Capybara::Lockstep.config` (`true`, `false`) with a more refined option `.mode` (`:auto`, `:manual`, `:off`)
15
+
16
+ ## 0.7.0
17
+
18
+ - Ruby 3 compatibility.
19
+ - Fix logging.
20
+
21
+ ## 0.6.0
22
+
23
+ - Synchronize around `evaluate_script` and `execute_script`.
24
+ - Improve logging.
25
+
26
+ ## 0.5.0
27
+
28
+ - Allow developer to signal custom async work.
29
+ - Option to wait additional tasks, to handle legacy promise implementations.
30
+ - Debugging log can be enabled during a running test.
31
+ - Also wait for images and iframes.
32
+
33
+ ## 0.4.0
34
+
35
+ - Don't fail the test when synchronization times out.
36
+ - Capybara::Lockstep.debug = true will now also enable client-side logging on the browser's JavaScript console.
37
+ - Always wait at least for `Capybara.default_max_wait_time`.
38
+
39
+ ## 0.3.2
40
+
41
+ - Delay synchronization when an alert is open (instead of failing)
42
+
43
+
44
+ ## 0.3.1
45
+
46
+ - Fix typo in log message
47
+
48
+ ## 0.3.0
49
+
50
+ - Rework entire waiting logic to be lazy.
51
+ - There is now a single method `Capybara::Lockstep.synchronize` (no distinction between awaiting "initialization" and "idle").
52
+
53
+ ## 0.2.3
54
+
55
+ - When we cannot wait for browser idle due to an open alert, wait before the next Capybara synchronize
56
+
57
+ ## 0.2.2
58
+
59
+ - Fix incorrect data in gemspec.
60
+
61
+
62
+ ## 0.2.1
63
+
64
+ - Internal changes.
65
+
66
+
67
+ ## 0.2.0
68
+
69
+ - Initial release.
data/Gemfile CHANGED
@@ -8,5 +8,9 @@ gemspec
8
8
  gem "rake", "~> 13.0"
9
9
 
10
10
  gem "rspec", "~> 3.0"
11
+ gem 'jasmine'
12
+ gem 'thin' # ruby 3 does not include a webserver
13
+ gem 'chrome_remote'
11
14
 
12
15
  gem 'byebug'
16
+ gem 'gemika'
data/Gemfile.lock CHANGED
@@ -1,15 +1,16 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- capybara-lockstep (0.4.0)
4
+ capybara-lockstep (1.1.0)
5
5
  activesupport (>= 3.2)
6
6
  capybara (>= 2.0)
7
+ ruby2_keywords
7
8
  selenium-webdriver (>= 3)
8
9
 
9
10
  GEM
10
11
  remote: https://rubygems.org/
11
12
  specs:
12
- activesupport (6.1.3)
13
+ activesupport (6.1.3.1)
13
14
  concurrent-ruby (~> 1.0, >= 1.0.2)
14
15
  i18n (>= 1.6, < 2)
15
16
  minitest (>= 5.1)
@@ -27,16 +28,26 @@ GEM
27
28
  regexp_parser (>= 1.5, < 3.0)
28
29
  xpath (~> 3.2)
29
30
  childprocess (3.0.0)
31
+ chrome_remote (0.3.0)
32
+ websocket-driver (~> 0.6)
30
33
  concurrent-ruby (1.1.8)
34
+ daemons (1.3.1)
31
35
  diff-lcs (1.3)
32
- i18n (1.8.9)
36
+ eventmachine (1.2.7)
37
+ gemika (0.6.0)
38
+ i18n (1.8.10)
33
39
  concurrent-ruby (~> 1.0)
34
- mini_mime (1.0.2)
35
- mini_portile2 (2.5.0)
40
+ jasmine (3.6.0)
41
+ jasmine-core (~> 3.6.0)
42
+ phantomjs
43
+ rack (>= 1.2.1)
44
+ rake
45
+ jasmine-core (3.6.0)
46
+ mini_mime (1.1.0)
36
47
  minitest (5.14.4)
37
- nokogiri (1.11.1)
38
- mini_portile2 (~> 2.5.0)
48
+ nokogiri (1.11.3-x86_64-linux)
39
49
  racc (~> 1.4)
50
+ phantomjs (2.1.1.0)
40
51
  public_suffix (4.0.6)
41
52
  racc (1.5.2)
42
53
  rack (2.2.3)
@@ -57,12 +68,20 @@ GEM
57
68
  diff-lcs (>= 1.2.0, < 2.0)
58
69
  rspec-support (~> 3.7.0)
59
70
  rspec-support (3.7.0)
71
+ ruby2_keywords (0.0.4)
60
72
  rubyzip (2.3.0)
61
73
  selenium-webdriver (3.142.7)
62
74
  childprocess (>= 0.5, < 4.0)
63
75
  rubyzip (>= 1.2.2)
76
+ thin (1.8.0)
77
+ daemons (~> 1.0, >= 1.0.9)
78
+ eventmachine (~> 1.0, >= 1.0.4)
79
+ rack (>= 1, < 3)
64
80
  tzinfo (2.0.4)
65
81
  concurrent-ruby (~> 1.0)
82
+ websocket-driver (0.7.3)
83
+ websocket-extensions (>= 0.1.0)
84
+ websocket-extensions (0.1.5)
66
85
  xpath (3.2.0)
67
86
  nokogiri (~> 1.8)
68
87
  zeitwerk (2.4.2)
@@ -73,8 +92,12 @@ PLATFORMS
73
92
  DEPENDENCIES
74
93
  byebug
75
94
  capybara-lockstep!
95
+ chrome_remote
96
+ gemika
97
+ jasmine
76
98
  rake (~> 13.0)
77
99
  rspec (~> 3.0)
100
+ thin
78
101
 
79
102
  BUNDLED WITH
80
- 2.2.12
103
+ 2.2.15
data/README.md CHANGED
@@ -1,12 +1,15 @@
1
1
  # capybara-lockstep
2
2
 
3
- This Ruby gem synchronizes [Capybara](https://github.com/teamcapybara/capybara) commands with client-side JavaScript and AJAX requests. This greatly improves the stability of a full-stack integration test suite, even if that suite has timing issues.
3
+ This Ruby gem synchronizes [Capybara](https://github.com/teamcapybara/capybara) commands with client-side JavaScript and AJAX requests. This greatly improves the stability of an end-to-end ("E2E") test suite, even if that suite has timing issues.
4
+
5
+ The next section explain why your test suite is flaky and how capybara-lockstep can help.\
6
+ If you don't care you may skip to [installation instructions](#installation).
4
7
 
5
8
 
6
9
  Why are tests flaky?
7
10
  --------------------
8
11
 
9
- A naively written integration test will have [race conditions](https://makandracards.com/makandra/47336-fixing-flaky-integration-tests) between the test script and the controlled browser. How often these timing issues will fail your test depends on luck and your machine's performance. You may not see these issues for years until a colleague runs your suite on their new laptop.
12
+ A naively written E2E test will have [race conditions](https://makandracards.com/makandra/47336-fixing-flaky-integration-tests) between the test script and the controlled browser. How often these timing issues will fail your test depends on luck and your machine's performance. You may not see these issues for years until a colleague runs your suite on their new laptop.
10
13
 
11
14
  Here is a typical example for a test that will fail with unlucky timing:
12
15
 
@@ -23,32 +26,50 @@ end
23
26
 
24
27
  This test has four timing issues that may cause it to fail:
25
28
 
26
- 1. We click on the "New tweet" button, but the the JS event handler to open the tweet form wasn't registered yet.
29
+ 1. We click on the *New tweet* button, but the the JS event handler to open the tweet form wasn't registered yet.
27
30
  2. We start filling in the form, but it wasn't loaded yet.
28
31
  3. After sending the tweet we immediately navigate away, killing the form submission request that is still in flight. Hence the tweet will never appear in the next step.
29
32
  4. We look for the new tweet, but the timeline wasn't loaded yet.
30
33
 
31
- Capybara will retry individual commands or expectations when they fail. However, only issues **2** and **4** can be healed by retrying.
34
+ [Capybara will retry](https://github.com/teamcapybara/capybara#asynchronous-javascript-ajax-and-friends) individual commands or expectations when they fail.\
35
+ However, only issues **2** and **4** can be healed by retrying.
36
+
37
+ While it is [possible](https://makandracards.com/makandra/47336-fixing-flaky-integration-tests) to remove most of the timing issues above, it requires skill and discipline.\
38
+ capybara-lockstep fixes issues **1**, **2**, **3** and **4** without any changes to the test code.
39
+
40
+
41
+ ### This is a JavaScript problem
42
+
43
+ The timing issues above will only manifest in an app where links, forms and buttons are handled by JavaScript.
44
+
45
+ When all you have is standard HTML links and forms, stock Capybara will not see timing issues:
46
+
47
+ - After a `visit()` Capybara/WebDriver will wait until the page is completely loaded
48
+ - When following a link Capybara/WebDriver will wait until the link destination is completely loaded
49
+ - When submitting a form Capybara/WebDriver will wait until the response is completely loaded
50
+
51
+ However, when JavaScript handles a link click, you get **zero guarantees**.\
52
+ Capybara/WebDriver **will not wait** for AJAX requests or any other async work.
32
53
 
33
- While it is [possible](https://makandracards.com/makandra/47336-fixing-flaky-integration-tests) to remove most of the timing issues above, it requires skill and discipline. capybara-lockstep fixes issues **1**, **2**, **3** and **4** without any changes to the test code.
34
54
 
35
55
 
36
56
  How capybara-lockstep helps
37
57
  ---------------------------
38
58
 
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.
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).
40
60
 
41
- Whenever Capybara visits a new URL:
61
+ Before Capybara simulates a user interaction (clicking, typing, etc.) or before it visits a new URL:
42
62
 
43
- - capybara-lockstep waits for all document resources to load.
63
+ - capybara-lockstep waits for all document resources to load (images, CSS, fonts, frames).
64
+ - capybara-lockstep waits for any AJAX requests to finish.
44
65
  - capybara-lockstep waits for client-side JavaScript to render or hydrate DOM elements.
45
- - capybara-lockstep waits for any AJAX requests.
46
66
  - 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
+ - capybara-lockstep waits for dynamically inserted `<img>` or `<iframe>` elements to load.
47
68
 
48
- Whenever Capybara simulates a user interaction (clicking, typing, etc.):
69
+ In summary Capybara can no longer observe the page while HTTP requests are in flight.
70
+ This covers most async work that causes flaky tests.
49
71
 
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).
72
+ You can also configure capybara-lockstep to [wait for other async work](#signaling-asynchronous-work) that does not involve the network, like animations.
52
73
 
53
74
 
54
75
  Installation
@@ -80,105 +101,81 @@ And then execute:
80
101
  $ bundle install
81
102
  ```
82
103
 
83
- If you're not using Rails you should also `require 'capybara-lockstep'` in your `spec_helper.rb` (RSpec) or `env.rb` (Cucumber).
104
+ If you're not using Rails you should also `require 'capybara-lockstep'` in your `spec_helper.rb` (RSpec), `test_helper.rb` (Minitest) or `env.rb` (Cucumber).
84
105
 
85
106
 
86
107
  ### Including the JavaScript snippet
87
108
 
88
109
  capybara-lockstep requires a JavaScript snippet to be embedded by the application under test. If that snippet is missing on a screen, capybara-lockstep will not be able to synchronize with the browser. In that case the test will continue without synchronization.
89
110
 
90
- If you're using Rails you can use the `capybara_lockstep` helper to insert the snippet into your application layouts:
111
+ **If you're using Rails** you can use the `capybara_lockstep` helper to insert the snippet into your application layouts:
91
112
 
92
113
  ```erb
93
114
  <%= capybara_lockstep if Rails.env.test? %>
94
115
  ```
95
116
 
96
- Ideally the snippet should be included in the `<head>` before any other `<script>` tags. If that's impractical you will also see some benefit if you insert it later.
117
+ Ideally the snippet should be included in the `<head>` before any other `<script>` tags.
97
118
 
98
- If you have a strict [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP), the `capybara_lockstep` helper will insert a CSP nonce by default. You can also pass a `:nonce` option.
119
+ **If you're not using Rails** you can `include Capybara::Lockstep::Helper` and access the JavaScript with `capybara_lockstep_script`.
99
120
 
100
- If you're not using Rails you can `include Capybara::Lockstep::Helper` and access the JavaScript with `capybara_lockstep_script`.
121
+ **If you have a strict [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)** the `capybara_lockstep` Rails helper will insert a CSP nonce by default. You can also pass an explicit nonce string using the `:nonce` option.
101
122
 
102
123
 
103
- ### Signaling the end of page initialization
104
124
 
105
- Most web applications run some JavaScript after the document was loaded. This JavaScript enhances existing DOM elements ("hydration") or renders additional element into the DOM.
125
+ ### Verify successful integration
106
126
 
107
- capybara-lockstep needs to know when your JavaScript is done hydrating and rendering, so it can automatically wait for initialization after every Capybara `visit()`.
127
+ capybara-lockstep will automatically patch Capybara to wait for the browser after every command.
108
128
 
109
- To signal that JavaScript is still initializing, your application layouts should render the `<body>` element with an `[data-initializing]` attribute:
129
+ Run your test suite to see if integration was successful and whether stability improves. During validation we recommend to activate the [debugging log](#debugging-log) before your test:
110
130
 
111
- ```html
112
- <body data-initializing>
131
+ ```ruby
132
+ Capybara::Lockstep.debug = true
113
133
  ```
114
134
 
115
- Your application JavaScript should remove the `[data-initializing]` attribute when it is done hydrating and rendering.
116
-
117
- 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
-
119
- If all your initializing JavaScript runs synchronously on `DOMContentLoaded`, you can remove `[data-initializing]` in an event handler:
135
+ You should see messages like this in your console:
120
136
 
121
- ```js
122
- document.addEventListener('DOMContentLoaded', function() {
123
- // Initialize the page here
124
- document.body.removeAttribute('data-initializing')
125
- })
137
+ ```text
138
+ [capybara-lockstep] Synchronizing
139
+ [capybara-lockstep] Finished waiting for JavaScript
140
+ [capybara-lockstep] Synchronized successfully
126
141
  ```
127
142
 
128
- If you do any asynchronous initialization work (like lazy-loading another script) you should only remove `[data-initializing]` once that is done:
143
+ Note that you may see some failures from tests with wrong assertions, which previously passed due to lucky timing.
129
144
 
130
- ```js
131
- document.addEventListener('DOMContentLoaded', function() {
132
- import('huge-library').then(function({ hugeLibrary }) {
133
- hugeLibrary.initialize()
134
- document.body.removeAttribute('data-initializing')
135
- })
136
- })
137
- ```
138
145
 
139
- If you call libraries during initialization, you may need to check the library code to see whether it finishes synchronously or asynchronously. E.g. if you discover that a library delays work for a task, you must also wait another task to remove `[data-initializing]`:
140
146
 
141
- ```js
142
- document.addEventListener('DOMContentLoaded', function() {
143
- Libary.doWorkInNextTask()
144
- setTimeout(function() { document.body.removeAttribute('data-initializing') })
145
- })
146
- ```
147
+ ## Signaling asynchronous work
147
148
 
148
- 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
+ By default capybara-lockstep blocks all async work that
150
+
151
+ If for some reason you want capybara-lockstep to consider additional asynchronous work as "busy", you can do so:
149
152
 
150
153
  ```js
151
- up.compiler('body', function(body) {
152
- body.removeAttribute('data-initializing')
154
+ CapybaraLockstep.startWork('Eject warp core')
155
+ doAsynchronousWork().then(function() {
156
+ CapybaraLockstep.stopWork('Eject warp core')
153
157
  })
154
158
  ```
155
159
 
156
- 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]`:
160
+ 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:
157
161
 
158
- ```js
159
- app.directive('body', function() {
160
- return {
161
- restrict: 'E',
162
- link: function() {
163
- document.body.removeAttribute('data-initializing')
164
- }
165
- }
166
- })
162
+ ```text
163
+ [capybara-lockstep] Started work: Eject warp core [1 jobs]
164
+ [capybara-lockstep] Finished work: Eject warp core [0 jobs]
167
165
  ```
168
166
 
169
- ### Verify successful integration
167
+ You may omit the string argument, in which case nothing will be logged, but the work will still be tracked.
170
168
 
171
- capybara-lockstep will automatically patch Capybara to wait for the browser after every command.
172
169
 
173
- Run your test suite to see if integration was successful and whether stability improves. During validation we recommend to activate `Capybara::Lockstep.debug = true` in your `spec_helper.rb` (RSpec) or `env.rb` (Cucumber). You should see messages like this in your console:
170
+ ## Note on interacting with the JavaScript API
174
171
 
175
- ```text
176
- [capybara-lockstep] Synchronizing
177
- [capybara-lockstep] Finished waiting for JavaScript
178
- [capybara-lockstep] Synchronized successfully
179
- ```
172
+ 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.
180
173
 
181
- Note that you may see some failures from tests with wrong assertions, which sometimes passed due to lucky timing.
174
+ ```js
175
+ if (window.CapybaraLockstep) {
176
+ // interact with CapybaraLockstep
177
+ }
178
+ ```
182
179
 
183
180
 
184
181
  ## Performance impact
@@ -187,7 +184,7 @@ capybara-lockstep may or may not impact the runtime of your test suite. It depen
187
184
 
188
185
  While waiting for the browser to be idle does take a few milliseconds, Capybara no longer needs to retry failed commands. You will also save time from not needing to re-run failed tests.
189
186
 
190
- In casual testing I experienced a performance impact between +/- 10%.
187
+ In casual testing with large test suites I experienced a performance impact between +/- 10%.
191
188
 
192
189
 
193
190
  ## Debugging log
@@ -224,27 +221,23 @@ You may also configure logging to an existing logger object:
224
221
  Capybara::Lockstep.debug = Rails.logger
225
222
  ```
226
223
 
224
+ ### Logging in the browser only
227
225
 
228
- ## Disabling synchronization
229
-
230
- Sometimes you want to disable browser synchronization, e.g. to observe a loading spinner during a long-running request.
231
-
232
- To disable synchronization:
226
+ To enable logging in the browser console (but not STDOUT), include the [JavaScript snippet](#including-the-javascript-snippet) with `{ debug: true }`:
233
227
 
234
228
  ```ruby
235
- begin
236
- Capybara::Lockstep.enabled = false
237
- do_unsynchronized_work
238
- ensure
239
- Capybara::Lockstep.enabled = true
240
- end
229
+ capybara_lockstep(debug: true)
241
230
  ```
242
231
 
243
232
  ## Synchronization timeout
244
233
 
245
234
  By default capybara-lockstep will wait `Capybara.default_max_wait_time` seconds for the page initialize and for JavaScript and AJAX request to finish.
246
235
 
247
- When synchronization times out, capybara-lockstep will log but not raise an error.
236
+ When synchronization times out, capybara-lockstep will [log](#debugging-log):
237
+
238
+ ```text
239
+ [capybara-lockstep] Could not synchronize within 3 seconds
240
+ ```
248
241
 
249
242
  You can configure a different timeout:
250
243
 
@@ -252,10 +245,19 @@ You can configure a different timeout:
252
245
  Capybara::Lockstep.timeout = 5 # seconds
253
246
  ```
254
247
 
255
- To revert to defaulting to `Capybara.default_max_wait_time`, set the timeout to `nil`:
248
+ By default Capybara will **not** raise an error after a timeout. You may occasionally get a slow server response, and Capybara will retry synchronization before the next interaction or `visit`. This is often good enough.
249
+
250
+ If you want to be strict you may configure that an `Capybara::Lockstep::Timeout` error is raised after a timeout:
251
+
252
+ ```ruby
253
+ Capybara::Lockstep.timeout_with = :error
254
+ ```
255
+
256
+ To revert to defaults:
256
257
 
257
258
  ```ruby
258
259
  Capybara::Lockstep.timeout = nil
260
+ Capybara::Lockstep.timeout_with = nil
259
261
  ```
260
262
 
261
263
 
@@ -275,48 +277,82 @@ You may also synchronize from your client-side JavaScript. The following will ru
275
277
  CapybaraLockstep.synchronize(callback)
276
278
  ```
277
279
 
278
- ## Signaling asynchronous work
279
280
 
280
- If for some reason you want capybara-lockstep to consider additional asynchronous work as "busy", you can do so:
281
+ ## Disabling synchronization
281
282
 
282
- ```js
283
- CapybaraLockstep.startWork('Eject warp core')
284
- doAsynchronousWork().then(function() {
285
- CapybaraLockstep.stopWork('Eject warp core')
286
- })
283
+ Sometimes you want to disable browser synchronization, e.g. to observe a loading spinner during a long-running request.
284
+
285
+ To disable automatic synchronization:
286
+
287
+ ```ruby
288
+ begin
289
+ Capybara::Lockstep.mode = :manual
290
+ do_unsynchronized_work
291
+ ensure
292
+ Capybara::Lockstep.mode = :auto
293
+ end
287
294
  ```
288
295
 
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:
296
+ In the `:manual` mode you may still force synchronization by calling `Capybara::Lockstep.synchronize` manually.
290
297
 
291
- ```text
292
- [capybara-lockstep] Started work: Eject warp core [1 jobs]
293
- [capybara-lockstep] Finished work: Eject warp core [0 jobs]
298
+ To completely disable synchronization:
299
+
300
+ ```ruby
301
+ Capybara::Lockstep.mode = :off
302
+ Capybara::Lockstep.synchronize # will not synchronize
294
303
  ```
295
304
 
296
- You may omit the string argument, in which case nothing will be logged, but the work will still be tracked.
297
305
 
298
306
 
299
- ## Note on interacting with the JavaScript API
307
+ ## Handling legacy promises
300
308
 
301
- 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.
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.
302
310
 
303
- ```
304
- if (window.CapybaraLockstep) {
305
- // interact with CapybaraLockstep
306
- }
311
+ This means there is a time window in which all AJAX requests have finished, but their callbacks have not yet run:
312
+
313
+ ```js
314
+ $.ajax('/foo').then(function() {
315
+ // This callback runs one task after the response was received
316
+ })
307
317
  ```
308
318
 
309
- ## Development
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:
310
320
 
311
- 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.
321
+ ```js
322
+ Capybara:Lockstep.wait_tasks = 1
323
+ ```
312
324
 
313
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
325
+ If you see longer `then()` chains in your code, you may need to configure a higher number of tasks to wait.
326
+
327
+ This will have a negative performance impact on your test suite.
314
328
 
315
329
 
316
330
  ## Contributing
317
331
 
318
332
  Pull requests are welcome on GitHub at <https://github.com/makandra/capybara-lockstep>.
319
333
 
334
+ After checking out the repo, run `bin/setup` to install dependencies.
335
+
336
+ Then, run `rake spec` to run the tests.
337
+
338
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
339
+
340
+ ### Manually testing a change
341
+
342
+ To test an unrelased change with a test suite, we recommend to temporarily link the local repository from your test suites's `Gemfile`:
343
+
344
+ ```ruby
345
+ gem 'capybara-lockstep', path: '../capybara-lockstep'
346
+ ```
347
+
348
+ As an alternative you may also install this gem onto your local machine by running `bundle exec rake install`.
349
+
350
+ ### Releasing a new version
351
+
352
+ - Update the version number in `version.rb`
353
+ - Run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
354
+ - If RubyGems publishing seems to freeze, try entering your OTP code.
355
+
320
356
 
321
357
  ## License
322
358