cypress-on-rails 1.18.0 → 1.20.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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/claude-code-review.yml +57 -0
  3. data/.github/workflows/claude.yml +50 -0
  4. data/CHANGELOG.md +399 -98
  5. data/README.md +139 -19
  6. data/RELEASING.md +200 -0
  7. data/Rakefile +1 -4
  8. data/cypress-on-rails.gemspec +3 -2
  9. data/docs/BEST_PRACTICES.md +678 -0
  10. data/docs/DX_IMPROVEMENTS.md +163 -0
  11. data/docs/PLAYWRIGHT_GUIDE.md +554 -0
  12. data/docs/RELEASE.md +124 -0
  13. data/docs/TROUBLESHOOTING.md +351 -0
  14. data/docs/VCR_GUIDE.md +499 -0
  15. data/lib/cypress_on_rails/command_executor.rb +24 -0
  16. data/lib/cypress_on_rails/configuration.rb +32 -0
  17. data/lib/cypress_on_rails/railtie.rb +7 -0
  18. data/lib/cypress_on_rails/server.rb +258 -0
  19. data/lib/cypress_on_rails/state_reset_middleware.rb +58 -0
  20. data/lib/cypress_on_rails/version.rb +1 -1
  21. data/lib/generators/cypress_on_rails/install_generator.rb +2 -2
  22. data/lib/generators/cypress_on_rails/templates/config/initializers/cypress_on_rails.rb.erb +14 -2
  23. data/lib/generators/cypress_on_rails/templates/spec/cypress/e2e/rails_examples/using_factory_bot.cy.js +2 -2
  24. data/lib/generators/cypress_on_rails/templates/spec/cypress/e2e/rails_examples/using_scenarios.cy.js +1 -1
  25. data/lib/generators/cypress_on_rails/templates/spec/cypress/support/on-rails.js +1 -1
  26. data/lib/tasks/cypress.rake +33 -0
  27. data/rakelib/release.rake +80 -0
  28. data/rakelib/task_helpers.rb +23 -0
  29. data/rakelib/update_changelog.rake +63 -0
  30. data/spec/cypress_on_rails/configuration_spec.rb +6 -0
  31. data/spec/generators/install_generator_spec.rb +222 -0
  32. metadata +34 -4
