How to Migrate from Capybara Webkit to Webdrivers
We all know testing is important. We have our unit tests and integration tests to make sure everything is working as expected. At OmbuLabs , we use Capybara for our integration tests so that we can interact with the app as a real user would.
This is the process we used to replace the capybara-webkit
gem in a legacy project with a more modern approach that uses the webdrivers
gem and a headless browser.
Why Capybara Webkit
By default, Capybara uses rack-test
as the driver. Unfortunately rack-test
does not support JavaScript. If we want to test things that rely on JavaScript, we need a driver with JS capabilities. Some of these drivers open up a web browser and show us all the activity.
Having the browser show up while the tests are running is not that practical and usually quite slow. The good news is that we can use headless features on browsers like Chrome or Firefox (even Edge). A few years ago the available options were mainly PhantomJS or capybara-webkit
(which uses QtWebKit ), but these options had its development suspended.
Issues with Capybara Webkit
- Development was officially suspended in March. commit
- Depends on QT so it requires the developer to install extra libraries on the system, adding an extra step to make a project run locally, inside docker, or in our CI system.
- It’s difficult to update the webkit engine that runs in the background so you can’t have all the new features you would have on the actual browsers.
Process
We will divide this change into 4 different steps:
- Replace
capybara-webkit
- Make sure specs pass locally
- Make sure specs pass inside the Docker container
- Make sure specs pass in CircleCI
Replacing
First of all we remove capybara-webkit
from the Gemfile and then replace it with the webdrivers
gem (which is already included in any new Rails project).
- gem "capybara-webkit"
+ gem 'webdrivers'
This project was old so it also had an old capybara version that won’t automatically register the browser drivers that we want so we had to register the headless browsers’ drivers in our capybara config:
# from https://github.com/teamcapybara/capybara/blob/c7c22789b7aaf6c1515bf6e68f00bfe074cf8fc1/lib/capybara/registrations/drivers.rb
Capybara.register_driver :headless_firefox do |app|
Capybara::Selenium::Driver.load_selenium
browser_options = ::Selenium::WebDriver::Firefox::Options.new
browser_options.args << '-headless'
Capybara::Selenium::Driver.new(app, browser: :firefox, options: browser_options)
end
Capybara.register_driver :headless_chrome do |app|
Capybara::Selenium::Driver.load_selenium
browser_options = ::Selenium::WebDriver::Chrome::Options.new.tap do |opts|
opts.args << '--headless'
opts.args << '--disable-gpu' if Gem.win_platform?
# Workaround https://bugs.chromium.org/p/chromedriver/issues/detail?id=2650&q=load&sort=-id&colspec=ID%20Status%20Pri%20Owner%20Summary
opts.args << '--disable-site-isolation-trials'
end
Capybara::Selenium::Driver.new(app, browser: :chrome, options: browser_options)
end
You’ll need Chrome or Firefox versions that supports headless mode
Then we can tell Capybara to use one of those drivers for all the tests that require JavaScript:
Capybara.javascript_driver = :headless_chrome # or :headless_firefox
We had an extra step here because a lot of specs had the drivers specified on the actual test describe
block like this:
describe "Do something", js: true, driver: :webkit do
Instead of replacing the driver there, we just removed that option on all the tests so it uses the base config we set above.
Finally, before you run your test suite, make sure you have no references to capybara-webkit in your code (search for capybara-webkit
, Capybara::Webkit
and :webkit
strings).
Fixing Specs
We first make sure tests are working locally so we don’t have errors due to a misconfigured container.
This application uses an old version of Chosen to customize the select
tags. The test suite includes a helper method so the driver can select options from that custom select
and that helper method started failing.
We were using a helper method from this gist that relied too much on page.execute_script
and page.evaluate_script
. The solution for this was to use a different method calling only capybara methods to actually mimic the user interactions.
def select_from_chosen(item_text, options)
- field = find('#' + options[:from], visible: false)
- option_value = page.evaluate_script("$(\"##{field[:id]} option:contains('#{item_text}')\").val()")
- page.execute_script("value = ['#{option_value}']\; if ($('##{field[:id]}').val()) {$.merge(value, $('##{field[:id]}').val())}")
- option_value = page.evaluate_script("value")
- page.execute_script("$('##{field[:id]}').val(#{option_value})")
- page.execute_script("$('##{field[:id]}').trigger('liszt:updated').trigger('change')")
+ field = find_field(options[:from], visible: false)
+ find("##{field[:id]}_chzn").click
+ find("##{field[:id]}_chzn ul.chzn-results li", text: item_text).click
end
On top of updating the gem, we improved the test suite by avoiding obscure JavaScript calls.
Updating Docker Container
Now that our tests are passing we can update the Dockerfile
removing the commands that installed QT’s libraries qt5-default
and libqt5webkit5-dev
(that will depend on the OS your are using, we have a Debian image so we just removed those packes from the apt-get
command).
Then we make sure we have Mozilla Firefox or Google Chrome installed. It was easier to install Firefox since the firefox-esr
package was already available on the Debian Buster image we were using. You can use a Docker image which includes other browsers or install Google Chrome if you want to.
So we changed the default driver to use Firefox:
Capybara.javascript_driver = :headless_firefox
Now our tests can be run inside Docker and we are all green.
Updating CircleCI Config
The last part of the process was to make sure the tests pass on the CI system. We only needed to make sure Firefox was available when running the test, so the easiest solution for this was to use a CircleCI’s Ruby image and use the correct variant so it also includes the most used browsers:
- - image: circleci/ruby:2.4.10-buster-node
+ - image: circleci/ruby:2.4.10-buster-node-browsers
We did have some problems related to caching, so you may want to try to retry your CI job without caching just to make sure if you see something failing.
Conclusion
To sum up we got rid of an outdated dependency (capybara-webkit
) which depended on QT and replaced it with webdrivers
which depends on modern web browsers (e.g. Firefox). And we highly recommend you do it in your legacy Rails applications!
It makes it easier for new developers to join the project, installing QT is quite complicated on some OSs (https://github.com/thoughtbot/capybara-webkit/wiki/Installing-Qt-and-compiling-capybara-webkit) and Firefox (or Chrome) are usually already installed in any web developer’s environment.
It also makes it easier to maintain our Rails application and it gets us closer to “the Rails way”. It will help us when upgrading Rails because more recent versions of Rails use the webdrivers
gem by default.
Another advantage of replacing QtWebKit with a major browser like Chrome and Firefox is that it helped us to find a bug in our current test suite. We had not been properly testing the interactions of the user in those tests related to Chosen, but now we fixed that.
And finally, we can run the complete test suite using different browsers (webdrivers
support Firefox, Chrome, IE and Edge) just by changing one configuration if we need to.