Unblock Rails UI Testing with Cypress
We’ve become so accustomed to dealing with or ignoring Selenium/PhantomJS issues that we’ve just come to accept them. There are blog posts and articles that advocate Selenium/PhantomJS but then say things like “intermittent PhantomJS issues.” I mean, I love Capybara with its page DSL and methods like: visit, click_on, fill_in, choose, check, select, page.has_*, expect, find_field|link|button, and within. But as soon as I start to test JS-heavy code, things become problematic.
Does this sound familiar to you: “Hmmm… how do I test this? Let’s try this…” Try, fail, Google, and repeat. Try, fail, Google, and repeat. Then you just give up on Capybara for that feature and set `:js => false`.
The issue (as Kamil Ogorek exposed in Integration Tests Can Be Fun) is that Selenium/PhantomJS are black boxes. Your Ruby test asks Selenium/PhantomJS to invoke a user UI action, then Selenium/PhantomJS then asks the browser. If you don’t have timing issues (which all tests seem to have intermittently) the browser responds to Selenium/PhantomJS which responds back to your test.
Your test code has no knowledge of the browser’s lifecycle. Cypress lives in the browser. It knows about states and event loops and application code. It doesn’t have the timeout issues prevalent with Selenium/PhantomJS based UI testing.
Don’t Trust Me
At this point you may be calling “Bullshit.” Well, click here to see a video of a sample test that works against Cypress’s own web site. Better yet, take a minute, install Cypress, and open its desktop application:
$ npm install cypress
$ cypress open
In the Cypress UI, add an application by clicking on the Add Project link:
Traverse to a working or temporary directory. Then click on the newly added link that has the same name as the folder you selected. You’ll get the following popup:
Click “OK, got it!” then click on the example_spec.js link (that you can see behind the popup above.) And bam!, you are testing:
The sample kitchen sink test has examples for more commands than you might ever need.
Look at the runtime UI. That’s Chrome! Your tests ran in Chrome! (I apologize for the overuse of exclamation points but…. Wow! This is cool stuff!). Anyway, open up Chrome Inspector and you can find the application’s source as well as the Cypress infrastructure code.
Now do this for me: With Chrome Inspection open in the Cypress Desktop app, edit your copy of example_spec.js.
Oops, sorry, Cypress already sensed the change and reran the test (like the guard-rspec Ruby gem.) And Bam! You’re in debug mode inside your tests in the browser:
You can go to the console, look at variables, checkout network requests and responses, whatever. What I do all the time is: 1) use debugger to stop the test, 2) review the state of page variables, 3) test a solution in the Chrome Console, 4) change my test (removing the debugger statement) and it reruns automatically. I’ve found that the development cycle for writing Cypress tests is very fast.
Cypress Promise to Tests
That said, you may still may have timeout issues due to slow responses to Ajax requests. Know that Cypress has a very simple strategy to wait for Ajax calls to complete. And Cypress makes it crazy easy to stub responses (more on that later) so your tests run blazingly fast. Check out my VIM screenshot with a Cypress JSON fixture on the top left (ignoring NERDTree,) a test on the top right, and, on the bottom, setup code that defines browser routes that, when called, will automatically use a fixture and a stub.
Expect a subsequent blog post on building Cypress stubs and fixtures for Rails apps.
I’ve written Cypress tests for Rails applications that had few or no tests. One was a very successful Rails application that made its creator a millionaire. I was brought in to refactor the spaghetti infected Ruby code — some methods of which had over a thousand lines of code. So, yeah, they were experiencing some flakiness when deploying production changes. For that application, writing Cypress tests was the quickest way to verifiy existing behavior and to gain confidence in new code. In a few weeks, the Cypress test suite was quite comprehensive, taking 10 minutes to run in Circle CI (that’s right, Cypress integrates well with your favorite Continuous Integration tool.) I also configured the Ruby Coverband gem to track the lines of Ruby code touched by the test suite. The Coverband report made my client even more comfortable being an early adopter of Cypress.