• Stars
    star
    312
  • Rank 129,327 (Top 3 %)
  • Language
    Ruby
  • License
    Other
  • Created over 4 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

Helps you write Cypress tests of your Rails app

cypress-rails

This is a simple gem to make it easier to start writing browser tests with Cypress for your Rails apps, regardless of whether your app is server-side rendered HTML, completely client-side JavaScript, or something in-between.

Installation

tl;dr:

  1. Install the npm package cypress
  2. Install this gem cypress-rails
  3. Run rake cypress:init

Installing Cypress itself

The first step is making sure Cypress is installed (that's up to you, this library doesn't install Cypress, it just provides a little Rails-specific glue).

If you're on newer versions of Rails and using webpacker for your front-end assets, then you're likely already using yarn to manage your JavaScript dependencies. If that's the case, you can add Cypress with:

$ yarn add --dev cypress

If you're not using yarn in conjunction with your Rails app, check out the Cypress docs on getting it installed. At the end of the day, this gem just needs the cypress binary to exist either in ./node_modules/.bin/cypress or on your PATH.

Installing the cypress-rails gem

Now, to install the cypress-rails gem, you'll want to add it to your development & test gem groups of your Gemfile, so that you have easy access to its rake tasks:

group :development, :test do
  gem "cypress-rails"
end

Once installed, you'll want to run:

$ rake cypress:init

This will override a few configurations in your cypress.config.js configuration file.

Usage

Develop tests interactively with cypress open

When writing tests with Cypress, you'll find the most pleasant experience (by way of a faster feedback loop and an interactive, easy-to-inspect test runner) using the cypress open command.

When using Rails, however, you'll also want your Rails test server to be running so that there's something for Cypress to interact with. cypress-rails provides a wrapper for running cypress open with a dedicated Rails test server.

So, by running:

$ rake cypress:open

Any JavaScript files added to cypress/integration will be identified by Cypress as tests. Simply click a test file in the Cypress application window to launch the test in a browser. Each time you save the test file, it will re-run itself.

Run tests headlessly with cypress run

To run your tests headlessly (e.g. when you're in CI), you'll want the run command:

$ rake cypress:run

Managing your test data

The tricky thing about browser tests is that they usually depend on some test data being available with which to exercise the app efficiently. Because cypress is a JavaScript-based tool and can't easily manipulate your Rails app directly, cypress-rails provides a number of hooks that you can use to manage your test data.

Here's what a config/initializers/cypress_rails.rb initializer might look like:

return unless Rails.env.test?

CypressRails.hooks.before_server_start do
  # Called once, before either the transaction or the server is started
end

CypressRails.hooks.after_transaction_start do
  # Called after the transaction is started (at launch and after each reset)
end

CypressRails.hooks.after_state_reset do
  # Triggered after `/cypress_rails_reset_state` is called
end

CypressRails.hooks.before_server_stop do
  # Called once, at_exit
end

(You can find an example initializer in this repo.)

The gem also provides a special route on the test server: /cypress_rails_reset_state. Each time it's called, cypress-rails will do two things at the beginning of the next request received by the Rails app:

  • If CYPRESS_RAILS_TRANSACTIONAL_SERVER is enabled, roll back the transaction, effectively resetting the application state to whatever it was at the start of the test run

  • Trigger any after_state_reset hooks you've configured (regardless of the transactional server setting)

This way, you can easily instruct the server to reset its test state from your Cypress tests like so:

beforeEach(() => {
  cy.request('/cypress_rails_reset_state')
})

(Remember, in Cypress, before is a before-all hook and beforeEach is run between each test case!)

Configuration

Environment variables

The cypress-rails gem is configured entirely via environment variables. If you find yourself repeating a number of verbose environment variables as you run your tests, consider invoking the gem from a custom script or setting your preferred environment variables project-wide using a tool like dotenv.

  • CYPRESS_RAILS_DIR (default: Dir.pwd) the directory of your project
  • CYPRESS_RAILS_HOST (default: "127.0.0.1") the hostname to bind to
  • CYPRESS_RAILS_PORT (default: a random available port) the port to run the Rails test server on
  • CYPRESS_RAILS_BASE_PATH (default: "/") the base path for all Cypress's requests to the app (e.g. via cy.visit()). If you've customized your baseUrl setting (e.g. in cypress.config.js), you'll need to duplicate it with this environment variable
  • CYPRESS_RAILS_TRANSACTIONAL_SERVER (default: true) when true, will start a transaction on all database connections before launching the server. In general this means anything done during cypress open or cypress run will be rolled back on exit (similar to running a Rails System test)
  • CYPRESS_RAILS_CYPRESS_OPTS (default: none) any options you want to forward to the Cypress CLI when running its open or run commands.

Example: Running a single spec from the command line

It's a little verbose, but an example of using the above options to run a single Cypress test would look like this:

$ CYPRESS_RAILS_CYPRESS_OPTS="--spec cypress/integration/a_test.js" bin/rake cypress:run

Example: Running your tests in Chromium

By default, Cypress will run its tests in its packaged Electron app, unless you've configured it globally. To choose which browser it will run from the command line, try this:

$ CYPRESS_RAILS_CYPRESS_OPTS="--browser chromium" bin/rake cypress:run

Initializer hooks

before_server_start

Pass a block to CypressRails.hooks.before_server_start to register a hook that will execute before the server or any transaction has been started. If you use Rails fixtures, it may make sense to load them here, so they don't need to be re-inserted for each request

after_server_start

Pass a block to CypressRails.hooks.after_server_start to register a hook that will execute after the server has booted.

after_transaction_start

If there's any custom behavior or state management you want to do inside the transaction (so that it's also rolled back each time a reset is triggered), pass a block to CypressRails.hooks.after_transaction_start.

