• Stars
    star
    402
  • Rank 103,484 (Top 3 %)
  • Language
    HTML
  • License
    MIT License
  • Created over 6 years ago
  • Updated 3 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Use cypress.io or playwright.dev with your rails application

CypressOnRails

Build Status cypress-on-rails Gem Version


This project is sponsored by the software consulting firm ShakaCode, creator of the React on Rails Gem. We focus on React (with TS or ReScript) front-ends, often with Ruby on Rails or Gatsby. See our recent work and client engagement model. Feel free to engage in discussions around this gem at our Slack Channel or our forum category for Cypress.

Interested in joining a small team that loves open source? Check our careers page.

Need help with cypress-on-rails? Contact ShakaCode.


Totally new to Cypress?

Suggest you first learn the basics of Cypress before attempting to integrate with Ruby on Rails

Totally new to Playwright?

Suggest you first learn the basics of Playwright before attempting to integrate with Ruby on Rails

Overview

Gem for using cypress.io or playwright.dev in Rails and Ruby Rack applications with the goal of controlling state as mentioned in Cypress Best Practices

It allows you to run code in the application context 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 setup data
  • create scenario files used for specific tests

Has examples of setting up state with:

  • factory_bot
  • rails test fixtures
  • scenarios
  • custom commands

Resources

Installation

Add this to your Gemfile:

group :test, :development do
  gem 'cypress-on-rails', '~> 1.0'
end

Generate the boilerplate code using:

# by default installs only cypress
bin/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 cypress
bin/rails g cypress_on_rails:install --framework playwright

# if you target the Rails server with a path prefix to your URL
bin/rails g cypress_on_rails:install --api_prefix=/api

# if you want to install with npm instead
bin/rails g cypress_on_rails:install --install_with=npm

# if you already have cypress installed globally
bin/rails g cypress_on_rails:install --install_with=skip

# to update the generated files run
bin/rails g cypress_on_rails:install --install_with=skip

The generator modifies/adds the following files/directory in your application:

  • config/initializers/cypress_on_rails.rb used to configure Cypress on Rails
  • e2e/cypress/integration/ contains your cypress tests
  • e2e/cypress/support/on-rails.js contains Cypress on Rails support code
  • e2e/cypress/e2e_helper.rb contains helper code to require libraries like factory_bot
  • e2e/cypress/app_commands/ contains your scenario definitions
  • e2e/playwright/e2e/ contains your playwright tests
  • e2e/playwright/support/on-rails.js contains Playwright on Rails support code

If you are not using database_cleaner look at e2e/cypress/app_commands/clean.rb. If you are not using factory_bot look at e2e/cypress/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.

Update your database.yml

When writing and running tests on your local computer it's recommended to start your server in development mode so that changes you make are picked up without having to restart your local server. It's recommended you update your database.yml to check if the CYPRESS environment variable is set and switch it to the test database otherwise cypress will keep clearing your development database.

For example:

development:
  <<: *default
  database: <%= ENV['CYPRESS'] ? 'my_db_test' : 'my_db_development' %>
test:
  <<: *default
  database: my_db_test

WARNING

WARNING!!: cypress-on-rails can execute arbitrary ruby code Please use with extra caution if starting your local server on 0.0.0.0 or running the gem on a hosted server

Usage

Getting started on your local environment

# start rails
CYPRESS=1 bin/rails server -p 5017

# in separate window start cypress
yarn cypress open --project ./e2e
# or for npm
npx cypress open --project ./e2e
# or for playwright
yarn playwright test --ui
# or using npm
npx playwright test --ui

How to run cypress on CI

# setup rails and start server in background
# ...

yarn run cypress run --project ./e2e
# or for npm
npx cypress run --project ./e2e

Example of using factory bot

You can run your factory_bot directly as well

// spec/cypress/e2e/simple.cy.js
describe('My First Test', () => {
  it('visit root', () => {
    // This calls to the backend to prepare the application state
    cy.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 test
    cy.visit('/')

    cy.contains('Hello World')

    // Accessing result
    cy.appFactories([['create', 'invoice', { paid: false }]]).then((records) => {
     cy.visit(`/invoices/${records[0].id}`);
    });
  })
})