@@ -0,0 +1,258 @@
1
+ require 'socket'
2
+ require 'timeout'
3
+ require 'fileutils'
4
+ require 'net/http'
5
+ require 'cypress_on_rails/configuration'
6
+
7
+ module CypressOnRails
8
+ class Server
9
+ attr_reader :host, :port, :framework, :install_folder
10
+
11
+ def initialize(options = {})
12
+ config = CypressOnRails.configuration
13
+
14
+ @framework = options[:framework] || :cypress
15
+ @host = options[:host] || config.server_host
16
+ @port = options[:port] || config.server_port || find_available_port
17
+ @port = @port.to_i if @port
18
+ @install_folder = options[:install_folder] || config.install_folder || detect_install_folder
19
+ @transactional = options.fetch(:transactional, config.transactional_server)
20
+ # Process management: track PID and process group for proper cleanup
21
+ @server_pid = nil
22
+ @server_pgid = nil
23
+ end
24
+
25
+ def open
26
+ start_server do
27
+ run_command(open_command, "Opening #{framework} test runner")
28
+ end
29
+ end
30
+
31
+ def run
32
+ start_server do
33
+ result = run_command(run_command_args, "Running #{framework} tests")
34
+ exit(result ? 0 : 1)
35
+ end
36
+ end
37
+
38
+ def init
39
+ ensure_install_folder_exists
40
+ puts "#{framework.to_s.capitalize} configuration initialized at #{install_folder}"
41
+ end
42
+
43
+ private
44
+
45
+ def detect_install_folder
46
+ # Check common locations for cypress/playwright installation
47
+ possible_folders = ['e2e', 'spec/e2e', 'spec/cypress', 'spec/playwright', 'cypress', 'playwright']
48
+ folder = possible_folders.find { |f| File.exist?(File.expand_path(f)) }
49
+ folder || 'e2e'
50
+ end
51
+
52
+ def ensure_install_folder_exists
53
+ unless File.exist?(install_folder)
54
+ puts "Creating #{install_folder} directory..."
55
+ FileUtils.mkdir_p(install_folder)
56
+ end
57
+ end
58
+
59
+ def find_available_port
60
+ server = TCPServer.new('127.0.0.1', 0)
61
+ port = server.addr[1]
62
+ server.close
63
+ port
64
+ end
65
+
66
+ def start_server(&block)
67
+ config = CypressOnRails.configuration
68
+
69
+ run_hook(config.before_server_start)
70
+
71
+ ENV['CYPRESS'] = '1'
72
+ ENV['RAILS_ENV'] = 'test'
73
+
74
+ server_pid = spawn_server
75
+
76
+ begin
77
+ wait_for_server
78
+ run_hook(config.after_server_start)
79
+
80
+ puts "Rails server started on #{base_url}"
81
+
82
+ if @transactional && defined?(ActiveRecord::Base)
83
+ ActiveRecord::Base.connection.begin_transaction(joinable: false)
84
+ run_hook(config.after_transaction_start)
85
+ end
86
+
87
+ yield
88
+
89
+ ensure
90
+ run_hook(config.before_server_stop)
91
+
92
+ if @transactional && defined?(ActiveRecord::Base)
93
+ ActiveRecord::Base.connection.rollback_transaction if ActiveRecord::Base.connection.transaction_open?
94
+ end
95
+
96
+ stop_server(server_pid)
97
+ ENV.delete('CYPRESS')
98
+ end
99
+ end
100
+
101
+ def spawn_server
102
+ rails_args = if File.exist?('bin/rails')
103
+ ['bin/rails']
104
+ else
105
+ ['bundle', 'exec', 'rails']
106
+ end
107
+
108
+ server_args = rails_args + ['server', '-p', port.to_s, '-b', host]
109
+
110
+ puts "Starting Rails server: #{server_args.join(' ')}"
111
+
112
+ @server_pid = spawn(*server_args, out: $stdout, err: $stderr, pgroup: true)
113
+ begin
114
+ @server_pgid = Process.getpgid(@server_pid)
115
+ rescue Errno::ESRCH => e
116
+ # Edge case: process terminated before we could get pgid
117
+ # This is OK - send_term_signal will fall back to single-process kill
118
+ CypressOnRails.configuration.logger.warn("Process #{@server_pid} terminated immediately after spawn: #{e.message}")
119
+ @server_pgid = nil
120
+ end
121
+ @server_pid
122
+ end
123
+
124
+ def wait_for_server(timeout = 30)
125
+ Timeout.timeout(timeout) do
126
+ loop do
127
+ break if server_responding?
128
+ sleep 0.1
129
+ end
130
+ end
131
+ rescue Timeout::Error
132
+ raise "Rails server failed to start on #{host}:#{port} after #{timeout} seconds"
133
+ end
134
+
135
+ def server_responding?
136
+ config = CypressOnRails.configuration
137
+ readiness_path = config.server_readiness_path || '/'
138
+ timeout = config.server_readiness_timeout || 5
139
+ uri = URI("http://#{host}:#{port}#{readiness_path}")
140
+
141
+ response = Net::HTTP.start(uri.host, uri.port, open_timeout: timeout, read_timeout: timeout) do |http|
142
+ http.get(uri.path)
143
+ end
144
+
145
+ # Accept 200-399 (success and redirects), reject 404 and 5xx
146
+ # 3xx redirects are considered "ready" because the server is responding correctly
147
+ (200..399).cover?(response.code.to_i)
148
+ rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL, Errno::ETIMEDOUT, SocketError,
149
+ Net::OpenTimeout, Net::ReadTimeout, Net::HTTPBadResponse
150
+ false
151
+ end
152
+
153
+ def stop_server(pid)
154
+ return unless pid
155
+
156
+ puts "Stopping Rails server (PID: #{pid})"
157
+ send_term_signal(pid)
158
+
159
+ begin
160
+ Timeout.timeout(10) do
161
+ Process.wait(pid)
162
+ end
163
+ rescue Timeout::Error
164
+ CypressOnRails.configuration.logger.warn("Server did not terminate after TERM signal, sending KILL")
165
+ safe_kill_process('KILL', pid)
166
+ Process.wait(pid) rescue Errno::ESRCH
167
+ end
168
+ rescue Errno::ESRCH
169
+ # Process already terminated
170
+ end
171
+
172
+ def send_term_signal(pid)
173
+ if @server_pgid && process_exists?(pid)
174
+ Process.kill('TERM', -@server_pgid)
175
+ else
176
+ safe_kill_process('TERM', pid)
177
+ end
178
+ rescue Errno::ESRCH, Errno::EPERM => e
179
+ CypressOnRails.configuration.logger.warn("Failed to kill process group #{@server_pgid}: #{e.message}, trying single process")
180
+ safe_kill_process('TERM', pid)
181
+ end
182
+
183
+ def process_exists?(pid)
184
+ return false unless pid
185
+ Process.kill(0, pid)
186
+ true
187
+ rescue Errno::ESRCH, Errno::EPERM
188
+ false
189
+ end
190
+
191
+ def safe_kill_process(signal, pid)
192
+ Process.kill(signal, pid) if pid
193
+ rescue Errno::ESRCH, Errno::EPERM
194
+ # Process already terminated or permission denied
195
+ end
196
+
197
+ def base_url
198
+ "http://#{host}:#{port}"
199
+ end
200
+
201
+ def open_command
202
+ case framework
203
+ when :cypress
204
+ if command_exists?('yarn')
205
+ ['yarn', 'cypress', 'open', '--project', install_folder, '--config', "baseUrl=#{base_url}"]
206
+ elsif command_exists?('npx')
207
+ ['npx', 'cypress', 'open', '--project', install_folder, '--config', "baseUrl=#{base_url}"]
208
+ else
209
+ ['cypress', 'open', '--project', install_folder, '--config', "baseUrl=#{base_url}"]
210
+ end
211
+ when :playwright
212
+ if command_exists?('yarn')
213
+ ['yarn', 'playwright', 'test', '--ui']
214
+ elsif command_exists?('npx')
215
+ ['npx', 'playwright', 'test', '--ui']
216
+ else
217
+ ['playwright', 'test', '--ui']
218
+ end
219
+ end
220
+ end
221
+
222
+ def run_command_args
223
+ case framework
224
+ when :cypress
225
+ if command_exists?('yarn')
226
+ ['yarn', 'cypress', 'run', '--project', install_folder, '--config', "baseUrl=#{base_url}"]
227
+ elsif command_exists?('npx')
228
+ ['npx', 'cypress', 'run', '--project', install_folder, '--config', "baseUrl=#{base_url}"]
229
+ else
230
+ ['cypress', 'run', '--project', install_folder, '--config', "baseUrl=#{base_url}"]
231
+ end
232
+ when :playwright
233
+ if command_exists?('yarn')
234
+ ['yarn', 'playwright', 'test']
235
+ elsif command_exists?('npx')
236
+ ['npx', 'playwright', 'test']
237
+ else
238
+ ['playwright', 'test']
239
+ end
240
+ end
241
+ end
242
+
243
+ def run_command(command_args, description)
244
+ puts "#{description}: #{command_args.join(' ')}"
245
+ system(*command_args)
246
+ end
247
+
248
+ def command_exists?(command)
249
+ system("which #{command} > /dev/null 2>&1")
250
+ end
251
+
252
+ def run_hook(hook)
253
+ if hook && hook.respond_to?(:call)
254
+ hook.call
255
+ end
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,58 @@
1
+ module CypressOnRails
2
+ class StateResetMiddleware
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ if env['PATH_INFO'] == '/__cypress__/reset_state' || env['PATH_INFO'] == '/cypress_rails_reset_state'
9
+ reset_application_state
10
+ [200, { 'Content-Type' => 'text/plain' }, ['State reset completed']]
11
+ else
12
+ @app.call(env)
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def reset_application_state
19
+ config = CypressOnRails.configuration
20
+
21
+ # Default state reset actions
22
+ if defined?(DatabaseCleaner)
23
+ DatabaseCleaner.clean_with(:truncation)
24
+ elsif defined?(ActiveRecord::Base)
25
+ connection = ActiveRecord::Base.connection
26
+
27
+ # Use disable_referential_integrity if available for safer table clearing
28
+ if connection.respond_to?(:disable_referential_integrity)
29
+ connection.disable_referential_integrity do
30
+ connection.tables.each do |table|
31
+ next if table == 'schema_migrations' || table == 'ar_internal_metadata'
32
+ connection.execute("DELETE FROM #{connection.quote_table_name(table)}")
33
+ end
34
+ end
35
+ else
36
+ # Fallback to regular deletion with proper table name quoting
37
+ connection.tables.each do |table|
38
+ next if table == 'schema_migrations' || table == 'ar_internal_metadata'
39
+ connection.execute("DELETE FROM #{connection.quote_table_name(table)}")
40
+ end
41
+ end
42
+ end
43
+
44
+ # Clear Rails cache
45
+ Rails.cache.clear if defined?(Rails) && Rails.cache
46
+
47
+ # Reset any class-level state
48
+ ActiveSupport::Dependencies.clear if defined?(ActiveSupport::Dependencies)
49
+
50
+ # Run after_state_reset hook after cleanup is complete
51
+ run_hook(config.after_state_reset)
52
+ end
53
+
54
+ def run_hook(hook)
55
+ hook.call if hook && hook.respond_to?(:call)
56
+ end
57
+ end
58
+ end
@@ -1,3 +1,3 @@
1
1
  module CypressOnRails
