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: 2 additions & 2 deletions .github/workflows/lint-js-and-ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ jobs:
run: cd spec/dummy && RAILS_ENV="test" bundle exec rake react_on_rails:generate_packs
- name: Detect dead code
run: |
yarn run knip
yarn run knip --production
yarn run knip --exclude binaries
yarn run knip --production --exclude binaries
- name: Lint JS
run: yarn run eslint --report-unused-disable-directives
- name: Check formatting
Expand Down
60 changes: 60 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Playwright E2E Tests

on:
pull_request:
push:
branches: [master]

jobs:
playwright:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'yarn'

- name: Install yalc globally
run: npm install -g yalc

Comment on lines +25 to +27
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 | 🟠 Major

Use Yarn for global tools per repo standards.

Project policy (see CLAUDE.md) forbids npm commands; the other workflows install yalc with Yarn. Please switch this step to yarn global add yalc (or equivalent) to stay compliant.

🤖 Prompt for AI Agents
.github/workflows/playwright.yml lines 25-27: the workflow step installs yalc
using npm which violates the repo policy; replace the npm command with the
repo-standard Yarn global installation (e.g., use yarn global add yalc or the
equivalent yarn corepack-enabled invocation) so the step uses Yarn to install
yalc globally and remains consistent with other workflows.

- name: Install root dependencies
run: yarn install

- name: Install dummy app dependencies
working-directory: spec/dummy
run: |
bundle install
yarn install
- name: Install Playwright browsers
working-directory: spec/dummy
run: yarn playwright install --with-deps

- name: Generate React on Rails packs
working-directory: spec/dummy
env:
RAILS_ENV: test
run: bundle exec rake react_on_rails:generate_packs

- name: Build test assets
working-directory: spec/dummy
run: yarn build:test

- name: Run Playwright tests
working-directory: spec/dummy
run: yarn test:e2e

- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: spec/dummy/e2e/playwright-report/
retention-days: 30
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,7 @@ ssr-generated
# Claude Code local settings
.claude/settings.local.json
.claude/.fuse_hidden*

# Playwright test artifacts (from cypress-on-rails gem)
/spec/dummy/e2e/playwright-report/
/spec/dummy/test-results/
144 changes: 144 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Pre-commit hooks automatically run:
- **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 @@ -233,10 +234,153 @@ rm debug-*.js
- 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

**Note:** Playwright will automatically start the Rails server on port 5017 before running tests. You don't need to manually start the server.

```bash
cd spec/dummy

# Run all tests (Rails server auto-starts)
yarn test:e2e

# Run tests in UI mode (interactive debugging)
yarn test:e2e:ui

# Run tests with visible browser
yarn test:e2e:headed

# Debug a specific test
yarn test:e2e:debug

# View test report
yarn test:e2e:report

# Run specific test file
yarn test:e2e 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
Comment on lines +272 to +345
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 | 🟡 Minor

Fix Playwright paths/extensions to match the actual test layout.

The examples here still point to spec/dummy/e2e/playwright/e2e/... and .spec.js, but this PR adds the suites as .spec.ts files alongside spec/dummy/playwright.config.ts. Please adjust the documentation so newcomers can copy/paste the correct TypeScript paths and config location.

🤖 Prompt for AI Agents
In CLAUDE.md around lines 272 to 345, the Playwright example paths and file
extensions are incorrect for the new layout; update all references from
spec/dummy/e2e/playwright/... to spec/dummy/playwright/... and change example
test filenames from .spec.js to .spec.ts, update the run command example to
reference playwright.config.ts in spec/dummy (or the new config location), and
adjust import paths in the code snippets to match the TypeScript helpers (e.g.,
.ts modules and correct relative paths) so the docs reflect the actual test
layout and can be copy/pasted.

│ │ └── using_scenarios.spec.js
│ └── app_commands/ # Rails helper commands
│ ├── clean.rb
│ ├── factory_bot.rb
│ ├── eval.rb
│ └── scenarios/
│ └── basic.rb
```
Comment on lines +334 to +353
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 | 🟠 Major

Test organization mixes Playwright and Cypress structures.

The documented directory structure shows:

  • Playwright-named directories (playwright/)
  • Cypress-specific infrastructure (app_commands/ at line 342, which belongs to cypress-on-rails)
  • Redundant path nesting (e2e/playwright/e2e/ - note "e2e" appears twice)

This inconsistent structure reflects the broader framework confusion in this PR. The actual directory structure should match whichever framework is genuinely being implemented.

Additionally, the path shown at line 268 (e2e/playwright/e2e/react_on_rails/basic_components.spec.js) has redundant "e2e" nesting that should be simplified.

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

330-330: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In CLAUDE.md around lines 329 to 348, the documented test tree mixes Playwright
and Cypress conventions and contains redundant "e2e" nesting; remove the
cypress-on-rails specific items (e.g., app_commands/) if this project uses
Playwright, or conversely convert/playwright-named folders to Cypress
equivalents if using Cypress, and flatten the paths so there is a single "e2e"
level (e.g., change e2e/playwright/e2e/react_on_rails/... to
e2e/playwright/react_on_rails/... or to e2e/react_on_rails/... depending on
chosen layout); update the example paths in the doc (including the path shown at
line ~268) to reflect the chosen framework and flat structure and ensure all
entries consistently follow that layout.


### 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 test:e2e:ui`
- Use `page.pause()` to pause execution
- Check `playwright-report/` for detailed results after test failures
- Enable debug logging in `playwright.config.js`

### CI Integration

Playwright E2E tests run automatically in CI via GitHub Actions (`.github/workflows/playwright.yml`). The workflow:
- Runs on all PRs and pushes to master
- Uses GitHub Actions annotations for test failures
- Uploads HTML reports as artifacts (available for 30 days)
- Auto-starts Rails server before running tests

## 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 @@ -418,6 +420,7 @@ DEPENDENCIES
capybara
capybara-screenshot
coveralls
cypress-on-rails (~> 1.19)
debug
equivalent-xml
gem-release
Expand Down
14 changes: 13 additions & 1 deletion knip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,17 @@ const config: KnipConfig = {
'config/webpack/webpack.config.js',
// SWC configuration for Shakapacker
'config/swc.config.js',
// Playwright E2E test configuration and tests
'e2e/playwright.config.js',
'e2e/playwright/e2e/**/*.spec.js',
// CI workflow files that reference package.json scripts
'../../.github/workflows/playwright.yml',
],
ignore: [
'**/app-react16/**/*',
// Playwright support files and helpers - generated by cypress-on-rails gem
'e2e/playwright/support/**',
],
ignore: ['**/app-react16/**/*'],
project: ['**/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}!', 'config/webpack/*.js'],
paths: {
'Assets/*': ['client/app/assets/*'],
Expand All @@ -127,10 +136,13 @@ const config: KnipConfig = {
'node-libs-browser',
// The below dependencies are not detected by the Webpack plugin
// due to the config issue.
'css-loader',
'expose-loader',
'file-loader',
'imports-loader',
'null-loader',
'sass',
'sass-loader',
'sass-resources-loader',
'style-loader',
'url-loader',
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