You can check the association 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 use FactoryBot.build to generate Ruby hashes that can then be used as mock JSON responses:

FactoryBot.define do
  factory :some_web_response, class: Hash do
    initialize_with { attributes.deep_stringify_keys }

    id { 123 }
    name { 'Mr Blobby' }
    occupation { 'Evil pink clown' }
  end
end

FactoryBot.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 response
    cy.appFactories([
      ['build', 'some_web_response', { name: 'Baby Blobby' }]
    ]).then(([responseBody]) => {
      cy.intercept('http://some-external-url.com/endpoint', {
        body: responseBody
      });

      // Visit the application under test
      cy.visit('/')
    })

    cy.contains('Hello World')
  })
})

Example of loading Rails test fixtures

# spec/e2e/app_commands/activerecord_fixtures.rb
require "active_record/fixtures"

fixtures_dir = ActiveRecord::Tasks::DatabaseTasks.fixtures_path
fixture_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_cache
ActiveRecord::FixtureSet.create_fixtures(fixtures_dir, fixture_files)
// spec/cypress/e2e/simple.cy.js
describe('My First Test', () => {
  it('visit root', () => {
    // This calls to the backend to prepare the application state
    cy.appFixtures()

    // Visit the application under test
    cy.visit('/')

    cy.contains('Hello World')
  })
})

Example of using scenarios

Scenarios are named before blocks that you can reference in your test.

You define a scenario in the spec/e2e/app_commands/scenarios directory:

# spec/cypress/app_commands/scenarios/basic.rb
Profile.create name: "Cypress Hill"

# or if you have factory_bot enabled in your cypress_helper
CypressOnRails::SmartFactoryWrapper.create(:profile, name: "Cypress Hill")

Then reference the scenario in your test:

// spec/cypress/e2e/scenario_example.cy.js
describe('My First Test', () => {
  it('visit root', () => {
    // This calls to the backend to prepare the application state
    cy.appScenario('basic')

    cy.visit('/profiles')

    cy.contains('Cypress Hill')
  })
})

Example of using app commands

Create a Ruby file in the spec/e2e/app_commands directory:

# spec/e2e/app_commands/load_seed.rb
load "#{Rails.root}/db/seeds.rb"

Then reference the command in your test with cy.app('load_seed'):

// spec/cypress/e2e/simple.cy.js
describe('My First Test', () => {
  beforeEach(() => { cy.app('load_seed') })

  it('visit root', () => {
    cy.visit('/')

    cy.contains("Seeds")
  })
})

Example of using scenario with Playwright

Scenarios are named before blocks that you can reference in your test.

You define a scenario in the spec/e2e/app_commands/scenarios directory:

# spec/e2e/app_commands/scenarios/basic.rb
Profile.create name: "Cypress Hill"

# or if you have factory_bot enabled in your cypress_helper
CypressOnRails::SmartFactoryWrapper.create(:profile, name: "Cypress Hill")

Then reference the scenario in your test:

// spec/playwright/e2e/scenario_example.spec.js
import { test, expect } from "@playwright/test";
import { app, appScenario } from '../../support/on-rails';

test.describe("Rails using scenarios examples", () => {
  test.beforeEach(async ({ page }) => {
    await app('clean');
  });

  test("setup basic scenario", async ({ page }) => {
    await appScenario('basic');
    await page.goto("/");
  });
});

Experimental Features (matching npm package)

Please test and give feedback.

Add the npm package:

yarn add cypress-on-rails --dev

for VCR

This only works when you start the Rails server with a single worker and single thread

setup

Add your VCR configuration to your cypress_helper.rb

require 'vcr'
VCR.configure do |config|
  config.hook_into :webmock
end

Add to your cypress/support/index.js:

import 'cypress-on-rails/support/index'

Add to your cypress/app_commands/clean.rb:

VCR.eject_cassette # make sure we no cassettes inserted before the next test starts
VCR.turn_off!
WebMock.disable! if defined?(WebMock)

