From 2d6c2c8b2fdd3c8a5de195a97b88b9a3a5fc1e5d Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Sat, 27 Sep 2025 17:59:10 -1000 Subject: [PATCH 1/3] Add cypress-rails compatible rake tasks and server lifecycle hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This enhancement brings cypress-rails functionality to cypress-playwright-on-rails: - Added rake tasks for cypress:open, cypress:run, playwright:open, playwright:run - Implemented automatic Rails server management with dynamic port selection - Added server lifecycle hooks (before_server_start, after_server_start, etc.) - Added transactional test mode for automatic database rollback - Added state reset middleware for /cypress_rails_reset_state endpoint - Support for CYPRESS_RAILS_HOST and CYPRESS_RAILS_PORT environment variables These changes make cypress-playwright-on-rails a more complete replacement for cypress-rails, providing the same developer-friendly test execution experience while maintaining all the existing cypress-on-rails functionality. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CHANGELOG.md | 17 ++ README.md | 61 ++++++ lib/cypress_on_rails/configuration.rb | 24 +++ lib/cypress_on_rails/railtie.rb | 7 + lib/cypress_on_rails/server.rb | 196 ++++++++++++++++++ .../state_reset_middleware.rb | 45 ++++ .../initializers/cypress_on_rails.rb.erb | 12 ++ lib/tasks/cypress.rake | 33 +++ 8 files changed, 395 insertions(+) create mode 100644 lib/cypress_on_rails/server.rb create mode 100644 lib/cypress_on_rails/state_reset_middleware.rb create mode 100644 lib/tasks/cypress.rake diff --git a/CHANGELOG.md b/CHANGELOG.md index bfc0b0c..98c49c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ This project adheres to [Semantic Versioning](https://semver.org/). --- +## [Unreleased] + +### Added +* **Rake tasks for test execution**: Added `cypress:open` and `cypress:run` rake tasks for seamless test execution, similar to cypress-rails functionality. Also added `playwright:open` and `playwright:run` tasks. +* **Server lifecycle hooks**: Added configuration hooks for test server management: + - `before_server_start`: Run code before Rails server starts + - `after_server_start`: Run code after Rails server is ready + - `after_transaction_start`: Run code after database transaction begins + - `after_state_reset`: Run code after application state is reset + - `before_server_stop`: Run code before Rails server stops +* **State reset endpoint**: Added `/cypress_rails_reset_state` and `/__cypress__/reset_state` endpoints for compatibility with cypress-rails +* **Transactional test mode**: Added support for automatic database transaction rollback between tests +* **Environment configuration**: Support for `CYPRESS_RAILS_HOST` and `CYPRESS_RAILS_PORT` environment variables +* **Automatic server management**: Test server automatically starts and stops with test execution + +--- + ## [1.18.0] — 2025-08-27 [Compare]: https://github.com/shakacode/cypress-playwright-on-rails/compare/v1.17.0...v1.18.0 diff --git a/README.md b/README.md index 473caff..db5a4cc 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,29 @@ Please use with extra caution if starting your local server on 0.0.0.0 or runnin Getting started on your local environment +### Using Rake Tasks (Recommended) + +The easiest way to run tests is using the provided rake tasks, which automatically manage the Rails server: + +```shell +# For Cypress +bin/rails cypress:open # Opens Cypress test runner UI +bin/rails cypress:run # Runs Cypress tests in headless mode + +# For Playwright +bin/rails playwright:open # Opens Playwright test runner UI +bin/rails playwright:run # Runs Playwright tests in headless mode +``` + +These tasks will: +- Start the Rails test server automatically +- Execute your tests +- Stop the server when done + +### Manual Server Management + +You can also manage the server manually: + ```shell # start rails CYPRESS=1 bin/rails server -p 5017 @@ -506,6 +529,44 @@ Consider VCR configuration in `cypress_helper.rb` to ignore hosts. All cassettes will be recorded and saved automatically, using the pattern `/graphql/` +## Server Hooks Configuration + +When using the rake tasks (`cypress:open`, `cypress:run`, `playwright:open`, `playwright:run`), you can configure lifecycle hooks to customize test server behavior: + +```ruby +CypressOnRails.configure do |c| + # Run code before Rails server starts + c.before_server_start = -> { + puts "Preparing test environment..." + } + + # Run code after Rails server is ready + c.after_server_start = -> { + puts "Server is ready for testing!" + } + + # Run code after database transaction begins (transactional mode only) + c.after_transaction_start = -> { + # Load seed data that should be rolled back after tests + } + + # Run code after application state is reset + c.after_state_reset = -> { + Rails.cache.clear + } + + # Run code before Rails server stops + c.before_server_stop = -> { + puts "Cleaning up test environment..." + } + + # Configure server settings + c.server_host = 'localhost' # or use ENV['CYPRESS_RAILS_HOST'] + c.server_port = 3001 # or use ENV['CYPRESS_RAILS_PORT'] + c.transactional_server = true # Enable automatic transaction rollback +end +``` + ## `before_request` configuration You may perform any custom action before running a CypressOnRails command, such as authentication, or sending metrics. Please set `before_request` as part of the CypressOnRails configuration. diff --git a/lib/cypress_on_rails/configuration.rb b/lib/cypress_on_rails/configuration.rb index 9ff0eda..a128052 100644 --- a/lib/cypress_on_rails/configuration.rb +++ b/lib/cypress_on_rails/configuration.rb @@ -10,6 +10,18 @@ class Configuration attr_accessor :before_request attr_accessor :logger attr_accessor :vcr_options + + # Server hooks for managing test lifecycle + attr_accessor :before_server_start + attr_accessor :after_server_start + attr_accessor :after_transaction_start + attr_accessor :after_state_reset + attr_accessor :before_server_stop + + # Server configuration + attr_accessor :server_host + attr_accessor :server_port + attr_accessor :transactional_server # Attributes for backwards compatibility def cypress_folder @@ -38,6 +50,18 @@ def reset self.before_request = -> (request) {} self.logger = Logger.new(STDOUT) self.vcr_options = {} + + # Server hooks + self.before_server_start = nil + self.after_server_start = nil + self.after_transaction_start = nil + self.after_state_reset = nil + self.before_server_stop = nil + + # Server configuration + self.server_host = ENV.fetch('CYPRESS_RAILS_HOST', 'localhost') + self.server_port = ENV.fetch('CYPRESS_RAILS_PORT', nil) + self.transactional_server = true end def tagged_logged diff --git a/lib/cypress_on_rails/railtie.rb b/lib/cypress_on_rails/railtie.rb index aaf808c..c9fe9d9 100644 --- a/lib/cypress_on_rails/railtie.rb +++ b/lib/cypress_on_rails/railtie.rb @@ -3,10 +3,17 @@ module CypressOnRails class Railtie < Rails::Railtie + rake_tasks do + load 'tasks/cypress.rake' + end initializer :setup_cypress_middleware, after: :load_config_initializers do |app| if CypressOnRails.configuration.use_middleware? require 'cypress_on_rails/middleware' app.middleware.use Middleware + + # Add state reset middleware for compatibility with cypress-rails + require 'cypress_on_rails/state_reset_middleware' + app.middleware.use StateResetMiddleware end if CypressOnRails.configuration.use_vcr_middleware? require 'cypress_on_rails/vcr/insert_eject_middleware' diff --git a/lib/cypress_on_rails/server.rb b/lib/cypress_on_rails/server.rb new file mode 100644 index 0000000..ab55e0f --- /dev/null +++ b/lib/cypress_on_rails/server.rb @@ -0,0 +1,196 @@ +require 'socket' +require 'timeout' +require 'cypress_on_rails/configuration' + +module CypressOnRails + class Server + attr_reader :host, :port, :framework, :install_folder + + def initialize(options = {}) + config = CypressOnRails.configuration + + @framework = options[:framework] || :cypress + @host = options[:host] || config.server_host + @port = options[:port] || config.server_port || find_available_port + @port = @port.to_i if @port + @install_folder = options[:install_folder] || config.install_folder || detect_install_folder + @transactional = options.fetch(:transactional, config.transactional_server) + end + + def open + start_server do + run_command(open_command, "Opening #{framework} test runner") + end + end + + def run + start_server do + result = run_command(run_command_str, "Running #{framework} tests") + exit(result ? 0 : 1) + end + end + + def init + ensure_install_folder_exists + puts "#{framework.to_s.capitalize} configuration initialized at #{install_folder}" + end + + private + + def detect_install_folder + # Check common locations for cypress/playwright installation + possible_folders = ['e2e', 'spec/e2e', 'spec/cypress', 'spec/playwright', 'cypress', 'playwright'] + folder = possible_folders.find { |f| File.exist?(f) } + folder || 'e2e' + end + + def ensure_install_folder_exists + unless File.exist?(install_folder) + puts "Creating #{install_folder} directory..." + FileUtils.mkdir_p(install_folder) + end + end + + def find_available_port + server = TCPServer.new('127.0.0.1', 0) + port = server.addr[1] + server.close + port + end + + def start_server(&block) + config = CypressOnRails.configuration + + run_hook(config.before_server_start) + + ENV['CYPRESS'] = '1' + ENV['RAILS_ENV'] = 'test' + + server_pid = spawn_server + + begin + wait_for_server + run_hook(config.after_server_start) + + puts "Rails server started on #{base_url}" + + if @transactional && defined?(ActiveRecord::Base) + ActiveRecord::Base.connection.begin_transaction(joinable: false) + run_hook(config.after_transaction_start) + end + + yield + + ensure + run_hook(config.before_server_stop) + + if @transactional && defined?(ActiveRecord::Base) + ActiveRecord::Base.connection.rollback_transaction if ActiveRecord::Base.connection.transaction_open? + end + + stop_server(server_pid) + ENV.delete('CYPRESS') + end + end + + def spawn_server + rails_command = if File.exist?('bin/rails') + 'bin/rails' + else + 'bundle exec rails' + end + + server_command = "#{rails_command} server -p #{port} -b #{host}" + + puts "Starting Rails server: #{server_command}" + + spawn(server_command, out: $stdout, err: $stderr) + end + + def wait_for_server(timeout = 30) + Timeout.timeout(timeout) do + loop do + begin + TCPSocket.new(host, port).close + break + rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH + sleep 0.1 + end + end + end + rescue Timeout::Error + raise "Rails server failed to start on #{host}:#{port} after #{timeout} seconds" + end + + def stop_server(pid) + if pid + puts "Stopping Rails server (PID: #{pid})" + Process.kill('TERM', pid) + Process.wait(pid) + end + rescue Errno::ESRCH + # Process already terminated + end + + def base_url + "http://#{host}:#{port}" + end + + def open_command + case framework + when :cypress + if command_exists?('yarn') + "yarn cypress open --project #{install_folder} --config baseUrl=#{base_url}" + elsif command_exists?('npx') + "npx cypress open --project #{install_folder} --config baseUrl=#{base_url}" + else + "cypress open --project #{install_folder} --config baseUrl=#{base_url}" + end + when :playwright + if command_exists?('yarn') + "yarn playwright test --ui" + elsif command_exists?('npx') + "npx playwright test --ui" + else + "playwright test --ui" + end + end + end + + def run_command_str + case framework + when :cypress + if command_exists?('yarn') + "yarn cypress run --project #{install_folder} --config baseUrl=#{base_url}" + elsif command_exists?('npx') + "npx cypress run --project #{install_folder} --config baseUrl=#{base_url}" + else + "cypress run --project #{install_folder} --config baseUrl=#{base_url}" + end + when :playwright + if command_exists?('yarn') + "yarn playwright test" + elsif command_exists?('npx') + "npx playwright test" + else + "playwright test" + end + end + end + + def run_command(command, description) + puts "#{description}: #{command}" + system(command) + end + + def command_exists?(command) + system("which #{command} > /dev/null 2>&1") + end + + def run_hook(hook) + if hook && hook.respond_to?(:call) + hook.call + end + end + end +end \ No newline at end of file diff --git a/lib/cypress_on_rails/state_reset_middleware.rb b/lib/cypress_on_rails/state_reset_middleware.rb new file mode 100644 index 0000000..39f2981 --- /dev/null +++ b/lib/cypress_on_rails/state_reset_middleware.rb @@ -0,0 +1,45 @@ +module CypressOnRails + class StateResetMiddleware + def initialize(app) + @app = app + end + + def call(env) + if env['PATH_INFO'] == '/__cypress__/reset_state' || env['PATH_INFO'] == '/cypress_rails_reset_state' + reset_application_state + [200, { 'Content-Type' => 'text/plain' }, ['State reset completed']] + else + @app.call(env) + end + end + + private + + def reset_application_state + config = CypressOnRails.configuration + + # Run after_state_reset hook if configured + run_hook(config.after_state_reset) + + # Default state reset actions + if defined?(DatabaseCleaner) + DatabaseCleaner.clean_with(:truncation) + elsif defined?(ActiveRecord::Base) + ActiveRecord::Base.connection.tables.each do |table| + next if table == 'schema_migrations' || table == 'ar_internal_metadata' + ActiveRecord::Base.connection.execute("DELETE FROM #{table}") + end + end + + # Clear Rails cache + Rails.cache.clear if defined?(Rails) && Rails.cache + + # Reset any class-level state + ActiveSupport::Dependencies.clear if defined?(ActiveSupport::Dependencies) + end + + def run_hook(hook) + hook.call if hook && hook.respond_to?(:call) + end + end +end \ No newline at end of file diff --git a/lib/generators/cypress_on_rails/templates/config/initializers/cypress_on_rails.rb.erb b/lib/generators/cypress_on_rails/templates/config/initializers/cypress_on_rails.rb.erb index 4f5eb8d..afc3a22 100644 --- a/lib/generators/cypress_on_rails/templates/config/initializers/cypress_on_rails.rb.erb +++ b/lib/generators/cypress_on_rails/templates/config/initializers/cypress_on_rails.rb.erb @@ -15,6 +15,18 @@ if defined?(CypressOnRails) <% unless options.experimental %># <% end %> cassette_library_dir: File.expand_path("#{__dir__}/../../<%= options.install_folder %>/<%= options.framework %>/fixtures/vcr_cassettes") <% unless options.experimental %># <% end %> } c.logger = Rails.logger + + # Server configuration for rake tasks (cypress:open, cypress:run, playwright:open, playwright:run) + # c.server_host = 'localhost' # or use ENV['CYPRESS_RAILS_HOST'] + # c.server_port = 3001 # or use ENV['CYPRESS_RAILS_PORT'] + # c.transactional_server = true # Enable automatic transaction rollback between tests + + # Server lifecycle hooks for rake tasks + # c.before_server_start = -> { DatabaseCleaner.clean_with(:truncation) } + # c.after_server_start = -> { puts "Test server started on port #{CypressOnRails.configuration.server_port}" } + # c.after_transaction_start = -> { Rails.application.load_seed } + # c.after_state_reset = -> { Rails.cache.clear } + # c.before_server_stop = -> { puts "Stopping test server..." } # If you want to enable a before_request logic, such as authentication, logging, sending metrics, etc. # Refer to https://www.rubydoc.info/gems/rack/Rack/Request for the `request` argument. diff --git a/lib/tasks/cypress.rake b/lib/tasks/cypress.rake new file mode 100644 index 0000000..6e7bc5e --- /dev/null +++ b/lib/tasks/cypress.rake @@ -0,0 +1,33 @@ +namespace :cypress do + desc "Open Cypress test runner UI" + task :open => :environment do + require 'cypress_on_rails/server' + CypressOnRails::Server.new.open + end + + desc "Run Cypress tests in headless mode" + task :run => :environment do + require 'cypress_on_rails/server' + CypressOnRails::Server.new.run + end + + desc "Initialize Cypress configuration" + task :init => :environment do + require 'cypress_on_rails/server' + CypressOnRails::Server.new.init + end +end + +namespace :playwright do + desc "Open Playwright test runner UI" + task :open => :environment do + require 'cypress_on_rails/server' + CypressOnRails::Server.new(framework: :playwright).open + end + + desc "Run Playwright tests in headless mode" + task :run => :environment do + require 'cypress_on_rails/server' + CypressOnRails::Server.new(framework: :playwright).run + end +end \ No newline at end of file From 17902541361ab78631be2fd493db099d6689b892 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Sat, 27 Sep 2025 18:03:54 -1000 Subject: [PATCH 2/3] Update CHANGELOG with comprehensive migration guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added detailed migration instructions for: - Users currently using manual server management (old way) - Users migrating from cypress-rails gem The migration guide clearly shows the before/after comparison and provides step-by-step instructions for both scenarios. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CHANGELOG.md | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98c49c5..227dfc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,64 @@ This project adheres to [Semantic Versioning](https://semver.org/). * **Environment configuration**: Support for `CYPRESS_RAILS_HOST` and `CYPRESS_RAILS_PORT` environment variables * **Automatic server management**: Test server automatically starts and stops with test execution +### Migration Guide + +#### From Manual Server Management (Old Way) +If you were previously running tests manually: + +**Before (Manual Process):** +```bash +# Terminal 1: Start Rails server +CYPRESS=1 bin/rails server -p 5017 + +# Terminal 2: Run tests +yarn cypress open --project ./e2e +# or +npx cypress run --project ./e2e +``` + +**After (Automated with Rake Tasks):** +```bash +# Single command - server managed automatically! +bin/rails cypress:open +# or +bin/rails cypress:run +``` + +#### From cypress-rails Gem +If migrating from the `cypress-rails` gem: + +1. Update your Gemfile: + ```ruby + # Remove + gem 'cypress-rails' + + # Add + gem 'cypress-on-rails', '~> 1.0' + ``` + +2. Run bundle and generator: + ```bash + bundle install + rails g cypress_on_rails:install + ``` + +3. Configure hooks in `config/initializers/cypress_on_rails.rb` (optional): + ```ruby + CypressOnRails.configure do |c| + # These hooks match cypress-rails functionality + c.before_server_start = -> { DatabaseCleaner.clean } + c.after_server_start = -> { Rails.application.load_seed } + c.transactional_server = true + end + ``` + +4. Use the same commands you're familiar with: + ```bash + bin/rails cypress:open + bin/rails cypress:run + ``` + --- ## [1.18.0] — 2025-08-27 From 1db6101a4a54e3e3740ec9ed34ef80275d215090 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Sun, 28 Sep 2025 16:22:52 -1000 Subject: [PATCH 3/3] Fix security and stability issues from PR review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add require 'fileutils' to server.rb - Refactor shell command execution to prevent injection attacks - Use argument arrays instead of string interpolation - Pass commands as arrays to spawn() and system() - Improve database state reset safety - Move after_state_reset hook to run after cleanup - Add support for disable_referential_integrity when available - Use proper table name quoting with quote_table_name - Use File.expand_path for folder detection These changes address security concerns about command injection and improve compatibility across different Rails environments. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- lib/cypress_on_rails/server.rb | 49 ++++++++++--------- .../state_reset_middleware.rb | 25 +++++++--- 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/lib/cypress_on_rails/server.rb b/lib/cypress_on_rails/server.rb index ab55e0f..dff8817 100644 --- a/lib/cypress_on_rails/server.rb +++ b/lib/cypress_on_rails/server.rb @@ -1,5 +1,6 @@ require 'socket' require 'timeout' +require 'fileutils' require 'cypress_on_rails/configuration' module CypressOnRails @@ -25,7 +26,7 @@ def open def run start_server do - result = run_command(run_command_str, "Running #{framework} tests") + result = run_command(run_command_args, "Running #{framework} tests") exit(result ? 0 : 1) end end @@ -40,7 +41,7 @@ def init def detect_install_folder # Check common locations for cypress/playwright installation possible_folders = ['e2e', 'spec/e2e', 'spec/cypress', 'spec/playwright', 'cypress', 'playwright'] - folder = possible_folders.find { |f| File.exist?(f) } + folder = possible_folders.find { |f| File.exist?(File.expand_path(f)) } folder || 'e2e' end @@ -94,17 +95,17 @@ def start_server(&block) end def spawn_server - rails_command = if File.exist?('bin/rails') - 'bin/rails' + rails_args = if File.exist?('bin/rails') + ['bin/rails'] else - 'bundle exec rails' + ['bundle', 'exec', 'rails'] end - server_command = "#{rails_command} server -p #{port} -b #{host}" + server_args = rails_args + ['server', '-p', port.to_s, '-b', host] - puts "Starting Rails server: #{server_command}" + puts "Starting Rails server: #{server_args.join(' ')}" - spawn(server_command, out: $stdout, err: $stderr) + spawn(*server_args, out: $stdout, err: $stderr) end def wait_for_server(timeout = 30) @@ -140,47 +141,47 @@ def open_command case framework when :cypress if command_exists?('yarn') - "yarn cypress open --project #{install_folder} --config baseUrl=#{base_url}" + ['yarn', 'cypress', 'open', '--project', install_folder, '--config', "baseUrl=#{base_url}"] elsif command_exists?('npx') - "npx cypress open --project #{install_folder} --config baseUrl=#{base_url}" + ['npx', 'cypress', 'open', '--project', install_folder, '--config', "baseUrl=#{base_url}"] else - "cypress open --project #{install_folder} --config baseUrl=#{base_url}" + ['cypress', 'open', '--project', install_folder, '--config', "baseUrl=#{base_url}"] end when :playwright if command_exists?('yarn') - "yarn playwright test --ui" + ['yarn', 'playwright', 'test', '--ui'] elsif command_exists?('npx') - "npx playwright test --ui" + ['npx', 'playwright', 'test', '--ui'] else - "playwright test --ui" + ['playwright', 'test', '--ui'] end end end - def run_command_str + def run_command_args case framework when :cypress if command_exists?('yarn') - "yarn cypress run --project #{install_folder} --config baseUrl=#{base_url}" + ['yarn', 'cypress', 'run', '--project', install_folder, '--config', "baseUrl=#{base_url}"] elsif command_exists?('npx') - "npx cypress run --project #{install_folder} --config baseUrl=#{base_url}" + ['npx', 'cypress', 'run', '--project', install_folder, '--config', "baseUrl=#{base_url}"] else - "cypress run --project #{install_folder} --config baseUrl=#{base_url}" + ['cypress', 'run', '--project', install_folder, '--config', "baseUrl=#{base_url}"] end when :playwright if command_exists?('yarn') - "yarn playwright test" + ['yarn', 'playwright', 'test'] elsif command_exists?('npx') - "npx playwright test" + ['npx', 'playwright', 'test'] else - "playwright test" + ['playwright', 'test'] end end end - def run_command(command, description) - puts "#{description}: #{command}" - system(command) + def run_command(command_args, description) + puts "#{description}: #{command_args.join(' ')}" + system(*command_args) end def command_exists?(command) diff --git a/lib/cypress_on_rails/state_reset_middleware.rb b/lib/cypress_on_rails/state_reset_middleware.rb index 39f2981..3851b1c 100644 --- a/lib/cypress_on_rails/state_reset_middleware.rb +++ b/lib/cypress_on_rails/state_reset_middleware.rb @@ -18,16 +18,26 @@ def call(env) def reset_application_state config = CypressOnRails.configuration - # Run after_state_reset hook if configured - run_hook(config.after_state_reset) - # Default state reset actions if defined?(DatabaseCleaner) DatabaseCleaner.clean_with(:truncation) elsif defined?(ActiveRecord::Base) - ActiveRecord::Base.connection.tables.each do |table| - next if table == 'schema_migrations' || table == 'ar_internal_metadata' - ActiveRecord::Base.connection.execute("DELETE FROM #{table}") + connection = ActiveRecord::Base.connection + + # Use disable_referential_integrity if available for safer table clearing + if connection.respond_to?(:disable_referential_integrity) + connection.disable_referential_integrity do + connection.tables.each do |table| + next if table == 'schema_migrations' || table == 'ar_internal_metadata' + connection.execute("DELETE FROM #{connection.quote_table_name(table)}") + end + end + else + # Fallback to regular deletion with proper table name quoting + connection.tables.each do |table| + next if table == 'schema_migrations' || table == 'ar_internal_metadata' + connection.execute("DELETE FROM #{connection.quote_table_name(table)}") + end end end @@ -36,6 +46,9 @@ def reset_application_state # Reset any class-level state ActiveSupport::Dependencies.clear if defined?(ActiveSupport::Dependencies) + + # Run after_state_reset hook after cleanup is complete + run_hook(config.after_state_reset) end def run_hook(hook)