|
| 1 | +# Claude Code Guidelines for cypress-playwright-on-rails |
| 2 | + |
| 3 | +This document provides guidance for Claude Code agents working on the cypress-playwright-on-rails gem. |
| 4 | + |
| 5 | +## ⚠️ Critical Requirements |
| 6 | + |
| 7 | +### Testing Protocol |
| 8 | +- **ALWAYS run corresponding RSpec tests when changing source files** |
| 9 | + - Changing `lib/cypress_on_rails/middleware.rb`? Run `bundle exec rspec spec/cypress_on_rails/middleware_spec.rb` |
| 10 | + - Changing `lib/cypress_on_rails/command_executor.rb`? Run `bundle exec rspec spec/cypress_on_rails/command_executor_spec.rb` |
| 11 | +- **Run full test suite before pushing**: `bundle exec rake` |
| 12 | +- **Test against multiple Rails versions** when making significant changes: |
| 13 | + ```bash |
| 14 | + ./specs_e2e/rails_6_1/test.sh |
| 15 | + ./specs_e2e/rails_7_2/test.sh |
| 16 | + ./specs_e2e/rails_8/test.sh |
| 17 | + ``` |
| 18 | + |
| 19 | +### Code Quality |
| 20 | +- **ALWAYS use `bundle exec` prefix** when running Ruby tools: `bundle exec rubocop`, `bundle exec rspec`, `bundle exec rake` |
| 21 | +- **ALWAYS run `bundle exec rubocop` and fix all violations before committing** |
| 22 | +- **NEVER push code that fails RuboCop locally** - if CI fails but local passes, add the exact disable directive CI expects |
| 23 | +- **ALWAYS end files with a newline character** - this is mandatory for all files |
| 24 | + |
| 25 | +### Git Workflow |
| 26 | +- **NEVER push directly to master** - always create feature branches and use PRs |
| 27 | +- **Keep PRs focused and minimal** - one logical change per PR |
| 28 | +- **Run `bundle exec rubocop` before every commit** |
| 29 | +- **Check CI status after pushing**: `gh pr view --json statusCheckRollup` |
| 30 | + |
| 31 | +## 🚀 Common Commands |
| 32 | + |
| 33 | +### Testing |
| 34 | +```bash |
| 35 | +# Run all unit tests |
| 36 | +bundle exec rspec |
| 37 | + |
| 38 | +# Run specific test file |
| 39 | +bundle exec rspec spec/cypress_on_rails/middleware_spec.rb |
| 40 | + |
| 41 | +# Run tests with focus tag |
| 42 | +bundle exec rspec --focus |
| 43 | + |
| 44 | +# Run full suite (tests + build gem) |
| 45 | +bundle exec rake |
| 46 | + |
| 47 | +# Run integration tests for specific Rails version |
| 48 | +./specs_e2e/rails_8/test.sh |
| 49 | +``` |
| 50 | + |
| 51 | +### Code Quality |
| 52 | +```bash |
| 53 | +# Run RuboCop (Ruby linter) |
| 54 | +bundle exec rubocop |
| 55 | + |
| 56 | +# Auto-fix RuboCop violations |
| 57 | +bundle exec rubocop -a |
| 58 | + |
| 59 | +# Check specific file |
| 60 | +bundle exec rubocop lib/cypress_on_rails/middleware.rb |
| 61 | +``` |
| 62 | + |
| 63 | +### Gem Development |
| 64 | +```bash |
| 65 | +# Install dependencies |
| 66 | +bundle install |
| 67 | + |
| 68 | +# Build gem |
| 69 | +gem build cypress-on-rails.gemspec |
| 70 | + |
| 71 | +# Install gem locally for testing |
| 72 | +gem install cypress-on-rails-*.gem |
| 73 | +``` |
| 74 | + |
| 75 | +### Generator Testing |
| 76 | +```bash |
| 77 | +# Test generator in a Rails app |
| 78 | +cd /path/to/test/rails/app |
| 79 | +bundle exec rails g cypress_on_rails:install |
| 80 | + |
| 81 | +# With options |
| 82 | +bundle exec rails g cypress_on_rails:install --framework=playwright |
| 83 | +bundle exec rails g cypress_on_rails:install --install_folder=e2e |
| 84 | +``` |
| 85 | + |
| 86 | +## 📁 Project Architecture |
| 87 | + |
| 88 | +### Core Components |
| 89 | + |
| 90 | +**Middleware Layer** (`lib/cypress_on_rails/middleware.rb`) |
| 91 | +- Rack middleware that intercepts `/__e2e__/command` endpoint |
| 92 | +- Parses JSON requests and routes to CommandExecutor |
| 93 | +- Returns JSON responses with appropriate status codes (201/404/500) |
| 94 | + |
| 95 | +**Command Executor** (`lib/cypress_on_rails/command_executor.rb`) |
| 96 | +- Loads `e2e_helper.rb` to set up test environment |
| 97 | +- Executes Ruby command files using `eval()` in binding context |
| 98 | +- Provides access to Rails, ActiveRecord, factories, and custom app code |
| 99 | + |
| 100 | +**Smart Factory Wrapper** (`lib/cypress_on_rails/smart_factory_wrapper.rb`) |
| 101 | +- Abstraction layer over FactoryBot/SimpleRailsFactory |
| 102 | +- Auto-reloads factory definitions when files change (mtime tracking) |
| 103 | +- Supports: `create()`, `create_list()`, `build()`, `build_list()` |
| 104 | + |
| 105 | +**Configuration** (`lib/cypress_on_rails/configuration.rb`) |
| 106 | +- Central configuration: `api_prefix`, `install_folder`, middleware settings |
| 107 | +- `before_request` hook for authentication/metrics |
| 108 | +- VCR options for HTTP recording/stubbing |
| 109 | + |
| 110 | +**Rails Integration** (`lib/cypress_on_rails/railtie.rb`) |
| 111 | +- Automatically injects middleware into Rails stack |
| 112 | +- Conditional loading based on configuration |
| 113 | +- Supports VCR middleware variants |
| 114 | + |
| 115 | +### Directory Structure |
| 116 | +``` |
| 117 | +lib/cypress_on_rails/ |
| 118 | +├── middleware.rb # Main HTTP request handler |
| 119 | +├── command_executor.rb # Ruby code execution engine |
| 120 | +├── smart_factory_wrapper.rb # Factory abstraction with auto-reload |
| 121 | +├── simple_rails_factory.rb # Fallback factory implementation |
| 122 | +├── configuration.rb # Settings management |
| 123 | +├── railtie.rb # Rails auto-integration |
| 124 | +├── middleware_config.rb # Shared middleware configuration |
| 125 | +└── vcr/ # VCR middleware variants |
| 126 | + ├── insert_eject_middleware.rb # Manual cassette control |
| 127 | + ├── use_cassette_middleware.rb # Automatic cassette wrapping |
| 128 | + └── middleware_helpers.rb # VCR utilities |
| 129 | +
|
| 130 | +lib/generators/cypress_on_rails/ |
| 131 | +├── install_generator.rb # Rails generator for project setup |
| 132 | +└── templates/ # Generated boilerplate files |
| 133 | + ├── config/initializers/cypress_on_rails.rb.erb |
| 134 | + ├── spec/e2e/e2e_helper.rb.erb |
| 135 | + ├── spec/e2e/app_commands/ # Command files |
| 136 | + ├── spec/cypress/ # Cypress setup |
| 137 | + └── spec/playwright/ # Playwright setup |
| 138 | +
|
| 139 | +spec/cypress_on_rails/ # Unit tests (RSpec) |
| 140 | +specs_e2e/ # Integration tests |
| 141 | +├── rails_6_1/ # Rails 6.1 example app |
| 142 | +├── rails_7_2/ # Rails 7.2 example app |
| 143 | +└── rails_8/ # Rails 8.0 example app |
| 144 | +``` |
| 145 | + |
| 146 | +## 🔧 Development Patterns |
| 147 | + |
| 148 | +### Request Flow |
| 149 | +``` |
| 150 | +Cypress/Playwright Test |
| 151 | + ↓ |
| 152 | +POST /__e2e__/command { name: 'clean', options: {} } |
| 153 | + ↓ |
| 154 | +Middleware.call(env) |
| 155 | + ↓ |
| 156 | +Configuration.before_request hook (optional auth/metrics) |
| 157 | + ↓ |
| 158 | +Parse JSON → Validate command file exists |
| 159 | + ↓ |
| 160 | +CommandExecutor.perform(file_path, options) |
| 161 | + ├─ Load e2e_helper.rb (setup factories, DatabaseCleaner) |
| 162 | + └─ eval(file_content) in binding context |
| 163 | + ↓ |
| 164 | +Return [status, headers, [json_body]] |
| 165 | +``` |
| 166 | + |
| 167 | +### Command File Pattern |
| 168 | +Command files are plain Ruby evaluated in application context: |
| 169 | + |
| 170 | +```ruby |
| 171 | +# spec/e2e/app_commands/clean.rb |
| 172 | +DatabaseCleaner.strategy = :truncation |
| 173 | +DatabaseCleaner.clean |
| 174 | +CypressOnRails::SmartFactoryWrapper.reload |
| 175 | + |
| 176 | +# spec/e2e/app_commands/factory_bot.rb |
| 177 | +Array.wrap(command_options).map do |factory_options| |
| 178 | + factory_method = factory_options.shift |
| 179 | + CypressOnRails::SmartFactoryWrapper.public_send(factory_method, *factory_options) |
| 180 | +end |
| 181 | + |
| 182 | +# spec/e2e/app_commands/scenarios/user_with_posts.rb |
| 183 | +user = CypressOnRails:: SmartFactoryWrapper.create( :user, email: '[email protected]') |
| 184 | +CypressOnRails::SmartFactoryWrapper.create_list(:post, 3, author: user) |
| 185 | +``` |
| 186 | + |
| 187 | +### Testing Patterns |
| 188 | + |
| 189 | +**Unit Tests** - Isolated component testing with doubles/mocks: |
| 190 | +```ruby |
| 191 | +# spec/cypress_on_rails/middleware_spec.rb |
| 192 | +let(:app) { ->(env) { [200, {}, ["app response"]] } } |
| 193 | +let(:command_executor) { class_double(CypressOnRails::CommandExecutor) } |
| 194 | +let(:file) { class_double(File) } |
| 195 | +subject { described_class.new(app, command_executor, file) } |
| 196 | + |
| 197 | +it "parses JSON and calls executor" do |
| 198 | + env['rack.input'] = StringIO.new(JSON.generate({name: 'seed'})) |
| 199 | + expect(command_executor).to receive(:perform) |
| 200 | + subject.call(env) |
| 201 | +end |
| 202 | +``` |
| 203 | + |
| 204 | +**Integration Tests** - Full Rails app with Cypress/Playwright: |
| 205 | +```bash |
| 206 | +# specs_e2e/rails_8/test.sh |
| 207 | +bundle install |
| 208 | +bin/rails db:drop db:create db:migrate |
| 209 | +bin/rails g cypress_on_rails:install |
| 210 | +CYPRESS=1 bin/rails server -p 3001 & |
| 211 | +bundle exec cypress run |
| 212 | +# or |
| 213 | +bundle exec playwright test |
| 214 | +``` |
| 215 | + |
| 216 | +### Code Style Conventions |
| 217 | + |
| 218 | +**Module Organization** |
| 219 | +- Use modules for namespacing: `module CypressOnRails` |
| 220 | +- Class methods for singletons: `CommandExecutor.perform`, `SmartFactoryWrapper.create` |
| 221 | + |
| 222 | +**Dependency Injection** |
| 223 | +- Middleware accepts dependencies in constructor for testability: |
| 224 | + ```ruby |
| 225 | + def initialize(app, command_executor = CommandExecutor, file = File) |
| 226 | + ``` |
| 227 | + |
| 228 | +**Error Handling** |
| 229 | +- Rescue exceptions and log them |
| 230 | +- Return appropriate HTTP status codes (404 for missing files, 500 for errors) |
| 231 | +- Include error details in JSON response body |
| 232 | + |
| 233 | +**Configuration** |
| 234 | +- Centralize settings in `Configuration` class |
| 235 | +- Provide sensible defaults |
| 236 | +- Document configuration options in initializer template |
| 237 | + |
| 238 | +## 🔄 Backward Compatibility |
| 239 | + |
| 240 | +**Active Deprecations** (maintain compatibility but warn): |
| 241 | +1. `cypress_folder` → `install_folder` |
| 242 | +2. `/__cypress__/command` → `/__e2e__/command` |
| 243 | +3. `cypress_helper.rb` → `e2e_helper.rb` |
| 244 | +4. `CypressDev` constant → `CypressOnRails` |
| 245 | + |
| 246 | +**When maintaining backward compatibility:** |
| 247 | +- Keep old code paths functional |
| 248 | +- Log deprecation warnings via `Configuration.logger.warn` |
| 249 | +- Update documentation to recommend new approach |
| 250 | +- Provide migration path in CHANGELOG |
| 251 | + |
| 252 | +## 📝 Changelog Guidelines |
| 253 | + |
| 254 | +**What merits a changelog entry:** |
| 255 | +- New features or middleware options |
| 256 | +- Bug fixes affecting user-facing behavior |
| 257 | +- Breaking changes or deprecations |
| 258 | +- Performance improvements |
| 259 | +- Security fixes |
| 260 | + |
| 261 | +**What doesn't:** |
| 262 | +- Internal refactoring |
| 263 | +- Test improvements |
| 264 | +- Documentation updates (unless user-facing) |
| 265 | +- Development tooling changes |
| 266 | + |
| 267 | +**Format:** |
| 268 | +```markdown |
| 269 | +## [Unreleased] |
| 270 | + |
| 271 | +### Added |
| 272 | +- VCR use_cassette middleware for automatic cassette wrapping (#123) |
| 273 | + |
| 274 | +### Changed |
| 275 | +- Improved error messages for missing command files (#124) |
| 276 | + |
| 277 | +### Deprecated |
| 278 | +- `cypress_folder` configuration option, use `install_folder` instead |
| 279 | + |
| 280 | +### Fixed |
| 281 | +- Factory auto-reload not detecting file changes on Windows (#125) |
| 282 | +``` |
| 283 | + |
| 284 | +## 🔒 Security Considerations |
| 285 | + |
| 286 | +**CRITICAL**: This gem executes arbitrary Ruby code in the application context. |
| 287 | + |
| 288 | +- **NEVER enable in production** (disabled by default via `!Rails.env.production?`) |
| 289 | +- **Implement `before_request` hook** for authentication in sensitive environments: |
| 290 | + ```ruby |
| 291 | + c.before_request = lambda { |request| |
| 292 | + auth_token = request.env['HTTP_AUTHORIZATION'] |
| 293 | + raise 'Unauthorized' unless valid_token?(auth_token) |
| 294 | + } |
| 295 | + ``` |
| 296 | +- **Bind to localhost only** when running Rails server for E2E tests |
| 297 | +- **Use in CI/CD pipelines safely** - isolated environments only |
| 298 | + |
| 299 | +## 🎯 cypress-on-rails Specific Considerations |
| 300 | + |
| 301 | +### Multi-Framework Support |
| 302 | +The gem supports **both Cypress and Playwright**. When making changes: |
| 303 | +- Test generator with both `--framework=cypress` and `--framework=playwright` |
| 304 | +- Update both JavaScript support files (`on-rails.js`) when changing API |
| 305 | +- Verify examples in both `spec/cypress/` and `spec/playwright/` templates |
| 306 | + |
| 307 | +### VCR Middleware Variants |
| 308 | +Two VCR integration modes exist: |
| 309 | +1. **Insert/Eject** (`use_vcr_middleware: true`) - Manual cassette control via endpoints |
| 310 | +2. **Use Cassette** (`use_vcr_use_cassette_middleware: true`) - Automatic wrapping |
| 311 | + |
| 312 | +When modifying VCR functionality, test both modes. |
| 313 | + |
| 314 | +### Factory Wrapper Intelligence |
| 315 | +The `SmartFactoryWrapper` auto-reloads factories when files change: |
| 316 | +- Test with file modifications during test runs |
| 317 | +- Verify mtime tracking works correctly |
| 318 | +- Ensure fallback to `SimpleRailsFactory` when FactoryBot unavailable |
| 319 | + |
| 320 | +### Rails Version Compatibility |
| 321 | +This gem targets **Rails 6.1, 7.2, and 8.0+**: |
| 322 | +- Test changes against all three versions in `specs_e2e/` |
| 323 | +- Be mindful of Rails API changes (e.g., Rails 8.1 delegation requirements) |
| 324 | +- Check CI pipeline covers all versions |
| 325 | + |
| 326 | +### Generator Flexibility |
| 327 | +The generator supports multiple options: |
| 328 | +```bash |
| 329 | +--framework=cypress|playwright # Test framework choice |
| 330 | +--install_folder=e2e # Custom location |
| 331 | +--install_with=yarn|npm|skip # Package manager |
| 332 | +--api_prefix=/api # Proxy routing |
| 333 | +--experimental # VCR features |
| 334 | +``` |
| 335 | +Test generator combinations when modifying templates. |
| 336 | + |
| 337 | +## 📚 Useful Resources |
| 338 | + |
| 339 | +- **RSpec Documentation**: https://rspec.info/ |
| 340 | +- **Rack Specification**: https://github.com/rack/rack/blob/main/SPEC.rdoc |
| 341 | +- **Cypress Best Practices**: https://docs.cypress.io/guides/references/best-practices |
| 342 | +- **Playwright Best Practices**: https://playwright.dev/docs/best-practices |
| 343 | +- **FactoryBot Documentation**: https://github.com/thoughtbot/factory_bot |
| 344 | +- **VCR Documentation**: https://github.com/vcr/vcr |
| 345 | + |
| 346 | +## 🤝 Contributing Workflow |
| 347 | + |
| 348 | +1. **Create feature branch** from master: `git checkout -b feature/my-feature` |
| 349 | +2. **Make focused changes** - one logical change per PR |
| 350 | +3. **Write/update tests** for your changes |
| 351 | +4. **Run full test suite**: `bundle exec rake` |
| 352 | +5. **Run RuboCop**: `bundle exec rubocop` |
| 353 | +6. **Update CHANGELOG.md** if user-facing change |
| 354 | +7. **Commit with descriptive message** |
| 355 | +8. **Push and create PR**: `git push -u origin feature/my-feature && gh pr create` |
| 356 | +9. **Address CI failures** immediately - don't wait for merge |
| 357 | + |
| 358 | +## 💡 Tips for Claude Code Agents |
| 359 | + |
| 360 | +- **Read tests first** when understanding a component - they document intended behavior |
| 361 | +- **Check `specs_e2e/` examples** to understand real-world usage patterns |
| 362 | +- **Use `bundle exec rspec --focus`** to iterate quickly on single test |
| 363 | +- **Reference existing middleware** when adding new endpoints |
| 364 | +- **Follow Rack middleware conventions** (call next app, return 3-element array) |
| 365 | +- **Log liberally** during development - users need debugging info |
| 366 | +- **Consider backward compatibility** before changing public APIs |
| 367 | +- **Test generator output** by running it in a fresh Rails app |
| 368 | + |
| 369 | +--- |
| 370 | + |
| 371 | +**Questions?** Check the README.md, docs/ folder, or existing tests for examples. |
0 commit comments