2
- VERSION = '1.18.0'.freeze
2
+ VERSION = '1.20.0'.freeze
3
3
  end
@@ -42,8 +42,8 @@ module CypressOnRails
42
42
 
43
43
  def add_initial_files
44
44
  template "config/initializers/cypress_on_rails.rb.erb", "config/initializers/cypress_on_rails.rb"
45
- template "spec/e2e/e2e_helper.rb.erb", "#{options.install_folder}/#{options.framework}/e2e_helper.rb"
46
- directory 'spec/e2e/app_commands', "#{options.install_folder}/#{options.framework}/app_commands"
45
+ template "spec/e2e/e2e_helper.rb.erb", "#{options.install_folder}/e2e_helper.rb"
46
+ directory 'spec/e2e/app_commands', "#{options.install_folder}/app_commands"
47
47
  if options.framework == 'cypress'
48
48
  copy_file "spec/cypress/support/on-rails.js", "#{options.install_folder}/cypress/support/on-rails.js"
49
49
  directory 'spec/cypress/e2e/rails_examples', "#{options.install_folder}/cypress/e2e/rails_examples"
@@ -1,7 +1,7 @@
1
1
  if defined?(CypressOnRails)
2
2
  CypressOnRails.configure do |c|
3
3
  c.api_prefix = "<%= options.api_prefix %>"
