Chrome as a service for Rails testing

Josef Šimánek
5 min readMar 9, 2019

--

Meanwhile working on next part of Ruby C extension series I have faced different problem.

Since it is more and more painful to install awesome capybara-webkit I have started to look around for some reasonable replacement. My requirements are really simple. It must be really easy to use it locally for developers and it should be possible to run it in containerized environment (Docker on CI in my case). Let’s generate example Rails 5.2 application we can use for testing based on my prepared template.

There’s is one system test created testing some javascript execution. By running our system test suite we can easily check if our solution works. Let’s try it!

If you have Chrome locally installed you should see new browser window for short time used to execute our system test. It works out of box! Nice! Our first requirement is met. There’s no need for any local configuration, no need to install any additional dependencies. All you need is Chrome installed. No more pain having proper Qt installed to compile capybara-webkit locally.

test app running
simple system test passing only when JavaScript is executed

Docker

Let’s slowly prepare application for containerization in Docker by adding simple Dockerfile using thin Alpine official Ruby images.

Pretty simple Dockerfile, isn’t it? We need to install tzdata package and temporarily install build-base and ruby-dev packages to be able to compile nokogiri. Let’s try to build and start our app.

App is running, we can reach it locally at http://localhost:3000/home/index. You should see “Hello from JS” text. Let’s try to run specs in our new container.

Since we are using really thin docker base image (based on Alpine linux) Selenium WebDriver can’t start Chrome because it is not available in image. We can fix it by installing it into the image, but it will almost double our image size and also it will probably lead into two dockerfiles, since we don’t need Chrome installed in production container.

Introducing Chrome as a service

We can take also an alternative approach and use containerized Chrome in our test suite. Since we use Selenium underneath, we can use Selenium Grid hub, attach Chrome instance to it and point our application to use it in system tests when available. To handle dependent services we can use docker-compose. Let’s create docker-compose.yml to start Selenium Grid with Chrome node.

You can see there are 3 environment variables exposed to application we can use later to configure our test suite to be able to reach “remote chrome”.

Using headless Chrome

But let’s start with another change. Starting with Chrome 59 (released in the middle of 2017) you can use “headless” mode. Locally it will result into not showing Chrome window during running system tests. On remote headless servers it removes need of emulating framebuffer (usually done with Xvfb).

We can easily change driven_by in test/application_system_test_case.rb.

driven_by is just fancy API around Capybara drivers. Sometimes it is needed to pass additional arguments to Capybara driver as “disable-gpu” flag.

Running system test suite locally should not create visible Chrome window now.

Use remote headless containerized Chrome

Do you remember those three variables exposed on application container in docker-compose.yml? We can use them to configure Capybara to use remote Chrome.

If environment variable SELENIUM_HUB_URL is present, we will use it to configure remote browser in Capybara driver. System tests start web server same way we do manually during development (via bin/rails s ) and use it via automated browser interaction.

First we need to make tested application URL predictable. I use TEST_APP_PORT and TEST_APP_HOST for that. Capybara.app_host is used as a base URL for Capybara requests. Calling visit "home/index" in our test suite actually visits Capybara.app_host + '/home/index'.

In our docker-compose we have assigned alias web to our service and assigned TEST_APP_PORT to 3001. System tests start web server available at http://web:3001 in our docker-compose prepared network (we need to pass --use-aliases flag to docker-compose run command to make it available from other containers).

We have everything prepared to run our specs using containerized Chrome as a service!

YAY! It works!

What’s happening in background? We use “remote” Selenium browser via Capybara pointing to Selenium Grid Hub. There’s one node with Chrome browser registered in Hub. Selenium remote browser requests Chrome session, Hub prepares one and pass info back to Selenium. Selenium controls remote Chrome running in separated container using internal hostname to reach rails server running in our web container. You can find out more info in docker logs from hub and chrome containers itself.

Example output from hub container:

Example output from Chrome container:

Summary

It took me a while to find out proper way how to link Rails app, Selenium Hub and Selenium Chrome node together. But once this is setup it is really straight forward to use it.

  • I can easily control version of Chrome used in docker-compose.
  • I can still use same Dockerfile for production/testing environment since it doesn’t need any testing only dependencies.
  • There’s no pain installing test-only dependencies locally.
  • I can probably easily add Firefox node in the future and run system tests on different browsers on CI.

Finally we can swap combination of Selenium Grid Hub and Selenium Chrome node images with Selenium Chrome Standalone image (having Grid and Chrome node running the the same time).

To see our new setup in action, we can configure CI to build our app. Let’s try Travis CI by adding simple .travis.yml. We need docker-compose version > 1.20.0 to ensure --use-aliases flag is supported. I use go language in here to prevent installing Ruby.

You can check example build —https://travis-ci.org/simi/rails-system-remote-chrome/jobs/503990343.

You can also follow all steps in my example git repo history https://github.com/simi/rails-system-remote-chrome/commits/master.

Happy testing!

Zalenium alternative update

As redditor ryudice mentioned, there’s also great alternative (extended version) to Selenium Grid maintained by Zalando called Zalenium. You can switch to Zalenium with small change to docker-compose file in testing application. You can also see it in action at example build — https://travis-ci.org/simi/rails-system-remote-chrome/builds/504280014.

Feel free to ping me here, at reddit comments or at @retrorubies with your experiences, alternatives or any troubles following my tutorial .

--

--