Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,7 @@ ssr-generated

# Claude Code local settings
.claude/settings.local.json

# Playwright test artifacts (from cypress-on-rails gem)
/spec/dummy/e2e/playwright-report/
/spec/dummy/test-results/
131 changes: 131 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Git hooks will automatically run linting on **all changed files (staged + unstag
- **Run tests**:
- Ruby tests: `rake run_rspec`
- JavaScript tests: `yarn run test` or `rake js_tests`
- Playwright E2E tests: See Playwright section below
- All tests: `rake` (default task runs lint and all tests except examples)
- **Linting** (MANDATORY BEFORE EVERY COMMIT):
- **REQUIRED**: `bundle exec rubocop` - Must pass with zero offenses
Expand Down Expand Up @@ -126,10 +127,140 @@ This project maintains both a Ruby gem and an NPM package:
- Generated examples are in `gen-examples/` (ignored by git)
- Only use `yarn` as the JS package manager, never `npm`

## Playwright E2E Testing

### Overview
Playwright E2E testing is integrated via the `cypress-on-rails` gem (v1.19+), which provides seamless integration between Playwright and Rails. This allows you to control Rails application state during tests, use factory_bot, and more.

### Setup
The gem and Playwright are already configured. To install Playwright browsers:

```bash
cd spec/dummy
yarn playwright install --with-deps
```

### Running Playwright Tests

```bash
cd spec/dummy

# Run all tests
yarn playwright test

# Run tests in UI mode (interactive debugging)
yarn playwright test --ui

# Run tests with visible browser
yarn playwright test --headed

# Debug a specific test
yarn playwright test --debug

# Run specific test file
yarn playwright test e2e/playwright/e2e/react_on_rails/basic_components.spec.js
```

### Writing Tests

Tests are located in `spec/dummy/e2e/playwright/e2e/`. The gem provides helpful commands for Rails integration:

```javascript
import { test, expect } from "@playwright/test";
import { app, appEval, appFactories } from '../../support/on-rails';

test.describe("My React Component", () => {
test.beforeEach(async ({ page }) => {
// Clean database before each test
await app('clean');
});

test("should interact with component", async ({ page }) => {
// Create test data using factory_bot
await appFactories([['create', 'user', { name: 'Test User' }]]);

// Or run arbitrary Ruby code
await appEval('User.create!(email: "[email protected]")');

// Navigate and test
await page.goto("/");
const component = page.locator('#MyComponent-react-component-0');
await expect(component).toBeVisible();
});
});
```

### Available Rails Helpers

The `cypress-on-rails` gem provides these helpers (imported from `support/on-rails.js`):

- `app('clean')` - Clean database
- `appEval(code)` - Run arbitrary Ruby code
- `appFactories(options)` - Create records via factory_bot
- `appScenario(name)` - Load predefined scenario
- See `e2e/playwright/app_commands/` for available commands

### Creating App Commands

Add custom commands in `e2e/playwright/app_commands/`:

```ruby
# e2e/playwright/app_commands/my_command.rb
CypressOnRails::SmartFactoryWrapper.configure(
always_reload: !Rails.configuration.cache_classes,
factory: :factory_bot,
dir: "{#{FactoryBot.definition_file_paths.join(',')}}"
)

command 'my_command' do |options|
# Your custom Rails code
{ success: true, data: options }
end
```

### Test Organization

```
spec/dummy/e2e/
├── playwright.config.js # Playwright configuration
├── playwright/
│ ├── support/
│ │ ├── index.js # Test setup
│ │ └── on-rails.js # Rails helper functions
│ ├── e2e/
│ │ ├── react_on_rails/ # React on Rails specific tests
│ │ │ └── basic_components.spec.js
│ │ └── rails_examples/ # Example tests
│ │ └── using_scenarios.spec.js
│ └── app_commands/ # Rails helper commands
│ ├── clean.rb
│ ├── factory_bot.rb
│ ├── eval.rb
│ └── scenarios/
│ └── basic.rb
```

### Best Practices

- Use `app('clean')` in `beforeEach` to ensure clean state
- Leverage Rails helpers (`appFactories`, `appEval`) instead of UI setup
- Test React on Rails specific features: SSR, hydration, component registry
- Use component IDs like `#ComponentName-react-component-0` for selectors
- Monitor console errors during tests
- Test across different browsers with `--project` flag

### Debugging

- Run in UI mode: `yarn playwright test --ui`
- Use `page.pause()` to pause execution
- Check `playwright-report/` for detailed results after test failures
- Enable debug logging in `playwright.config.js`

## IDE Configuration

Exclude these directories to prevent IDE slowdowns:

- `/coverage`, `/tmp`, `/gen-examples`, `/packages/react-on-rails/lib`
- `/node_modules`, `/spec/dummy/node_modules`, `/spec/dummy/tmp`
- `/spec/dummy/app/assets/webpack`, `/spec/dummy/log`
- `/spec/dummy/e2e/playwright-report`, `/spec/dummy/test-results`
1 change: 1 addition & 0 deletions Gemfile.development_dependencies
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ group :test do
gem "capybara"
gem "capybara-screenshot"
gem "coveralls", require: false
gem "cypress-on-rails", "~> 1.19"
gem "equivalent-xml"
gem "generator_spec"
gem "launchy"
Expand Down
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ GEM
thor (>= 0.19.4, < 2.0)
tins (~> 1.6)
crass (1.0.6)
cypress-on-rails (1.19.0)
rack
date (3.3.4)
debug (1.9.2)
irb (~> 1.10)
Expand Down Expand Up @@ -410,6 +412,7 @@ DEPENDENCIES
capybara
capybara-screenshot
coveralls
cypress-on-rails (~> 1.19)
debug
equivalent-xml
gem-release
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@babel/preset-react": "^7.26.3",
"@babel/preset-typescript": "^7.27.1",
"@eslint/compat": "^1.2.7",
"@playwright/test": "^1.55.1",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Scope Playwright to the dummy app or align versions across the repo

If Playwright runs only under spec/dummy, keeping a second copy at the repo root adds install time and invites version drift. Prefer removing the root devDependency or ensure both places use the same version.

Apply if scoping to dummy app only:

-    "@playwright/test": "^1.55.1",

Run to compare versions (root vs dummy):


🏁 Script executed:

#!/bin/bash
set -euo pipefail
for f in package.json spec/dummy/package.json; do
  if [ -f "$f" ]; then
    printf "%-28s %s\n" "$f" "$(jq -r '.devDependencies["@playwright/test"] // .dependencies["@playwright/test"] // "MISSING"' "$f")"
  fi
done

Length of output: 73


Align @playwright/test versions across root and dummy

@playwright/test is ^1.55.1 in package.json but ^1.41.0 in spec/dummy/package.json; update the dummy app to ^1.55.1 or remove the root entry if you intend to scope Playwright solely under spec/dummy.

🤖 Prompt for AI Agents
In package.json around line 39, the root dependency "@playwright/test" is
^1.55.1 while spec/dummy/package.json pins ^1.41.0; update the dummy app to use
^1.55.1 so versions are aligned (or, if Playwright should only live under
spec/dummy, remove the root entry instead). Edit spec/dummy/package.json to bump
the "@playwright/test" version to ^1.55.1, then reinstall (npm/yarn/pnpm
install) and update the lockfile; alternatively delete the root-level
"@playwright/test" entry and reinstall to scope Playwright to spec/dummy only.

"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
Expand Down
3 changes: 3 additions & 0 deletions spec/dummy/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ GEM
thor (>= 0.19.4, < 2.0)
tins (~> 1.6)
crass (1.0.6)
cypress-on-rails (1.19.0)
rack
date (3.4.1)
debug (1.9.2)
irb (~> 1.10)
Expand Down Expand Up @@ -412,6 +414,7 @@ DEPENDENCIES
capybara
capybara-screenshot
coveralls
cypress-on-rails (~> 1.19)
debug
equivalent-xml
generator_spec
Expand Down
50 changes: 50 additions & 0 deletions spec/dummy/config/initializers/cypress_on_rails.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# frozen_string_literal: true

if defined?(CypressOnRails)
CypressOnRails.configure do |c|
c.api_prefix = ""
c.install_folder = File.expand_path("#{__dir__}/../../e2e/playwright")
# WARNING!! CypressOnRails can execute arbitrary ruby code
# please use with extra caution if enabling on hosted servers or starting your local server on 0.0.0.0
c.use_middleware = !Rails.env.production?
# c.use_vcr_middleware = !Rails.env.production?
# # Use this if you want to use use_cassette wrapper instead of manual insert/eject
# # c.use_vcr_use_cassette_middleware = !Rails.env.production?
# # Pass custom VCR options
# c.vcr_options = {
# hook_into: :webmock,
# default_cassette_options: { record: :once },
# cassette_library_dir: File.expand_path("#{__dir__}/../../e2e/playwright/fixtures/vcr_cassettes")
# }
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.
# Return nil to continue through the Cypress command. Return a response [status, header, body] to halt.
# c.before_request = lambda { |request|
# unless request.env['warden'].authenticate(:secret_key)
# return [403, {}, ["forbidden"]]
# end
# }
end

# # if you compile your asssets on CI
# if ENV['CYPRESS'].present? && ENV['CI'].present?
# Rails.application.configure do
# config.assets.compile = false
# config.assets.unknown_asset_fallback = false
# end
# end
end
Loading
Loading