4
- c.install_folder = File.expand_path("#{__dir__}/../../<%= options.install_folder %>/<%= options.framework %>")
4
+ c.install_folder = File.expand_path("#{__dir__}/../../<%= options.install_folder %>")
5
5
  # WARNING!! CypressOnRails can execute arbitrary ruby code
6
6
  # please use with extra caution if enabling on hosted servers or starting your local server on 0.0.0.0
7
7
  c.use_middleware = !Rails.env.production?
@@ -12,9 +12,21 @@ if defined?(CypressOnRails)
12
12
  <% unless options.experimental %># <% end %> c.vcr_options = {
13
13
  <% unless options.experimental %># <% end %> hook_into: :webmock,
14
14
  <% unless options.experimental %># <% end %> default_cassette_options: { record: :once },
15
- <% unless options.experimental %># <% end %> cassette_library_dir: File.expand_path("#{__dir__}/../../<%= options.install_folder %>/<%= options.framework %>/fixtures/vcr_cassettes")
15
+ <% unless options.experimental %># <% end %> cassette_library_dir: File.expand_path("#{__dir__}/../../<%= options.install_folder %>/fixtures/vcr_cassettes")
16
16
  <% unless options.experimental %># <% end %> }
17
17
  c.logger = Rails.logger
18
+
19
+ # Server configuration for rake tasks (cypress:open, cypress:run, playwright:open, playwright:run)
20
+ # c.server_host = 'localhost' # or use ENV['CYPRESS_RAILS_HOST']
21
+ # c.server_port = 3001 # or use ENV['CYPRESS_RAILS_PORT']
22
+ # c.transactional_server = true # Enable automatic transaction rollback between tests
23
+
24
+ # Server lifecycle hooks for rake tasks
25
+ # c.before_server_start = -> { DatabaseCleaner.clean_with(:truncation) }
26
+ # c.after_server_start = -> { puts "Test server started on port #{CypressOnRails.configuration.server_port}" }
27
+ # c.after_transaction_start = -> { Rails.application.load_seed }
28
+ # c.after_state_reset = -> { Rails.cache.clear }
29
+ # c.before_server_stop = -> { puts "Stopping test server..." }
18
30
 