after_state_reset

Every time the test server receives an HTTP request at /cypress_rails_reset_state, the transaction will be rolled back (if CYPRESS_RAILS_TRANSACTIONAL_SERVER is enabled) and the after_state_reset hook will be triggered. To set up the hook, pass a block to CypressRails.hooks.after_state_reset.

before_server_stop

In case you've made any permanent changes to your test database that could pollute other test suites or scripts, you can use the before_server_stop to (assuming everything exits gracefully) clean things up and restore the state of your test database. To set up the hook, pass a block to CypressRails.hooks.before_server_stop.

Configuring Rails

Beyond the configuration options above, you'll probably also want to disable caching in your Rails app's config/environments/test.rb file, so that changes to your Ruby code are reflected in your tests while you work on them with rake cypress:open. (If either option is set to true, any changes to your Ruby code will require a server restart to be reflected as you work on your tests.)

To illustrate, here's what that might look like in config/environments/test.rb:

config.cache_classes = false
config.action_view.cache_template_loading = false

Setting up continuous integration

Circle CI

Nowadays, Cypress and Circle get along pretty well without much customization. The only tricky bit is that Cypress will install its large-ish binary to ~/.cache/Cypress, so if you cache your dependencies, you'll want to include that path:

version: 2
jobs:
  build:
    docker:
      - image: circleci/ruby:2.6-node-browsers
      - image: circleci/postgres:9.4.12-alpine
        environment:
          POSTGRES_USER: circleci
    steps:
      - checkout

      # Bundle install dependencies
      - type: cache-restore
        key: v1-gems-{{ checksum "Gemfile.lock" }}

      - run: bundle install --path vendor/bundle

      - type: cache-save
        key: v1-gems-{{ checksum "Gemfile.lock" }}
        paths:
          - vendor/bundle

      # Yarn dependencies
      - restore_cache:
          keys:
            - v1-yarn-{{ checksum "yarn.lock" }}
            # fallback to using the latest cache if no exact match is found
            - v1-yarn-

      - run: yarn install

      - save_cache:
          paths:
            - node_modules
            - ~/.cache
          key: v1-yarn-{{ checksum "yarn.lock" }}

      # Run your cypress tests
      - run: bin/rake cypress:run

