Capybara: Taming the Hydrochoerus

  • You should be using Capybara version 2.7.1 or higher. Earlier versions do not wait for all sessions to close before kicking off Database cleaner’s truncation. When truncation happens before all sessions are closed, bad things happen (like intermittent failing tests). Waiting and timing is explained in detail below.
  • This applies to you if you app makes PATCH requests: Make sure you are on Phantom JS 2.0 or higher. Note this is a binary to install and on CI server it probably is a global (shell) configuration. (On ours, Semaphore, you need to specify the Phantom JS in the global build commands, not just in your Gemfile.) You to be running on Phantom JS 2.0 or higher to be able to process PATCH requests correctly. When they aren’t, they come through on the server side as empty requests, which can lead to unexpected results.
  • Capybara-webkit sucks. It just does. Don’t use it. The intermittent issues alone are enough to throw it out. Use Poltergeist instead. It was an older technology and by and large it has been replaced by Poltergeist. Experienced developers know this and don’t use webkit for this reason. Junior developers fight in vein trying to get webkit to work and waste lots of time believing in something that simply is a shitty piece of technology.
  • When working with ChromeDriver note that it is annoyingly difficult to open the Developer Tools while the test is running. This is a known-issue, and the Chrome developers advise you pause your test to open Chrome Dev toos. This is explained here.
  • When using Database cleaner with Truncation, make sure you have it in an append_after hook and not in config.after(:each) (several tutorials will mistakenly lead you down the wrong path here.) It should look like this:
config.append_after(:each) do
DatabaseCleaner.clean
end
  • Prefer transaction instead of truncation for all non-Javascript tests (unit tests, controller tests, etc). For Javascript integration specs, you need truncation. An explanation about why can be found at https://github.com/DatabaseCleaner/database_cleaner#rspec-with-capybara-example
  • Use Factories and don’t use fixture data. Fixture data can lead to brittle tests. Generally the entire Rails community has learned from the Dark Days and recommends factories over fixtures.
  • Don’t use connection pooling. Some people on the internet will tell you to use connection pooling to solve thread-locking problems — don’t listen to them. Capybara already has dealt with this under the hood; make sure you are on a recent version of Capybara.
  • Avoid using .trigger. Sometimes if an element isn’t visible Capybara will advise you when it fails you can ‘work around’ the element not being on the page by referencing the element and calling .trigger. You’re just trying to get around the on-screen-and-visible enforcement by Capybara, but this isn’t a good idea. If the thing isn’t on the screen and visible, it probably means there’s a bug and you want to catch that as a failure. Remember your tests are only as valuable as what they catch when things mess up.
  • Circular Dependancy when trying to load ___
    This development issue that causes race condition (intermittent) failures has been explained on this Thoughtbot blog post. To fix if you’re on Rails 4.1 or prior, set allow_concurrency = false in test.rb (Rails 4.1 + earlier only)
config.allow_concurrency = false

Timing

Capybara Native Waiting Behavior

expect(page).to have_content(“xxx”)

Wait Helpers (Ruby metaprograms Javascript)

def wait_for_ajax
counter = 0
while page.evaluate_script(“$.active > 0”)
counter += 1
print “_”
$stdout.flush
sleep(0.1)
if counter >= 100
msg = “AJAX request took longer than 10 seconds.”
if page.driver.respond_to?(:console_messages)
msg << “ console messages at time of failure: “ + page.driver.console_messages.inspect
end
raise msg
end
end
end
def wait_for_your_app
counter = 0
while page.evaluate_script("typeof(yourApp) === 'undefined' || typeof(yourApp._initialized) === 'undefined'")
counter += 1
print "~"
$stdout.flush
raise "Your app failed to initialize after 10 seconds" if counter >= 100
end
end

Explicit Sleeps

Warning for anyone who has an expires_in set as a cache-control header in your controller endpoints (html or json).

if Rails.env.test?
expires_in 0.minutes, :public => false
else
expires_in 3.minutes, :public => true
end

Conclusion

--

--

--

The Answer is Automated Testing

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Map it out: Identify the Problem and Find Solutions.

Get into Facebook Amazon Netflix Google?

Profiling Applications using Shodan

Learning a New Language

Analyze Large CSV Files in Minutes With Docker Compose

Must Read Topics for Oracle Retail Techno Functional Interviews

Making tiny binaries with TCC

Public-Sector Code-Sharing Communities

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Jason Fleetwood-Boldt

Jason Fleetwood-Boldt

The Answer is Automated Testing

More from Medium

Cheatsheet: How to use Tailwind elements in Rails links

All About Hotwire and Turbo

local_assigns What is this?

Upgrade Ruby on Rails Project 101 — Phase 2 (Webpack)