Skip to content

Conversation

@justin808
Copy link
Member

@justin808 justin808 commented Sep 28, 2025

Summary

This PR adds cypress-rails compatible functionality to cypress-playwright-on-rails, making it a more complete replacement for cypress-rails.

Key Features Added

1. Rake Tasks for Test Execution

  • cypress:open - Opens Cypress test runner UI
  • cypress:run - Runs Cypress tests in headless mode
  • playwright:open - Opens Playwright test runner UI
  • playwright:run - Runs Playwright tests in headless mode

These tasks automatically:

  • Start the Rails test server
  • Execute tests
  • Stop the server when done
  • No manual server management required!

2. Server Lifecycle Hooks

New configuration hooks for customizing test server behavior:

  • 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 (transactional mode)
  • after_state_reset - Run code after application state is reset
  • before_server_stop - Run code before Rails server stops

3. Additional Enhancements

  • Transactional test mode - Automatic database transaction rollback between tests
  • State reset endpoint - Added /cypress_rails_reset_state endpoint for compatibility
  • Environment variables - Support for CYPRESS_RAILS_HOST and CYPRESS_RAILS_PORT
  • Dynamic port selection - Automatically finds available port if not specified

Migration from cypress-rails

This enhancement makes migrating from cypress-rails straightforward:

  1. Replace gem 'cypress-rails' with gem 'cypress-on-rails'
  2. Run rails g cypress_on_rails:install
  3. Configure hooks in config/initializers/cypress_on_rails.rb
  4. Use the same rake tasks (cypress:open, cypress:run)

Test Plan

  • Test rake tasks with existing Cypress tests
  • Test rake tasks with existing Playwright tests
  • Verify server lifecycle hooks are called correctly
  • Test transactional mode with database operations
  • Verify state reset endpoint functionality
  • Test environment variable configuration
  • Verify dynamic port selection

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added rake tasks to open/run Cypress and Playwright, plus init setup.
    • Introduced automatic test server management with lifecycle hooks (start/stop/transaction).
    • Added configurable server host/port and transactional test mode.
    • Provided state reset endpoints to clear DB/cache between tests.
  • Documentation

    • Expanded README with server lifecycle, hooks, and rake-based workflows; added examples.
    • Added Migration Guide (including steps from cypress-rails).
    • Updated changelog with Unreleased items and preserved version history.

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 <[email protected]>
@coderabbitai
Copy link

coderabbitai bot commented Sep 28, 2025

Warning

Rate limit exceeded

@justin808 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 5 minutes and 25 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 1790254 and 1db6101.

📒 Files selected for processing (2)
  • lib/cypress_on_rails/server.rb (1 hunks)
  • lib/cypress_on_rails/state_reset_middleware.rb (1 hunks)

Walkthrough

Adds rake tasks and a new Server class to manage Cypress/Playwright test runs with automatic Rails server lifecycle, transactional mode, and env setup. Introduces state reset middleware and configurable lifecycle hooks. Updates configuration API, railtie wiring, initializer template comments, and documentation (README and CHANGELOG).

Changes

Cohort / File(s) Summary
Documentation
CHANGELOG.md, README.md
Documents new rake tasks, server lifecycle semantics, hooks, transactional mode, env config, manual vs automated workflows, and migration guidance.
Configuration API
lib/cypress_on_rails/configuration.rb
Adds accessors for server lifecycle hooks and settings (before_server_start, after_server_start, after_transaction_start, after_state_reset, before_server_stop, server_host, server_port, transactional_server) and initializes defaults from env.
Railtie Wiring
lib/cypress_on_rails/railtie.rb
Loads rake tasks; conditionally registers StateResetMiddleware alongside existing middleware when enabled.
Server Orchestration
lib/cypress_on_rails/server.rb
New CypressOnRails::Server to spawn/manage Rails server for tests, handle transactional wrapping, detect ports, construct Cypress/Playwright commands, run hooks, and manage lifecycle (open/run/init).
State Reset Middleware
lib/cypress_on_rails/state_reset_middleware.rb
New middleware intercepts reset endpoints, clears DB/cache/dependencies, and invokes after_state_reset hook; otherwise delegates to app.
Initializer Template
lib/generators/cypress_on_rails/templates/config/initializers/cypress_on_rails.rb.erb
Adds commented examples for server settings and lifecycle hooks; no runtime changes.
Rake Tasks
lib/tasks/cypress.rake
Adds cypress:{open,run,init} and playwright:{open,run} tasks that invoke CypressOnRails::Server for respective flows.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev as Developer
  participant Rake as Rake Task
  participant Srv as CypressOnRails::Server
  participant Rails as Rails App Server
  participant Test as Cypress/Playwright

  Dev->>Rake: rake cypress:open / cypress:run / playwright:*
  Rake->>Srv: new(...).open()/run()
  note over Srv: before_server_start hook
  Srv->>Rails: spawn (host/port/env)
  Srv-->>Srv: wait until ready (timeout)
  note over Srv: after_server_start hook
  alt transactional_server
    Srv-->>Rails: begin transaction (AR)
    note over Srv: after_transaction_start hook
  end
  Srv->>Test: exec command (open/run)
  Test-->>Srv: exit status
  note over Srv: before_server_stop hook
  opt transactional_server
    Srv-->>Rails: rollback transaction
  end
  Srv-->>Rails: terminate server
  Srv-->>Dev: exit with status
