Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork62
Use cypress.io or playwright.dev with your rails application. This Ruby gem lets you use your regular Rails test setup and clean-up, such as FactoryBot.
License
shakacode/cypress-playwright-on-rails
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
This project is sponsored by the software consulting firmShakaCode, creator of theReact on Rails Gem.
ShakaCode focuses on helping Ruby on Rails teams use React and Webpack better. We can upgrade your project and improve your development and customer experiences, allowing you to focus on building new features or fixing bugs instead.
For an overview of working with us, see ourClient Engagement Model article andhow we bill for time.
We also specialize in helping development teams lower infrastructure and CI costs. Check out our projectControl Plane Flow, which can allow you to get the ease of Heroku with the power of Kubernetes and big cost savings.
If you think ShakaCode can help your project,click here to book a call withJustin Gordon, the creator of React on Rails and Shakapacker.
Here's a testimonial of how ShakaCode can help fromFlorian Gößler ofBlinkist, January 2, 2023:
Hey Justin 👋
I just wanted to let you know that we today shipped the webpacker to shakapacker upgrades and it all seems to be running smoothly! Thanks again for all your support and your teams work! 😍
On top of your work, it was now also very easy for me to upgrade Tailwind and include our external node_module based web component library which we were using for our other (more modern) apps already. That work is going to be shipped later this week though as we are polishing the last bits of it. 😉
Have a great 2023 and maybe we get to work together again later in the year! 🙌
Read thefull review here.
Feel free to engage in discussions around this gem at ourSlack Channel or ourforum category for Cypress.
Need help with cypress-on-rails? ContactJustin Gordon.
Consider first learning the basics of Cypress before attempting to integrate with Ruby on Rails.
Consider first learning the basics of Playwright before attempting to integrate with Ruby on Rails.
# 1. Add to Gemfilegem'cypress-on-rails','~> 1.0'# 2. Install and generatebundle installbin/rails g cypress_on_rails:install# 3. Run tests (new rake tasks!)bin/rails cypress:open# Open Cypress UIbin/rails cypress:run# Run headless
For Playwright:
bin/rails g cypress_on_rails:install --framework playwrightbin/rails playwright:open# Open Playwright UIbin/rails playwright:run# Run headless
Gem for usingcypress.io orplaywright.dev in Rails and Ruby Rack applications to control state as mentioned inCypress Best Practices.
It allows you to run code in the context of the application when executing Cypress or Playwright tests.Do things like:
- use database_cleaner before each test
- seed the database with default data for each test
- use factory_bot to set up data
- create scenario files used for specific tests
Has examples of setting up state with:
- factory_bot
- rails test fixtures
- scenarios
- custom commands
- Best Practices Guide - Recommended patterns and practices
- Troubleshooting Guide - Solutions to common issues
- Playwright Guide - Complete Playwright documentation
- VCR Integration Guide - HTTP recording and mocking
- DX Improvements - Recent improvements based on user feedback
Add this to yourGemfile:
group:test,:developmentdogem'cypress-on-rails','~> 1.0'end
Generate the boilerplate code using:
# by default installs only cypressbin/rails g cypress_on_rails:install# if you have/want a different cypress folder (default is e2e)bin/rails g cypress_on_rails:install --install_folder=spec/cypress# to install playwright instead of cypressbin/rails g cypress_on_rails:install --framework playwright# if you target the Rails server with a path prefix to your URLbin/rails g cypress_on_rails:install --api_prefix=/api# if you want to install with npm insteadbin/rails g cypress_on_rails:install --install_with=npm# if you already have cypress installed globallybin/rails g cypress_on_rails:install --install_with=skip# to update the generated files runbin/rails g cypress_on_rails:install --install_with=skip
The generator creates the following structure in your application:
For Cypress:
e2e/ cypress.config.js # Cypress configuration e2e_helper.rb # Helper code for factory_bot, database_cleaner, etc. app_commands/ # Your custom commands and scenarios clean.rb factory_bot.rb scenarios/ basic.rb fixtures/ vcr_cassettes/ # VCR recordings (if using VCR) cypress/ support/ index.js commands.js on-rails.js # Cypress on Rails support code e2e/ rails_examples/ # Example testsFor Playwright:
e2e/ playwright.config.js # Playwright configuration e2e_helper.rb # Helper code for factory_bot, database_cleaner, etc. app_commands/ # Your custom commands and scenarios (shared with Cypress) fixtures/ vcr_cassettes/ # VCR recordings (if using VCR) playwright/ support/ index.js on-rails.js # Playwright on Rails support code e2e/ rails_examples/ # Example testsAdditional files:
config/initializers/cypress_on_rails.rb- Configuration for Cypress on Rails
Important: Note thate2e_helper.rb andapp_commands/ are at the root of the install folder (e.g.,e2e/), NOT inside the framework subdirectory (e.g.,e2e/cypress/). This allows both Cypress and Playwright to share the same commands and helpers when using both frameworks.
If you are not usingdatabase_cleaner look ate2e/app_commands/clean.rb.If you are not usingfactory_bot look ate2e/app_commands/factory_bot.rb.
Now you can create scenarios and commands that are plain Ruby files that get loaded through middleware, the ruby sky is your limit.
When writing and running tests on your local computer, it's recommended to start your server in development mode so that changes youmake are picked up without having to restart your local server.
It's recommended you update yourdatabase.yml to check if theCYPRESS environment variable is set and switch it to the test databaseotherwise, cypress will keep clearing your development database.
For example:
development:<<:*defaultdatabase:<%= ENV['CYPRESS'] ? 'my_db_test' : 'my_db_development' %>test:<<:*defaultdatabase:my_db_test
WARNING!!: cypress-on-rails can execute arbitrary ruby codePlease use with extra caution if starting your local server on 0.0.0.0 or running the gem on a hosted server
Getting started on your local environment
The easiest way to run tests is using the provided rake tasks, which automatically manage the Rails server:
# For Cypressbin/rails cypress:open# Opens Cypress test runner UIbin/rails cypress:run# Runs Cypress tests in headless mode# For Playwrightbin/rails playwright:open# Opens Playwright test runner UIbin/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
You can also manage the server manually:
# start railsCYPRESS=1 bin/rails server -p 5017# in separate window start cypressyarn cypress open --project ./e2e# or for npmnpx cypress open --project ./e2e# or for playwrightyarn playwrighttest --ui# or using npmnpx playwrighttest --ui
How to run cypress on CI
# setup rails and start server in background# ...yarn run cypress run --project ./e2e# or for npmnpx cypress run --project ./e2e
You can run yourfactory_bot directly as well
then in Cypress
// spec/cypress/e2e/simple.cy.jsdescribe('My First Test',()=>{it('visit root',()=>{// This calls to the backend to prepare the application statecy.appFactories([['create_list','post',10],['create','post',{title:'Hello World'}],['create','post','with_comments',{title:'Factory_bot Traits here'}]// use traits])// Visit the application under testcy.visit('/')cy.contains('Hello World')// Accessing resultcy.appFactories([['create','invoice',{paid:false}]]).then((records)=>{cy.visit(`/invoices/${records[0].id}`);});})})
then in Playwright
const{ test, expect, request}=require('@playwright/test');test.describe('My First Test',()=>{test('visit root',async({ page})=>{// This calls to the backend to prepare the application stateawaitappFactories([['create_list','post',10],['create','post',{title:'Hello World'}],['create','post','with_comments',{title:'Factory_bot Traits here'}]]);// Visit the application under testawaitpage.goto('/');awaitexpect(page).toHaveText('Hello World');// Accessing resultconstrecords=awaitappFactories([['create','invoice',{paid:false}]]);awaitpage.goto(`/invoices/${records[0].id}`);});});
You can check theassociation docs on more ways to setup association with the correct data.
In some cases, using static Cypress fixtures may not provide sufficient flexibility when mocking HTTP response bodies. It's possible to useFactoryBot.build to generate Ruby hashes that can then be used as mock JSON responses:
FactoryBot.definedofactory:some_web_response,class:Hashdoinitialize_with{attributes.deep_stringify_keys}id{123}name{'Mr Blobby'}occupation{'Evil pink clown'}endendFactoryBot.build(:some_web_response=>{'id'=>123,'name'=>'Mr Blobby','occupation'=>'Evil pink clown'})
This can then be combined with Cypress mocks:
describe('My First Test',()=>{it('visit root',()=>{// This calls to the backend to generate the mocked responsecy.appFactories([['build','some_web_response',{name:'Baby Blobby'}]]).then(([responseBody])=>{cy.intercept('http://some-external-url.com/endpoint',{body:responseBody});// Visit the application under testcy.visit('/')})cy.contains('Hello World')})})
# spec/e2e/app_commands/activerecord_fixtures.rbrequire"active_record/fixtures"fixtures_dir=ActiveRecord::Tasks::DatabaseTasks.fixtures_pathfixture_files=Dir["#{fixtures_dir}/**/*.yml"].map{ |f|f[(fixtures_dir.size +1)..-5]}logger.debug"loading fixtures: { dir:#{fixtures_dir}, files:#{fixture_files} }"ActiveRecord::FixtureSet.reset_cacheActiveRecord::FixtureSet.create_fixtures(fixtures_dir,fixture_files)
// spec/cypress/e2e/simple.cy.jsdescribe('My First Test',()=>{it('visit root',()=>{// This calls to the backend to prepare the application statecy.appFixtures()// Visit the application under testcy.visit('/')cy.contains('Hello World')})})
Scenarios are namedbefore blocks that you can reference in your test.
You define a scenario in thespec/e2e/app_commands/scenarios directory:
# spec/cypress/app_commands/scenarios/basic.rbProfile.createname:"Cypress Hill"# or if you have factory_bot enabled in your cypress_helperCypressOnRails::SmartFactoryWrapper.create(:profile,name:"Cypress Hill")
Then reference the scenario in your test:
// spec/cypress/e2e/scenario_example.cy.jsdescribe('My First Test',()=>{it('visit root',()=>{// This calls to the backend to prepare the application statecy.appScenario('basic')cy.visit('/profiles')cy.contains('Cypress Hill')})})
Create a Ruby file in thespec/e2e/app_commands directory:
# spec/e2e/app_commands/load_seed.rbload"#{Rails.root}/db/seeds.rb"
Then reference the command in your test withcy.app('load_seed'):
// spec/cypress/e2e/simple.cy.jsdescribe('My First Test',()=>{beforeEach(()=>{cy.app('load_seed')})it('visit root',()=>{cy.visit('/')cy.contains("Seeds")})})
Scenarios are namedbefore blocks that you can reference in your test.
You define a scenario in thespec/e2e/app_commands/scenarios directory:
# spec/e2e/app_commands/scenarios/basic.rbProfile.createname:"Cypress Hill"# or if you have factory_bot enabled in your cypress_helperCypressOnRails::SmartFactoryWrapper.create(:profile,name:"Cypress Hill")
Then reference the scenario in your test:
// spec/playwright/e2e/scenario_example.spec.jsimport{test,expect}from"@playwright/test";import{app,appScenario}from'../../support/on-rails';test.describe("Rails using scenarios examples",()=>{test.beforeEach(async({ page})=>{awaitapp('clean');});test("setup basic scenario",async({ page})=>{awaitappScenario('basic');awaitpage.goto("/");});});
Please test and give feedback.
Add the npm package:
yarn add cypress-on-rails --devThis only works when you start the Rails server with a single worker and single threadIt can be used in two modes:
- with separate insert/eject calls (more general, recommended way)
- with use_cassette wrapper (supports only GraphQL integration)
Add your VCR configuration to yourconfig/cypress_on_rails.rb
c.vcr_options={hook_into::webmock,default_cassette_options:{record::once},cassette_library_dir:File.expand_path("#{__dir__}/../../e2e/fixtures/vcr_cassettes")}
Add to yourcypress/support/index.js:
import'cypress-on-rails/support/index'
Add to yourcypress/app_commands/clean.rb:
VCR.eject_cassette# make sure we no cassettes inserted before the next test startsVCR.turn_off!WebMock.disable!ifdefined?(WebMock)
Add to yourconfig/cypress_on_rails.rb:
c.use_vcr_middleware= !Rails.env.production? &&ENV['CYPRESS'].present?# c.use_vcr_use_cassette_middleware = !Rails.env.production? && ENV['CYPRESS'].present?
You havevcr_insert_cassette andvcr_eject_cassette available.https://www.rubydoc.info/github/vcr/vcr/VCR:insert_cassette
describe('My First Test',()=>{beforeEach(()=>{cy.app('load_seed')})it('visit root',()=>{cy.app('clean')// have a look at e2e/app_commands/clean.rbcy.vcr_insert_cassette('cats',{record:"new_episodes"})cy.visit('/using_vcr/index')cy.get('a').contains('Cats').click()cy.contains('Wikipedia has a recording of a cat meowing, because why not?')cy.vcr_eject_cassette()cy.vcr_insert_cassette('cats')cy.visit('/using_vcr/record_cats')cy.contains('Wikipedia has a recording of a cat meowing, because why not?')})})
Add to yourconfig/cypress_on_rails.rb:
# c.use_vcr_middleware = !Rails.env.production? && ENV['CYPRESS'].present?c.use_vcr_use_cassette_middleware= !Rails.env.production? &&ENV['CYPRESS'].present?
Adjust record mode inconfig/cypress_on_rails.rb if needed:
c.vcr_options={hook_into::webmock,default_cassette_options:{record::once},}
Add to yourcypress/support/command.js:
// Add proxy-like mock to add operation name into query stringCypress.Commands.add('mockGraphQL',()=>{cy.on('window:before:load',(win)=>{constoriginalFetch=win.fetch;constfetch=(path,options, ...rest)=>{if(options&&options.body){try{constbody=JSON.parse(options.body);if(body.operationName){returnoriginalFetch(`${path}?operation=${body.operationName}`,options, ...rest);}}catch(e){returnoriginalFetch(path,options, ...rest);}}returnoriginalFetch(path,options, ...rest);};cy.stub(win,'fetch',fetch);});});
Add to yourcypress/support/on-rails.js, tobeforeEach:
cy.mockGraphQL()// for GraphQL usage with use_cassette, see cypress/support/commands.rb
There is nothing special to be called during the Cypress scenario. Each request is wrapped withVCR.use_cassette.Consider VCR configuration incypress_helper.rb to ignore hosts.
All cassettes will be recorded and saved automatically, using the pattern<vcs_cassettes_path>/graphql/<operation_name>
When using the rake tasks (cypress:open,cypress:run,playwright:open,playwright:run), you can configure lifecycle hooks to customize test server behavior:
CypressOnRails.configuredo |c|# Run code before Rails server startsc.before_server_start=->{puts"Preparing test environment..."}# Run code after Rails server is readyc.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 resetc.after_state_reset=->{Rails.cache.clear}# Run code before Rails server stopsc.before_server_stop=->{puts"Cleaning up test environment..."}# Configure server settingsc.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 rollbackend
You may perform any custom action before running a CypressOnRails command, such as authentication, or sending metrics. Please setbefore_request as part of the CypressOnRails configuration.
You should get familiar withRack middlewares.If your function returns a[status, header, body] response, CypressOnRails will halt, and your command will not be executed. To execute the command,before_request should returnnil.
CypressOnRails.configuredo |c|# ...# Refer to https://www.rubydoc.info/gems/rack/Rack/Request for the `request` argument.c.before_request=lambda{ |request|body=JSON.parse(request.body.string)ifbody['cypress_token'] !=ENV.fetch('SWEEP_CYPRESS_SECRET_TOKEN')# You may also use warden for authentication:# if !request.env['warden'].authenticate(:secret_key)return[401,{},['unauthorized']]end}end
CypressOnRails.configuredo |c|# ...# Refer to https://www.rubydoc.info/gems/rack/Rack/Request for the `request` argument.c.before_request=lambda{ |request|statsd=Datadog::Statsd.new('localhost',8125)statsd.increment('cypress_on_rails.requests')}end
Add CypressOnRails to your config.ru
# an example config.rurequireFile.expand_path('my_app',File.dirname(__FILE__))require'cypress_on_rails/middleware'CypressOnRails.configuredo |c|c.cypress_folder=File.expand_path("#{__dir__}/test/cypress")enduseCypressOnRails::MiddlewarerunMyApp
add the following file to Cypress
// test/cypress/support/on-rails.js// CypressOnRails: don't remove these commandsCypress.Commands.add('appCommands',(body)=>{cy.request({method:'POST',url:'/__cypress__/command',body:JSON.stringify(body),headers:{'Content-Type':'application/json; charset=utf-8',},log:true,failOnStatusCode:true})});Cypress.Commands.add('app',(name,command_options)=>{cy.appCommands({name:name,options:command_options})});Cypress.Commands.add('appScenario',(name)=>{cy.app('scenarios/'+name)});Cypress.Commands.add('appFactories',(options)=>{cy.app('factory_bot',options)});// CypressOnRails: end// The next is optionalbeforeEach(()=>{cy.app('clean')// have a look at cypress/app_commands/clean.rb});
add the following file to Playwright
// test/playwright/support/on-rails.jsasyncfunctionappCommands(body){constcontext=awaitrequest.newContext();constresponse=awaitcontext.post('/__e2e__/command',{data:body,headers:{'Content-Type':'application/json'}});if(response.status()!==201){constresponseBody=awaitresponse.text();thrownewError(`Expected status 201 but got${response.status()} -${responseBody}`);}returnresponse.json();}asyncfunctionapp(name,commandOptions={}){constbody=awaitappCommands({ name,options:commandOptions});returnbody[0];}asyncfunctionappScenario(name,options={}){constbody={name:`scenarios/${name}`, options};constresult=awaitappCommands(body);returnresult[0];}asyncfunctionappFactories(options){returnapp('factory_bot',options);}asyncfunctionclean(){awaitapp('clean');}
If your Rails server is exposed under a proxy, typicallyhttps://my-local.dev/api, you can use theapi_prefix option.Inconfig/initializers/cypress_on_rails.rb, add this line:
CypressOnRails.configuredo |c|# ...c.api_prefix='/api'end
- Fork it (https://github.com/shakacode/cypress-on-rails/fork )
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request




The following companies support our open source projects, and ShakaCode uses their products!
About
Use cypress.io or playwright.dev with your rails application. This Ruby gem lets you use your regular Rails test setup and clean-up, such as FactoryBot.
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Sponsor this project
Uh oh!
There was an error while loading.Please reload this page.
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.