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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fd9324031c217a8ec83714af3b5e4aa13970d8f7207329dec77cf7eac9e67f77
4
- data.tar.gz: 063caea1b6bb80fd619cb1fb9cde292140282b5d9943c5bd7d35cbdf11e1942f
3
+ metadata.gz: a76f78f94ceebb76953e47c1d45a2e4b39f3f5260e3238721861bf6102522690
4
+ data.tar.gz: 2e2c200ec76728898e642ece116eb1ad163490f55a6a855c7dc8a96dc7f03cb1
5
5
  SHA512:
6
- metadata.gz: 63ca3042ff7f05e8f785f79c396b644267fb3570599bb4a7f21ca6034a58dc75460ee71d396af671502e0721a012b63daeabe22683fc24b7f825951ddce5606a
7
- data.tar.gz: d5a1972b1930d8ae3d68b1c2498cc2a08d496d32d18af7ca1274b3e6999be06b8a6990e9b58988c364c492ecf0e997649a0e8fdd75fa94adc376c6ed61dd62ab
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
@@ -37,6 +37,7 @@ module IntegrationTestsRails
37
37
  let(:function) { nil }
38
38
 
39
39
  before do
40
+ # page.driver.wait_for_network_idle(timeout: IntegrationTestsRails.configuration.timeout)
40
41
  visit '/tests'
41
42
  result
42
43
  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 = config.max_server_retries
20
+ configuration = IntegrationTestsRails.configuration
21
+ server_retries = configuration.max_server_retries
23
22
  server_retries.times do |attempt|
24
- context.visit('/400')
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
- config.include(Helper, type: :feature, unit: true)
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
- escaped_code = JSON.generate(code)
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
- js_command = <<~JS
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 = #{escaped_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
- instrumented = `node -e #{Shellwords.escape(js_command)}`.strip
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IntegrationTestsRails
4
- VERSION = '1.0.8'
4
+ VERSION = '1.1.0'
5
5
  end
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.8
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