Loading
sequenceDiagram
  autonumber
  participant Client as Test Runner / Helper
  participant MW as StateResetMiddleware
  participant App as Rails App
  participant DB as DB/AR
  participant Cache as Rails.cache

  Client->>MW: HTTP POST /__cypress__/reset (example)
  alt matches reset path
    note over MW: after_state_reset hook (safe invoke)
    MW-->>DB: clean DB (DatabaseCleaner or truncate/clear)
    MW-->>Cache: clear cache (if available)
    MW-->>App: clear dependencies
    MW-->>Client: 200 OK (reset done)
  else non-reset path
    MW->>App: call(env)
    App-->>Client: response
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

I thump my paws—new tasks abound!
A server wakes without a sound.
Hooks hop in before and after,
Transactions roll back with soft laughter.
With reset paths swept neat and tight,
I race the specs—green lights at night. 🐇✅

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.17% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly and concisely describes the main changes in the pull request by highlighting the addition of cypress-rails compatible rake tasks and server lifecycle hooks, which aligns directly with the PR objectives and file-level changes.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🧪 Early access (Sonnet 4.5): enabled

We are currently testing the Sonnet 4.5 model, which is expected to improve code review quality. However, this model may lead to increased noise levels in the review comments. Please disable the early access features if the noise level causes any inconvenience.

Note:

  • Public repositories are always opted into early access features.
  • You can enable or disable early access features from the CodeRabbit UI or by updating the CodeRabbit configuration file.

Comment @coderabbitai help to get the list of available commands and usage tips.

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 <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac5d69f and 1790254.