Why use this?

Rails ships with a perfectly competent browser-testing facility called system tests which depend on capybara to drive your tests, most often with Selenium. All of these tools work, are used by lots of people, and are a perfectly reasonable choice when writing full-stack tests of your Rails application.

So why would you go off the Rails to use Cypress and this gem, adding two additional layers to the Jenga tower of testing facilities that Rails ships with? Really, it comes down to the potential for an improved development experience. In particular:

  • Cypress's IDE-like open command provides a highly visual, interactive, inspectable test runner. Not only can you watch each test run and read the commands as they're executed, Cypress takes a DOM snapshot before and after each command, which makes rewinding and inspecting the state of the DOM trivially easy, something that I regularly find myself losing 20 minutes attempting to do with Capybara
  • cypress open enables an almost REPL-like feedback loop that is much faster and more information dense than using Capybara and Selenium. Rather than running a test from the command line, seeing it fail, then adding a debug breakpoint to a test to try to manipulate the browser or tweaking a call to a Capybara API method, failures tend to be rather obvious when using Cypress and fixing it is usually as easy as tweaking a command, hitting save, and watching it re-run
  • With very few exceptions, a Cypress test that works in a browser window will also pass when run headlessly in CI
  • Cypress selectors are just jQuery selectors, which makes them both more familiar and more powerful than the CSS and XPath selectors offered by Capybara. Additionally, Cypress makes it very easy to drop into a plain synchronous JavaScript function for making more complex assertions or composing repetitive tasks into custom commands
  • Cypress commands are, generally, much faster than analogous tasks in Selenium. Where certain clicks and form inputs will hang for 300-500ms for seemingly no reason when running against Selenium WebDriver, Cypress commands tend to run as fast as jQuery can select and fill an element (which is, of course, pretty fast)
  • By default, Cypress takes a video of every headless test run, taking a lot of the mystery (and subsequent analysis & debugging) out of test failures in CI