19
31
  # If you want to enable a before_request logic, such as authentication, logging, sending metrics, etc.
20
32
  # Refer to https://www.rubydoc.info/gems/rack/Rack/Request for the `request` argument.
@@ -9,7 +9,7 @@ describe('Rails using factory bot examples', function() {
9
9
  ])
10
10
  cy.visit('/')
11
11
  cy.get('table').find('tbody').should(($tbody) => {
12
- // clean should of removed these from other tests
12
+ // clean should have removed these from other tests
13
13
  expect($tbody).not.to.contain('Hello World')
14
14
 
15
15
  expect($tbody).to.contain('Good bye Mars')
@@ -23,7 +23,7 @@ describe('Rails using factory bot examples', function() {
23
23
  ])
24
24
  cy.visit('/')
25
25
  cy.get('table').find('tbody').should(($tbody) => {
26
- // clean should of removed these from other tests
26
+ // clean should have removed these from other tests
27
27
  expect($tbody).to.contain('Hello World')
28
28
  expect($tbody).not.to.contain('Good bye Mars')
29
29
  })
@@ -7,7 +7,7 @@ describe('Rails using scenarios examples', function() {
7
7
  cy.appScenario('basic')
8
8
  cy.visit('/')
9
9
  cy.get('table').find('tbody').should(($tbody) => {
10
- // clean should of removed these from other tests
10
+ // clean should have removed these from other tests
11
11
  expect($tbody).not.to.contain('Good bye Mars')
12
12
  expect($tbody).not.to.contain('Hello World')
13
13
 
@@ -1,4 +1,4 @@
1
- // CypressOnRails: dont remove these command
1
+ // CypressOnRails: don't remove these commands
2
2
  Cypress.Commands.add('appCommands', function (body) {
3
3
  Object.keys(body).forEach(key => body[key] === undefined ? delete body[key] : {});
4
4
  const log = Cypress.log({ name: "APP", message: body, autoEnd: false })
@@ -0,0 +1,33 @@
1
+ namespace :cypress do
2
+ desc "Open Cypress test runner UI"
3
+ task :open => :environment do
4
+ require 'cypress_on_rails/server'
5
+ CypressOnRails::Server.new.open
6
+ end
7
+
8
+ desc "Run Cypress tests in headless mode"
9
+ task :run => :environment do
10
+ require 'cypress_on_rails/server'
11
+ CypressOnRails::Server.new.run
12
+ end
13
+
14
+ desc "Initialize Cypress configuration"
15
+ task :init => :environment do
16
+ require 'cypress_on_rails/server'
17
+ CypressOnRails::Server.new.init
18
+ end
19
+ end
20
+
21
+ namespace :playwright do
22
+ desc "Open Playwright test runner UI"
23
+ task :open => :environment do
24
+ require 'cypress_on_rails/server'
25
+ CypressOnRails::Server.new(framework: :playwright).open
26
+ end
27
+
28
+ desc "Run Playwright tests in headless mode"
29
+ task :run => :environment do
30
+ require 'cypress_on_rails/server'
31
+ CypressOnRails::Server.new(framework: :playwright).run
32
+ end
33
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler"
4
+ require_relative "task_helpers"
5
+
6
+ class RaisingMessageHandler
7
+ def add_error(error)
8
+ raise error
9
+ end
10
+ end
11
+
12
+ # rubocop:disable Metrics/BlockLength
13
+
14
+ desc("Releases the gem using the given version.
15
+
16
+ IMPORTANT: the gem version must be in valid rubygem format (no dashes).
17
+
18
+ This task depends on the gem-release (ruby gem) which is installed via `bundle install`
19
+
20
+ 1st argument: The new version in rubygem format (no dashes). Pass no argument to
21
+ automatically perform a patch version bump.
22
+ 2nd argument: Perform a dry run by passing 'true' as a second argument.
23
+
24
+ Note, accept defaults for rubygems options. Script will pause to get 2FA tokens.
25
+
26
+ Example: `rake release[2.1.0,false]`")
27
+ task :release, %i[gem_version dry_run] do |_t, args|
28
+ include CypressOnRails::TaskHelpers
29
+
30
+ # Check if there are uncommitted changes
31
+ unless `git status --porcelain`.strip.empty?
32
+ raise "You have uncommitted changes. Please commit or stash them before releasing."
33
+ end
34
+
35
+ args_hash = args.to_hash
36
+
37
+ is_dry_run = args_hash[:dry_run] == 'true'
38
+
39
+ gem_version = args_hash.fetch(:gem_version, "")
40
+
41
+ # See https://github.com/svenfuchs/gem-release
42
+ sh_in_dir(gem_root, "git pull --rebase")
43
+ sh_in_dir(gem_root, "gem bump --no-commit --file lib/cypress_on_rails/version.rb #{gem_version.strip.empty? ? '' : %(--version #{gem_version})}")
44
+
45
+ # Read the actual version from the file after bump
46
+ load File.expand_path("../lib/cypress_on_rails/version.rb", __dir__)
47
+ actual_version = CypressOnRails::VERSION
48
+
49
+ # Update Gemfile.lock files
50
+ sh_in_dir(gem_root, "bundle install")
51
+
52
+ unless is_dry_run
53
+ # Commit the version bump
54
+ sh_in_dir(gem_root, "git add lib/cypress_on_rails/version.rb")
55
+ sh_in_dir(gem_root, "git commit -m \"Release v#{actual_version}\"")
56
+
57
+ # Tag the release
58
+ sh_in_dir(gem_root, "git tag v#{actual_version}")
59
+
60
+ # Push the commit and tag
61
+ sh_in_dir(gem_root, "git push && git push --tags")
62
+
63
+ # Release the new gem version
64
+ puts "Carefully add your OTP for Rubygems. If you get an error, run 'gem release' again."
65
+ sh_in_dir(gem_root, "gem release")
66
+ else
67
+ puts "DRY RUN: Would have committed, tagged v#{actual_version}, pushed, and released gem"
68
+ end
69
+
70
+ msg = <<~MSG
71
+ Once you have successfully published, run these commands to update CHANGELOG.md:
72
+
73
+ bundle exec rake update_changelog
74
+ git commit -a -m 'Update CHANGELOG.md'
75
+ git push
76
+ MSG
77
+ puts msg
78
+ end
79
+
80
+ # rubocop:enable Metrics/BlockLength
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CypressOnRails
4
+ module TaskHelpers
5
+ # Returns the root folder of the cypress-on-rails gem
6
+ def gem_root
7
+ File.expand_path("..", __dir__)
8
+ end
9
+
10
+ # Executes a string or an array of strings in a shell in the given directory
11
+ def sh_in_dir(dir, *shell_commands)
12
+ Dir.chdir(dir) do
13
+ # Without `with_unbundled_env`, running bundle in the child directories won't correctly
14
+ # update the Gemfile.lock
15
+ Bundler.with_unbundled_env do
16
+ shell_commands.flatten.each do |shell_command|
17
+ sh(shell_command.strip)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "English"
4
+
5
+ desc "Updates CHANGELOG.md inserting headers for the new version.
6
+
7
+ Argument: Git tag. Defaults to the latest tag."
8
+
9
+ task :update_changelog, %i[tag] do |_, args|
10
+ tag = args[:tag] || `git describe --tags --abbrev=0`.strip
11
+
12
+ # Remove 'v' prefix if present (e.g., v1.18.0 -> 1.18.0)
13
+ version = tag.start_with?('v') ? tag[1..-1] : tag
14
+ anchor = "[#{version}]"
15
+
16
+ changelog = File.read("CHANGELOG.md")
17
+
18
+ if changelog.include?(anchor)
19
+ puts "Tag #{version} is already documented in CHANGELOG.md, update manually if needed"
20
+ next
21
+ end
22
+
23
+ tag_date_output = `git show -s --format=%cs #{tag} 2>&1`
24
+ if $CHILD_STATUS.success?
25
+ tag_date = tag_date_output.split("\n").last.strip
26
+ else
27
+ abort("Failed to find tag #{tag}")
28
+ end
29
+
30
+ # After "## [Unreleased]", insert new version header
31
+ unreleased_section = "## [Unreleased]"
32
+ new_version_header = "\n\n## #{anchor} - #{tag_date}"
33
+
34
+ if changelog.include?(unreleased_section)
35
+ changelog.sub!(unreleased_section, "#{unreleased_section}#{new_version_header}")
36
+ else
37
+ abort("Could not find '## [Unreleased]' section in CHANGELOG.md")
38
+ end
39
+
40
+ # Find and update version comparison links at the bottom
41
+ # Pattern: [1.18.0]: https://github.com/shakacode/cypress-playwright-on-rails/compare/v1.17.0...v1.18.0
42
+ compare_link_prefix = "https://github.com/shakacode/cypress-playwright-on-rails/compare"
43
+
44
+ # Find the last version link to determine the previous version
45
+ last_version_match = changelog.match(/\[(\d+\.\d+\.\d+(?:\.\w+)?)\]:.*?compare\/v(\d+\.\d+\.\d+(?:\.\w+)?)\.\.\.v(\d+\.\d+\.\d+(?:\.\w+)?)/)
46
+
47
+ if last_version_match
48
+ last_version = last_version_match[1]
49
+ # Add new version link at the top of the version list
50
+ new_link = "#{anchor}: #{compare_link_prefix}/v#{last_version}...v#{version}"
51
+ # Insert after the "<!-- Version diff reference list -->" comment
52
+ changelog.sub!("<!-- Version diff reference list -->", "<!-- Version diff reference list -->\n#{new_link}")
53
+ else
54
+ puts "Warning: Could not find version comparison links. You may need to add the link manually."
55
+ end
56
+
57
+ File.write("CHANGELOG.md", changelog)
58
+ puts "Updated CHANGELOG.md with an entry for #{version}"
59
+ puts "\nNext steps:"
60
+ puts "1. Edit CHANGELOG.md to add release notes under the [#{version}] section"
61
+ puts "2. Move content from [Unreleased] to [#{version}] if applicable"
62
+ puts "3. Review and commit the changes"
63
+ end
@@ -10,6 +10,8 @@ RSpec.describe CypressOnRails::Configuration do
10
10
  expect(CypressOnRails.configuration.logger).to_not be_nil
11
11
  expect(CypressOnRails.configuration.before_request).to_not be_nil
12
12
  expect(CypressOnRails.configuration.vcr_options).to eq({})
13
+ expect(CypressOnRails.configuration.server_readiness_path).to eq('/')
14
+ expect(CypressOnRails.configuration.server_readiness_timeout).to eq(5)
13
15
  end
14
16
 
15
17
  it 'can be configured' do
@@ -22,6 +24,8 @@ RSpec.describe CypressOnRails::Configuration do
22
24
  config.logger = my_logger
23
25
  config.before_request = before_request_lambda
24
26
  config.vcr_options = { hook_into: :webmock }
27
+ config.server_readiness_path = '/health'
28
+ config.server_readiness_timeout = 10
25
29
  end
26
30
  expect(CypressOnRails.configuration.api_prefix).to eq('/api')
27
31
  expect(CypressOnRails.configuration.install_folder).to eq('my/path')
@@ -29,5 +33,7 @@ RSpec.describe CypressOnRails::Configuration do
29
33
  expect(CypressOnRails.configuration.logger).to eq(my_logger)
30
34
  expect(CypressOnRails.configuration.before_request).to eq(before_request_lambda)
31
35
  expect(CypressOnRails.configuration.vcr_options).to eq(hook_into: :webmock)
36
+ expect(CypressOnRails.configuration.server_readiness_path).to eq('/health')
37
+ expect(CypressOnRails.configuration.server_readiness_timeout).to eq(10)
32
38
  end
33
39
  end