Add to your config/cypress_on_rails.rb:

  c.use_vcr_middleware = !Rails.env.production? && ENV['CYPRESS'].present?

usage

You have vcr_insert_cassette and vcr_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.rb

    cy.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?')
  })
})

before_request configuration

You may perform any custom action before running a CypressOnRails command, such as authentication, or sending metrics. Please set before_request as part of the CypressOnRails configuration.

You should get familiar with Rack 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 return nil.

Authenticate CypressOnRails

  CypressOnRails.configure do |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)
      if body['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

Send usage metrics

  CypressOnRails.configure do |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

Usage with other rack applications

Add CypressOnRails to your config.ru

# an example config.ru
require File.expand_path('my_app', File.dirname(__FILE__))

require 'cypress_on_rails/middleware'
CypressOnRails.configure do |c|
  c.cypress_folder = File.expand_path("#{__dir__}/test/cypress")
end
use CypressOnRails::Middleware

run MyApp

add the following file to Cypress

// test/cypress/support/on-rails.js
// CypressOnRails: don't remove these commands
Cypress.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 optional
beforeEach(() => {
  cy.app('clean') // have a look at cypress/app_commands/clean.rb
});

API Prefix

If your Rails server is exposed under a proxy, typically https://my-local.dev/api, you can use the api_prefix option. In config/initializers/cypress_on_rails.rb, add this line:

  CypressOnRails.configure do |c|
    # ...
    c.api_prefix = '/api'
  end

Contributing

  1. Fork it ( https://github.com/shakacode/cypress-on-rails/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Supporters

JetBrains ScoutAPM Control Plane
BrowserStack Rails Autoscale Honeybadger Reviewable

The following companies support our open source projects, and ShakaCode uses their products!

More Repositories

1

react_on_rails

Integration of React + Webpack + Rails + rails/webpacker including server-side rendering of React, enabling a better developer experience and faster client performance.
Ruby
5,026
star
2

react-webpack-rails-tutorial

Example of integration of Rails, react, redux, using the react_on_rails gem, webpack, enabling the es7 and jsx transpilers, and node integration. And React Native! Live Demo:
Ruby
1,710
star
3

bootstrap-loader

Load Bootstrap styles and scripts in your Webpack bundle
JavaScript
1,024
star
4

sass-resources-loader

SASS resources (e.g. variables, mixins etc.) loader for Webpack. Also works with less, post-css, etc.
JavaScript
978
star
5

shakapacker

Use Webpack to manage app-like JavaScript modules in Rails
Ruby
375
star
6

fat-code-refactoring-techniques

Code samples for RailsConf 2014 on Fat Code Refactoring
JavaScript
288
star
7

re-formality

Form validation tool for reason-react
Reason
244
star
8

bootstrap-sass-loader

Webpack Loader for the Sass version Twitter Bootstrap
JavaScript
118
star
9

rescript-dnd

Drag-n-drop for @rescript/react
ReScript
114
star
10

rescript-classnames

Reimplementation of classnames in ReScript
ReScript
109
star
11

rescript-logger

Logging implementation for ReScript
OCaml
107
star
12

react_on_rails_demo_ssr_hmr

react_on_rails tutorial demonstrating SSR, HMR fast refresh, and Typescript based on the rails/webpacker webpack setup
Ruby
86
star
13

font-awesome-loader

Webpack Loader for Font Awesome Using Sass
JavaScript
47
star
14

webpacker_lite

Slimmed down version of Webpacker with only the asset helpers optimized for, but not requiring, React on Rails
Ruby
46
star
15

mirror-creator

One more way to create an object with values equal to its key names
JavaScript
43
star
16

rescript-debounce

Debounce for ReScript
ReScript
42
star
17

steward

Task runner and process manager for Rust
Rust
36
star
18

heroku-to-control-plane

The power of Kubernetes with the ease of Heroku! Our playbook for migrating from Heroku to Control Plane, controlplane.com, and CPL CLI source
Ruby
35
star
19

reactrails-react-native-client

This repository is for a react-native client to the https://www.reactrails.com/, source at https://github.com/shakacode/react-webpack-rails-tutorial/.
JavaScript
30
star
20

redux-tree

An alternative way to compose Redux reducers
JavaScript
23
star
21

rust-rescript-demo

Rust
22
star
22

messagebus

Message bus - enables async communication between different parts of application using messages
Rust
22
star
23

redux-interactions

Composing UI as a set of interactions
21
star
24

ssr-rs

Server-Side Rendering for Rust web servers using Node.js
Rust
20
star
25

bootstrap-sass-loader-example

Example of using the bootstrap-sass-loder to load the Sass version of Twitter Bootstrap using Webpack
JavaScript
16
star
26

style-guide-javascript

ShakaCode's JavaScript Style Guide (Also see our Ruby one: https://github.com/shakacode/style-guide-ruby)
JavaScript
15
star
27

jstreamer

Ruby
12
star
28

react-validation-layer

An opinionated form validation tool for React apps
JavaScript
11
star
29

justerror

Extension to `thiserror` that helps reduce the amount of handwriting
Rust
11
star
30

rescript-react-on-rails-example

Example of https://rescript-lang.org/ with React on Rails
Ruby
8
star
31

rescript-react-on-rails

BuckleScript bindings to react-on-rails
ReScript
8
star
32

todos-react-native-redux

Todos app with React Native and Redux
JavaScript
7
star
33

conform

Macro to transform struct string fields in place
Rust
7
star
34

rescript-first-class-modules-usage

CSS
6
star
35

rescript-throttle

Throttle for ReScript
ReScript
5
star
36

webpacker-examples

Ruby
5
star
37

package_json

Ruby
4
star
38

react_on_rails-with-webpacker

Prototype of webpacker integration
Ruby
4
star
39

use-ssr-computation.macro

A babel macro for reducing initial bundle-size of Apps with React SSR. Uses conditional compilation to make computations server-side and pass the results to the client.
TypeScript
4
star
40

react_on_rails-generator-results

Results of running different react_on_rails generators. See https://github.com/shakacode/react_on_rails
4
star
41

vosk-rs

Rust
3
star
42

react-starter-kit-with-bootstrap-loader

JavaScript
3
star
43

react_on_rails-generator-results-pre-0

Ruby
2
star
44

js-promises-testing

JavaScript
2
star
45

react-on-rails-demo

Example of using the generator for React on Rails
Ruby
2
star
46

uber_task

Ruby
2
star
47

node-webpack-async-example

Simple example of setting up node with webpack
JavaScript
2
star
48

egghead-add-redux-component-to-react-on-rails

Source for Egghead lesson: Add Redux State Management to a React on Rails Project
Ruby
1
star
49

reactrails-in-reagent

Test of converting reactrails example to reagent
1
star
50

rails-tutorial-with-react-on-rails

Adding React on Rails to the Rails Tutorial
Ruby
1
star
51

.github

1
star
52

react_on_rails-test-new-redux-generation

Testing out a different redux generation. See https://github.com/shakacode/react_on_rails/pull/584
Ruby
1
star
53

yardi

Yet Another Rust Dependency Injector
Rust
1
star
54

old-react-on-rails-examples

Various example apps for React on Rails, outdated
JavaScript
1
star
55

v8-demo

React on Rails v8 Usage of the basic generator and a "generator function example"
Ruby
1
star
56

docker-ci

docker container that runs all test and linting against app
Ruby
1
star
57

react-rails-webpacker-resources

1
star
58

sass-rel-paths-issue

JavaScript
1
star
59

reactrails-in-om-next-example

Test of converting reactrails example to om-next
1
star
60

zone-helper

Swift
1
star
61

react_on_rails-update-webpack-v2

Updating the default React on Rails 6.6.0 redux installation to use Webpack v2 plus adding eslint.
Ruby
1
star
62

shakacode-website

Public website and blog
HTML
1
star
63

react-native-image-zoom-slider

JavaScript
1
star
64

derive-from-one

Automatically generates `From` impls so you don't have to
Rust
1
star
65

rescript-on-rails-example

A sample application using Rescript and React on Rails.
Ruby
1
star