Nevertheless, there are trade-offs to attempting this (most notably around Cypress's limited browser support and the complications to test data management), and I wouldn't recommend adopting Cypress and writing a bunch of browser tests for every application. But, if the above points sound like solutions to problems you experience, you might consider trying it out.

Code of Conduct

This project follows Test Double's code of conduct for all community interactions, including (but not limited to) one-on-one communications, public posts/comments, code reviews, pull requests, and GitHub issues. If violations occur, Test Double will take any action they deem appropriate for the infraction, up to and including blocking a user from the organization's repositories.

More Repositories

1

standard

🌟 Ruby Style Guide, with linter & automatic code fixer
Ruby
2,104
star
2

testdouble.js

A minimal test double library for TDD with JavaScript
JavaScript
1,407
star
3

suture

🏥 A Ruby gem that helps you refactor your legacy code
Ruby
1,401
star
4

contributing-tests

1,104
star
5

scripty

Because no one should be shell-scripting inside a JSON file.
JavaScript
957
star
6

test-smells

A workbook repository of example test smells and what to do about them.
JavaScript
421
star
7

jasmine-rails

A Jasmine runner for rails projects that's got you covered in both the terminal and the browser
JavaScript
378
star
8

referral

🕵️‍♀️ Find, filter, and sort your Ruby code's definitions & references
Ruby
343
star
9

good-migrations

Prevent Rails from auto-loading app/ code when running database migrations
Ruby
294
star
10

mocktail

🥃 Take your Ruby, and make it a double!
Ruby
273
star
11

static-rails

Build & serve static sites (e.g. Jekyll, Hugo) from your Rails app
Ruby
149
star
12

maybe_later

Run code after the current Rack response or Rails action completes
Ruby
132
star
13

time_up

⏱ Create and manage multiple timers to tell where your Ruby code's time is going
Ruby
117
star
14

teenytest

A very simple, zero-config test runner for Node.js
JavaScript
97
star
15

test_data

A fast & reliable system for managing your Rails application's test data
Ruby
95
star
16

put

Ruby
92
star
17

theredoc

Makes your multi-line JavaScript strings look good
JavaScript
79
star
18

quibble

Makes it easy to replace require'd dependencies.
JavaScript
78
star
19

react-decoupler

JavaScript
55
star
20

noncommittal

A gem that ensures test isolation by preventing your Rails tests from committing to the database
Ruby
46
star
21

real-world-testing-video

testdouble/real-world-testing + screencasts
JavaScript
40
star
22

clojurescript.csv

A ClojureScript library for reading and writing CSV
Clojure
37
star
23

testdouble-jest

A testdouble.js extension to add support for Jest module mocking
JavaScript
37
star
24

grunt-markdown-blog

Grunt task for building a blog with markdown posts & underscore templates
CoffeeScript
36
star
25

ought

A dumb assertion library with smart diffs for JavaScript
JavaScript
34
star
26

cypress-capybara

Capybara finders re-implemented as custom Cypress commands
JavaScript
33
star
27

minitest-suite

Re-order your Minitest suite into logical sub-suites/groups
Ruby
32
star
28

rust-ffi-example

An example project that shows how to use FFI between Rust and Unity.
Rust
31
star
29

gem_dating

How old is that anyway?
Ruby
29
star
30

rspec-graphql_response

Verify ruby-graphql responses with a :graphql spec type
Ruby
25
star
31

ecto_resource

A simple module to clear up the boilerplate of CRUD resources in Phoenix context files.
Elixir
22
star
32

java-testing-example

An example project that's configured for JUnit and Mocha
Java
20
star
33

real-world-testing

Workshop for Testing JavaScripts
JavaScript
17
star
34

unusual-spending

A code kata for outside-in TDD in Node.js
JavaScript
16
star
35

webpacker-assets-demo

A demo repo to show how to reference images and styles when using Webpacker instead of Sprockets
Ruby
13
star
36

javascript-testing-tactics

The Missing Manual for writing great JavaScript Testing
13
star
37

magic_email_demo

An example Rails app that implements passwordless authentication by emailing a magic link
Ruby
12
star
38

scheduled-merge

Merge PRs on a specified date using Labels
JavaScript
12
star
39

rust-ffi-complex-example

Follow-up project to shows how to use complex data structures between Unity and Rust.
Rust
12
star
40

todos

JavaScript
11
star
41

grunt-asset-fingerprint

CoffeeScript
9
star
42

covet

Instruct a remote Express app to stub APIs via HTTP requests
CoffeeScript
9
star
43

bored

Gives you ideas of stuff to do when you're bored
Ruby
8
star
44

rails-twitter-oauth-example

An example Rails app that implements log in to Twitter via OAuth
Ruby
8
star
45

javascript-tdd-examples

Yet another little toy repo of javascript tdd examples
JavaScript
8
star
46

baizen

BAI file format parser
Clojure
8
star
47

tiny_type

Fast, easy, and simple runtime type checking for Ruby
Ruby
8
star
48

halfpipe

A Pipedrive client for Ruby that doesn't do half of what you want it to 🛹
Ruby
7
star
49

forewarn

Configure method invocation warnings for deprecated or dangerous methods (e.g. mutable methods in default-frozen String literals in Ruby 3)
Ruby
7
star
50

dependable

A minimalist dependency injection framework for node.js
JavaScript
7
star
51

grunt-jasmine-bundle

A "spec" grunt task for Jasmine that includes a standard pack of helpers (jasmine-given, jasmine-stealth, jasmine-only). Uses minijasminenode.
CoffeeScript
6
star
52

servme

gimme for integration tests
Ruby
6
star
53

intro-to-node

Introduction to Node.js Workshop
JavaScript
6
star
54

standardrb

You're probably in the wrong place. This is an alias for the gem standard, whose binary is standardrb
Ruby
6
star
55

bootboot-example

An example of using boot-boot.
Ruby
5
star
56

testdrivennode

Test Driven Node.js Precompiler for Codemash 2014
JavaScript
5
star
57

docunit

Makes sure the code examples in your docs actually work
CoffeeScript
5
star
58

railsconf-test-drive-javascript

JavaScript
5
star
59

json-to-svg-to-pdf

Converts JSON/CSON input through SVG templates and renders them to PDF using librsvg
JavaScript
5
star
60

imagemagick-macos-font-setup

Sets up user fonts for imagemagick on macOS
Shell
5
star
61

jasmine-before-all

Adds a done-friendly beforeAll global function to Jasmine
JavaScript
5
star
62

good-day

An example ember + active_model_serializers + rails + lineman app
JavaScript
5
star
63

sockem

A wrapper around the ActionCable JS client to ensure eventual delivery for requests
Ruby
5
star
64

satisfaction

Satisfaction tracker for your work!
Ruby
5
star
65

headerify

Browserify plugin to add a comment containing lib name, version, description, and homepage to the top of the bundle
JavaScript
4
star
66

SublimeLinter-contrib-standardrb

SublimeLinter 3 plugin for Ruby, using Standard, a wrapper for Rubocop.
Python
4
star
67

rails-upsert-all-demo

An example app that demos use of Rails 6 `upsert_all` method
Ruby
4
star
68

cobbler

A tool to generate résumés for Test Double agents.
JavaScript
3
star
69

react-d3-blog-example

Example for Blog Post
JavaScript
3
star
70

supertitle

Converts between subtitles and transcript formats
Ruby
3
star
71

time_traveler_demo

A Rails app that demoes time traveling both Ruby and Postgres in lock-step with one another
Ruby
3
star
72

devops-standards

Standard Auditing Tools for DevSecOps best practices
Python
3
star
73

least

A pager that can dynamically filter log lines
Ruby
3
star
74

defuse

An API to define and use JavaScript in a module-y way. And nothing else.
JavaScript
3
star
75

lockify

Ensure an async function does not run concurrently.
JavaScript
3
star
76

jasmine-matcher-wrapper

A utility to wrap Jasmine 1.x argument matchers for use under Jasmine 2.x
CoffeeScript
3
star
77

testdouble-nock

JavaScript
3
star
78

teenytest-promise

Promise support for asynchronous teenytest tests
JavaScript
3
star
79

npm-tree

Generates a tree of all the node.js modules depended on by a module
CoffeeScript
3
star
80

function-names-at-line

Name the functions found at a particular line number in some JavaScript source
JavaScript
2
star
81

tradecraft

CSS
2
star
82

course-cypress-intro-demo-app

Demo application to supplement Test Double's End-to-end Testing with Cypress intro video course
Ruby
2
star
83

standard-ruby-action

Ruby
2
star
84

rails-training-201

A demo app for Rails 201 students to build on!
Ruby
2
star
85

testdrivennode-frontend

JavaScript
2
star
86

yslow-grader

A little Node.js wrapper for YSlow for PhantomJS
CoffeeScript
2
star
87

ios-learnins

Objective-C
2
star
88

fetcher

Fetches things based on a JSON recipe hosted in a repository
CoffeeScript
2
star
89

backbone-fixins

Boilerplate that strengthens your backbone
JavaScript
2
star
90

ruby_rails_training_github

Ruby
1
star
91

prioritize-api

Elixir
1
star
92

baruco2014-angular

Ruby
1
star
93

oredev2014-angular

JavaScript
1
star
94

double-up

Slack scheduler to set up rotating brunch pairings
Ruby
1
star
95

elm-testdouble

A minimal test double library for TDD with Elm
Elm
1
star
96

doubot

test double's hubot
CoffeeScript
1
star
97

jasmine-example

JavaScript
1
star
98

arg-that

arg-that makes it easier to assert equality on complex objects
Ruby
1
star
99

react-routing-example

Example for screencast on client-side routing in React
CSS
1
star
100

cucumber-peel

Provides a CLI to search a project's step implementations for a given step
Ruby
1
star