📒 Files selected for processing (8)
  • CHANGELOG.md (1 hunks)
  • README.md (2 hunks)
  • lib/cypress_on_rails/configuration.rb (2 hunks)
  • lib/cypress_on_rails/railtie.rb (1 hunks)
  • lib/cypress_on_rails/server.rb (1 hunks)
  • lib/cypress_on_rails/state_reset_middleware.rb (1 hunks)
  • lib/generators/cypress_on_rails/templates/config/initializers/cypress_on_rails.rb.erb (1 hunks)
  • lib/tasks/cypress.rake (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
lib/cypress_on_rails/state_reset_middleware.rb (2)
lib/cypress_on_rails/configuration.rb (2)
  • initialize (36-38)
  • configuration (76-78)
lib/cypress_on_rails/server.rb (2)
  • initialize (9-18)
  • run_hook (190-194)
lib/cypress_on_rails/railtie.rb (1)
lib/cypress_on_rails/configuration.rb (1)
  • configuration (76-78)
lib/cypress_on_rails/server.rb (2)
lib/cypress_on_rails/configuration.rb (2)
  • initialize (36-38)
  • configuration (76-78)
lib/cypress_on_rails/state_reset_middleware.rb (4)
  • initialize (2-44)
  • initialize (3-5)
  • run_hook (41-43)
  • call (7-14)
🪛 Brakeman (7.0.2)
lib/cypress_on_rails/state_reset_middleware.rb

[medium] 30-30: Possible SQL injection
Type: SQL Injection
Confidence: Medium
More info: https://brakemanscanner.org/docs/warning_types/sql_injection/

(SQL Injection)

lib/cypress_on_rails/server.rb

[medium] 187-187: Possible command injection
Type: Command Injection
Confidence: Medium
More info: https://brakemanscanner.org/docs/warning_types/command_injection/

(Command Injection)


[medium] 107-107: Possible command injection
Type: Command Injection
Confidence: Medium
More info: https://brakemanscanner.org/docs/warning_types/command_injection/

(Command Injection)

# 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'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we follow a similar pattern from config using File.expand_path?

end
end
end
end No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few newline issues

end

# Clear Rails cache
Rails.cache.clear if defined?(Rails) && Rails.cache
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does run_hook(config.after_state_reset) go above the reset?

Seems like this clear cache line should be here, so we don't need to have it in the hook by default

c.after_state_reset = -> { Rails.cache.clear }

We can have some puts by default

# c.after_state_reset = -> { puts "State reset is done" }

@justin808
Copy link
Member Author

@claude please review this PR

@claude
Copy link

claude bot commented Sep 29, 2025

Claude finished @justin808's task —— View job


Code Review Complete ✅

Todo List:

  • Gather context and understand the PR changes
  • Review CHANGELOG.md
  • Review README.md documentation changes
  • Review lib/cypress_on_rails/configuration.rb
  • Review lib/cypress_on_rails/railtie.rb
  • Review lib/cypress_on_rails/server.rb (new file)
  • Review lib/cypress_on_rails/state_reset_middleware.rb (new file)
  • Review lib/generators/cypress_on_rails/templates/config/initializers/cypress_on_rails.rb.erb
  • Review lib/tasks/cypress.rake (new file)
  • Compile and post comprehensive review feedback

Overall Assessment

This PR adds valuable cypress-rails compatible functionality, making the gem a more complete replacement for cypress-rails. The rake tasks and server lifecycle hooks will significantly improve the developer experience. However, there are several critical security issues and bugs that must be addressed before merging.

Critical Issues ⚠️

1. Command Injection Vulnerabilities in lib/cypress_on_rails/server.rb

Lines 103-107: The Rails server spawn command is vulnerable to shell injection:

server_command = "#{rails_command} server -p #{port} -b #{host}"
spawn(server_command, out: $stdout, err: $stderr)

If someone sets CYPRESS_RAILS_HOST="localhost;rm -rf /tmp", the injected command will execute.

Lines 139-184: The open_command and run_command_str methods build shell strings with interpolated values that are passed directly to system(). This allows command injection via install_folder and base_url.

Lines 186-187: The command_exists? method also passes user input directly to shell.

Fix: Use argument arrays instead of interpolated strings:

# For spawn
args = [rails_command, 'server', '-p', port.to_s, '-b', host]
spawn(*args, out: $stdout, err: $stderr)

# For system calls
system('yarn', 'cypress', 'open', '--project', install_folder, '--config', "baseUrl=#{base_url}")

2. SQL Injection Risk in lib/cypress_on_rails/state_reset_middleware.rb:30

ActiveRecord::Base.connection.execute("DELETE FROM #{table}")

While table names come from Rails, this should use proper quoting:

connection.execute("DELETE FROM #{connection.quote_table_name(table)}")

3. Missing Dependency in lib/cypress_on_rails/server.rb

Line 50 uses FileUtils.mkdir_p but the file never requires fileutils. This will cause a NameError when running rake cypress:init.

Fix: Add require 'fileutils' at the top of the file.

4. State Reset Hook Order in lib/cypress_on_rails/state_reset_middleware.rb:22

The after_state_reset hook runs BEFORE the actual state reset (line 22), meaning any fixtures seeded by the hook will be immediately deleted. This breaks cypress-rails compatibility.

Fix: Move the hook to run AFTER all cleanup operations (after line 38).

5. Foreign Key Constraint Issues in lib/cypress_on_rails/state_reset_middleware.rb:28-31

The plain DELETE FROM loop will fail with PG::ForeignKeyViolation errors in databases with foreign key constraints.

Fix: Use ActiveRecord's referential integrity handling:

connection.disable_referential_integrity do
  tables.each do |table|
    connection.execute("DELETE FROM #{connection.quote_table_name(table)}")
  end
end

Additional Issues

6. Path Handling

  • lib/cypress_on_rails/server.rb:44: Should use File.expand_path like the configuration does for consistency

7. Code Duplication

  • Both server.rb and state_reset_middleware.rb define identical run_hook methods - should be extracted to a shared module

8. Documentation Suggestions

  • The migration guide in CHANGELOG.md is excellent and helpful
  • Consider adding a note about the security implications of the /cypress_rails_reset_state endpoint in production

Positive Aspects ✅

  1. Excellent backwards compatibility - The PR thoughtfully maintains cypress-rails API compatibility
  2. Comprehensive documentation - The migration guide and examples are clear and helpful
  3. Smart defaults - Dynamic port selection and automatic framework detection are great features
  4. Clean API design - The lifecycle hooks provide good extension points
  5. Test framework agnostic - Supporting both Cypress and Playwright is forward-thinking

Recommendations

  1. Must Fix Before Merge:

    • All command injection vulnerabilities
    • Missing FileUtils require
    • State reset hook order
    • Foreign key constraint handling
  2. Should Fix:

    • SQL injection risk (even though low probability)
    • Extract duplicate run_hook method
  3. Consider for Future:

    • Add tests for the new functionality
    • Add configuration validation
    • Consider rate limiting or authentication for the state reset endpoint

Summary

This is a valuable addition to the gem that will significantly improve the developer experience. The implementation is mostly solid, but the security vulnerabilities must be addressed before merging. With the fixes above, this will be an excellent enhancement that makes migration from cypress-rails straightforward while adding Playwright support.


- 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 <[email protected]>
@claude
Copy link

claude bot commented Sep 30, 2025

Code Review for PR #179

Thank you for this comprehensive PR! Here's my detailed review:

Strengths

  • Excellent backwards compatibility
  • Comprehensive documentation and migration guide
  • Well-structured server management
  • Smart defaults with automatic port detection

Critical Issues

1. SQL Injection Risk (lib/cypress_on_rails/state_reset_middleware.rb:30-39)
Table names need proper escaping. Use connection.quote_table_name(table) instead of direct string interpolation.

2. Process Management Issues

  • Missing error handling for server spawn failures
  • No timeout/force-kill mechanism if TERM signal fails
  • Consider adding retry logic for port detection race conditions

3. Missing Test Coverage
No tests were added for:

  • Server lifecycle management
  • State reset middleware
  • Rake task execution
  • Configuration hooks
  • Port collision handling
  • Process cleanup on failure

4. Configuration Validation
Hook setters should validate that values are callable when provided.

5. Performance Consideration
State reset with full truncation could be slow for large databases. Consider offering configurable strategies.

Security Notes

  • SQL injection must be fixed before merge
  • Consider rate limiting on reset_state endpoint
  • Document implications of running test server on 0.0.0.0

The main blockers are the SQL injection vulnerability and lack of test coverage. Once addressed, this will be a great addition!

@justin808 justin808 requested a review from Copilot September 30, 2025 01:30
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds cypress-rails compatible functionality to cypress-playwright-on-rails, providing rake tasks for automated test execution and server lifecycle hooks for customizing test server behavior.

  • Added rake tasks (cypress:open, cypress:run, playwright:open, playwright:run) that automatically manage Rails server lifecycle
  • Introduced server lifecycle hooks for customizing test environment setup and teardown
  • Added state reset endpoints and transactional test mode for database management between tests

Reviewed Changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
lib/tasks/cypress.rake Defines rake tasks for opening and running Cypress/Playwright tests with automatic server management
lib/generators/cypress_on_rails/templates/config/initializers/cypress_on_rails.rb.erb Adds configuration template for server settings and lifecycle hooks
lib/cypress_on_rails/state_reset_middleware.rb Implements middleware for resetting application state between tests with database cleanup
lib/cypress_on_rails/server.rb Core server management class handling test server lifecycle, port detection, and command execution
lib/cypress_on_rails/railtie.rb Integrates rake tasks and state reset middleware into Rails application
lib/cypress_on_rails/configuration.rb Extends configuration with server settings and lifecycle hook attributes
README.md Documents new rake tasks, server hooks configuration, and migration guidance
CHANGELOG.md Records new features and provides migration guide from cypress-rails
Comments suppressed due to low confidence (1)

lib/cypress_on_rails/state_reset_middleware.rb:1

  • This line is checking transaction_open? on the connection but appears in the server.rb file context. However, the method transaction_open? may not be available in all ActiveRecord versions and could raise a NoMethodError.
module CypressOnRails

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +79 to +94
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
Copy link

Copilot AI Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The transaction logic could lead to issues in multi-threaded environments. The begin_transaction(joinable: false) creates a non-joinable transaction, but there's no guarantee that the same connection will be used throughout the test execution, potentially causing transaction state inconsistencies.

Suggested change
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
ActiveRecord::Base.connection_pool.with_connection do |conn|
conn.begin_transaction(joinable: false)
run_hook(config.after_transaction_start)
begin
yield
ensure
run_hook(config.before_server_stop)
conn.rollback_transaction if conn.transaction_open?
end
end
else
yield
run_hook(config.before_server_stop)
end
stop_server(server_pid)
ENV.delete('CYPRESS')

Copilot uses AI. Check for mistakes.
end

def command_exists?(command)
system("which #{command} > /dev/null 2>&1")
Copy link

Copilot AI Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The command parameter is passed directly to shell without sanitization, creating a potential command injection vulnerability. Use Open3.capture3 or validate the command parameter to ensure it contains only expected command names.

Suggested change
system("which #{command} > /dev/null 2>&1")
system('which', command, out: File::NULL, err: File::NULL)

Copilot uses AI. Check for mistakes.
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)}")
Copy link

Copilot AI Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DELETE query is duplicated in both the disable_referential_integrity block and the fallback section. Extract this logic into a private method to avoid code duplication.

Copilot uses AI. Check for mistakes.
# 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)}")
Copy link

Copilot AI Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DELETE query is duplicated in both the disable_referential_integrity block and the fallback section. Extract this logic into a private method to avoid code duplication.

Copilot uses AI. Check for mistakes.
@justin808 justin808 merged commit d26d966 into master Sep 30, 2025
6 checks passed
@justin808 justin808 deleted the add-cypress-rake-tasks branch September 30, 2025 01:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants