integration-tests-rails 1.0.8 → 1.1.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/README.md +47 -0
- data/lib/integration_tests_rails/capybara/dsl.rb +26 -0
- data/lib/integration_tests_rails/capybara/helpers.rb +1 -0
- data/lib/integration_tests_rails/capybara/remote.rb +4 -0
- data/lib/integration_tests_rails/capybara/util.rb +13 -5
- data/lib/integration_tests_rails/configuration.rb +5 -1
- data/lib/integration_tests_rails/istanbul/instrumenter.rb +25 -7
- data/lib/integration_tests_rails/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a76f78f94ceebb76953e47c1d45a2e4b39f3f5260e3238721861bf6102522690
|
|
4
|
+
data.tar.gz: 2e2c200ec76728898e642ece116eb1ad163490f55a6a855c7dc8a96dc7f03cb1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cb4e8d66540dd7b3a854af21a5fad11c439fc3c8a31b8aa96f7a9dcab74e9a2f97c6d30478201b9e2afb057121f5ca89a8c36a1019a0548e93d241c4e96171c5
|
|
7
|
+
data.tar.gz: 52227cad3110ba38c617a671614419fb321a0f58a05249ed5957a5d46f2881c0ff3cf09a3e792f28e8b9881b073be97303a53329c2f73a8970e1bce5d3061347
|
data/README.md
CHANGED
|
@@ -13,6 +13,8 @@ This gem is designed to facilitate integration testing in Ruby on Rails applicat
|
|
|
13
13
|
|
|
14
14
|
## Getting Started
|
|
15
15
|
|
|
16
|
+
Gem is easy to setup.
|
|
17
|
+
|
|
16
18
|
### Installation
|
|
17
19
|
|
|
18
20
|
Add this line to your Rails application's Gemfile:
|
|
@@ -64,6 +66,8 @@ IntegrationTestsRails.setup do |config|
|
|
|
64
66
|
config.max_server_retries = 1000 # Before running the tests, Cuprite starts a server to communicate with Chrome. This sets the maximum number of retries to connect to that server.
|
|
65
67
|
config.puma_threads = '1:1' # Number of threads for the Puma server used by Cuprite.
|
|
66
68
|
config.remote = false # Whether to use a remote Chrome instance.
|
|
69
|
+
config.retry_attempts = 1 # Number of times to retry a test if an example fails inside a retry_on_fail block.
|
|
70
|
+
config.retry_sleep_duration = 0 # Number of seconds to wait between retries inside a retry_on_fail block.
|
|
67
71
|
config.server_host = '0.0.0.0' # Host for the Puma server used by Cuprite.
|
|
68
72
|
config.server_port = nil # Port for the Puma server used by Cuprite.
|
|
69
73
|
config.source_dir = 'app/javascript' # Directory containing the JavaScript files to be instrumented.
|
|
@@ -76,6 +80,14 @@ end
|
|
|
76
80
|
|
|
77
81
|
## Unit Testing JavaScript Code
|
|
78
82
|
|
|
83
|
+
This feature is **EXPERIMENTAL** and may change in future releases. It allows you to write unit tests for JavaScript code using RSpec and Capybara. To enable the feature, one must set the `experimental_features` configuration option to `true`:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
IntegrationTestsRails.setup do |config|
|
|
87
|
+
config.experimental_features = true
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
79
91
|
### Usage
|
|
80
92
|
|
|
81
93
|
To unit test JavaScript code, the gem will visit a test HTML page. By default, it only renders a complete HTML page that also loads importmap-supporting JavaScript code to set up the environment for testing.
|
|
@@ -211,12 +223,42 @@ In such cases where `script` is an array, the `result` component will contain th
|
|
|
211
223
|
|
|
212
224
|
Refer to [Cuprite](https://github.com/rubycdp/cuprite) and [Capybara](https://github.com/teamcapybara/capybara). Use them as normally in integration tests.
|
|
213
225
|
|
|
226
|
+
## Retrying Flaky Tests
|
|
227
|
+
|
|
228
|
+
In practice, this is frowned upon. However, in such cases where there exists a flaky test (e.g. network issues, timing issues, third-party services respnsiveness, etc.), the gem provides a way to retry tests through `retry_on_fail`.
|
|
229
|
+
|
|
230
|
+
```ruby
|
|
231
|
+
RSpec.describe 'Flaky Test', type: :feature do
|
|
232
|
+
describe 'Flaky Behavior' do
|
|
233
|
+
it 'must succeed' do
|
|
234
|
+
visit '/form_with_complex_javascript'
|
|
235
|
+
retry_on_fail do
|
|
236
|
+
check 'Checkbox that executes scripts with animations'
|
|
237
|
+
expect(page).to have_css('div#animated-element', visible: :visible, wait: 5)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
expect(page).to have_content('Complex Modal Opened')
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
The above will retry the test if the browser was not able to find the checkbox or the page was not able to find the animated element. The number of retries and sleep duration between retries can be configured through `retry_attempts` and `retry_sleep_duration` configuration options respectively. The default number of retries is 1 and the default sleep duration is 0 seconds. You can also pass them as arguments:
|
|
247
|
+
|
|
248
|
+
```ruby
|
|
249
|
+
retry_on_fail(retry_attempts: 3, retry_sleep_duration: 1) do
|
|
250
|
+
expect(page).to have_css('div#animated-element', visible: :visible, wait: 5)
|
|
251
|
+
end
|
|
252
|
+
```
|
|
253
|
+
|
|
214
254
|
## JavaScript Coverage Reports
|
|
215
255
|
|
|
216
256
|
After the tests (successful, failed or cancelled), coverage reports will be generated in `coverage/javascript` by default.
|
|
217
257
|
|
|
218
258
|
## Example Setups
|
|
219
259
|
|
|
260
|
+
The following are working examples.
|
|
261
|
+
|
|
220
262
|
### Using Docker Desktop
|
|
221
263
|
|
|
222
264
|
If you want to use the [Alpine Chrome](https://hub.docker.com/r/zenika/alpine-chrome) image, this is definitely possible.
|
|
@@ -256,6 +298,11 @@ IntegrationTestsRails.setup do |config|
|
|
|
256
298
|
end
|
|
257
299
|
```
|
|
258
300
|
|
|
301
|
+
## TODO
|
|
302
|
+
|
|
303
|
+
1. JavaScript unit testing needs to have a way to define the JavaScript loader in the spec file itself instead of in `IntegrationTestsRails.configuration.tests_page_html`.
|
|
304
|
+
2. There is no graceful way to stop failing CDNs from being retried. Find a way to wait for them to complete loading. It should respect the `IntegrationTestsRails.configuration.timeout` setting.
|
|
305
|
+
|
|
259
306
|
## Contributing
|
|
260
307
|
|
|
261
308
|
1. Fork the repository.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module IntegrationTestsRails
|
|
4
|
+
module Capybara
|
|
5
|
+
# This module provides the main DSL for writing integration tests with Capybara.
|
|
6
|
+
module Dsl
|
|
7
|
+
def retry_on_fail(attempts: nil, sleep_duration: nil)
|
|
8
|
+
config = IntegrationTestsRails.configuration
|
|
9
|
+
attempts ||= config.retry_attempts
|
|
10
|
+
sleep_duration ||= config.retry_sleep_duration
|
|
11
|
+
counter = 0
|
|
12
|
+
|
|
13
|
+
begin
|
|
14
|
+
yield
|
|
15
|
+
rescue RSpec::Expectations::ExpectationNotMetError, Capybara::ElementNotFound => e
|
|
16
|
+
counter += 1
|
|
17
|
+
Util.log("Attempt #{counter} for #{RSpec.current_example.full_description} failed!")
|
|
18
|
+
raise e if counter >= attempts
|
|
19
|
+
|
|
20
|
+
sleep(sleep_duration) if sleep_duration.positive?
|
|
21
|
+
retry
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -34,6 +34,10 @@ module IntegrationTestsRails
|
|
|
34
34
|
timeout = config.timeout
|
|
35
35
|
options = {
|
|
36
36
|
window_size: config.window_size,
|
|
37
|
+
browser_options: {
|
|
38
|
+
'no-sandbox': nil,
|
|
39
|
+
'disable-dev-shm-usage': nil
|
|
40
|
+
},
|
|
37
41
|
url: config.chrome_url,
|
|
38
42
|
timeout: timeout,
|
|
39
43
|
process_timeout: timeout
|
|
@@ -16,12 +16,15 @@ module IntegrationTestsRails
|
|
|
16
16
|
def ensure_server_ready(context)
|
|
17
17
|
return if @server_ready
|
|
18
18
|
|
|
19
|
-
config = IntegrationTestsRails.configuration
|
|
20
19
|
log "Waiting for server on #{::Capybara.app_host.presence || 'localhost'} to start..."
|
|
21
|
-
|
|
22
|
-
server_retries =
|
|
20
|
+
configuration = IntegrationTestsRails.configuration
|
|
21
|
+
server_retries = configuration.max_server_retries
|
|
23
22
|
server_retries.times do |attempt|
|
|
24
|
-
|
|
23
|
+
if configuration.experimental_features
|
|
24
|
+
context.visit('/tests')
|
|
25
|
+
else
|
|
26
|
+
context.visit('/')
|
|
27
|
+
end
|
|
25
28
|
@server_ready = true
|
|
26
29
|
log 'Server is ready!'
|
|
27
30
|
break
|
|
@@ -33,16 +36,21 @@ module IntegrationTestsRails
|
|
|
33
36
|
|
|
34
37
|
def configure_rspec
|
|
35
38
|
RSpec.configure do |config|
|
|
39
|
+
config.include Dsl, type: :feature
|
|
36
40
|
config.before(:each, type: :feature) do
|
|
37
41
|
::Capybara.current_driver = ::Capybara.javascript_driver
|
|
38
42
|
IntegrationTestsRails::Capybara::Util.ensure_server_ready(self)
|
|
39
43
|
end
|
|
40
44
|
|
|
41
|
-
|
|
45
|
+
if IntegrationTestsRails.configuration.experimental_features
|
|
46
|
+
config.include(Helper, type: :feature, unit: true)
|
|
47
|
+
end
|
|
42
48
|
end
|
|
43
49
|
end
|
|
44
50
|
|
|
45
51
|
def configure_routes
|
|
52
|
+
return unless IntegrationTestsRails.configuration.experimental_features
|
|
53
|
+
|
|
46
54
|
app = Rails.application
|
|
47
55
|
routes = app.routes
|
|
48
56
|
# Use append and let Rails handle finalization automatically
|
|
@@ -39,16 +39,20 @@ module IntegrationTestsRails
|
|
|
39
39
|
|
|
40
40
|
attr_accessor :source_dir, :output_dir, :backup_dir, :coverage_path, :wait_time, :remote,
|
|
41
41
|
:chrome_url, :tests_page_html, :window_size, :max_server_retries,
|
|
42
|
-
:verbose, :timeout, :server_host, :server_port, :puma_threads
|
|
42
|
+
:verbose, :timeout, :server_host, :server_port, :puma_threads, :experimental_features,
|
|
43
|
+
:retry_attempts, :retry_sleep_duration
|
|
43
44
|
|
|
44
45
|
def initialize
|
|
45
46
|
@backup_dir = 'tmp/js_backup'
|
|
46
47
|
@chrome_url = nil
|
|
47
48
|
@coverage_path = 'coverage/nyc'
|
|
49
|
+
@experimental_features = false
|
|
48
50
|
@max_server_retries = 1000
|
|
49
51
|
@output_dir = 'tmp/instrumented_js'
|
|
50
52
|
@puma_threads = '1:1'
|
|
51
53
|
@remote = false
|
|
54
|
+
@retry_attempts = 1
|
|
55
|
+
@retry_sleep_duration = 0
|
|
52
56
|
@server_host = '0.0.0.0' # rubocop:disable Style/IpAddresses
|
|
53
57
|
@server_port = nil
|
|
54
58
|
@source_dir = 'app/javascript'
|
|
@@ -32,27 +32,45 @@ module IntegrationTestsRails
|
|
|
32
32
|
|
|
33
33
|
def instrument_file(file)
|
|
34
34
|
config = IntegrationTestsRails.configuration
|
|
35
|
-
|
|
36
35
|
relative_path = Pathname.new(file).relative_path_from(config.source_path)
|
|
37
36
|
output_file = config.output_path.join(relative_path)
|
|
38
37
|
|
|
39
38
|
FileUtils.mkdir_p(output_file.dirname)
|
|
40
39
|
|
|
41
|
-
# Use Node.js to instrument the file
|
|
42
40
|
code = File.read(file)
|
|
43
|
-
|
|
41
|
+
instrumented = instrument_code_with_istanbul(code, file)
|
|
42
|
+
|
|
43
|
+
write_instrumented_file(output_file, instrumented, relative_path)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def instrument_code_with_istanbul(code, file)
|
|
49
|
+
require 'tempfile'
|
|
50
|
+
Tempfile.create(['code', '.js']) do |temp_code|
|
|
51
|
+
temp_code.write(code)
|
|
52
|
+
temp_code.flush
|
|
53
|
+
|
|
54
|
+
js_command = build_istanbul_command(temp_code.path, file)
|
|
55
|
+
`node -e #{Shellwords.escape(js_command)}`.strip
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def build_istanbul_command(code_path, file)
|
|
60
|
+
escaped_code_path = JSON.generate(code_path)
|
|
44
61
|
escaped_file = JSON.generate(file.to_s)
|
|
45
62
|
|
|
46
|
-
|
|
63
|
+
<<~JS
|
|
64
|
+
const fs = require('fs');
|
|
47
65
|
const { createInstrumenter } = require('istanbul-lib-instrument');
|
|
48
66
|
const instrumenter = createInstrumenter({ esModules: true, compact: false });
|
|
49
|
-
const code = #{
|
|
67
|
+
const code = fs.readFileSync(#{escaped_code_path}, 'utf8');
|
|
50
68
|
const filename = #{escaped_file};
|
|
51
69
|
console.log(instrumenter.instrumentSync(code, filename));
|
|
52
70
|
JS
|
|
71
|
+
end
|
|
53
72
|
|
|
54
|
-
|
|
55
|
-
|
|
73
|
+
def write_instrumented_file(output_file, instrumented, relative_path)
|
|
56
74
|
if $CHILD_STATUS.success?
|
|
57
75
|
File.write(output_file, instrumented)
|
|
58
76
|
else
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: integration-tests-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tien
|
|
@@ -90,6 +90,7 @@ files:
|
|
|
90
90
|
- lib/integration-tests-rails.rb
|
|
91
91
|
- lib/integration_tests_rails.rb
|
|
92
92
|
- lib/integration_tests_rails/capybara.rb
|
|
93
|
+
- lib/integration_tests_rails/capybara/dsl.rb
|
|
93
94
|
- lib/integration_tests_rails/capybara/helpers.rb
|
|
94
95
|
- lib/integration_tests_rails/capybara/local.rb
|
|
95
96
|
- lib/integration_tests_rails/capybara/remote.rb
|