Xvfb

Xvfb is the Ruby interface for Xvfb. It allows you to create a xvfb display straight from Ruby code, hiding the low-level action. It can also capture images and video from the virtual framebuffer. For example, you can record screenshots and screencasts of your failing integration specs.

I created it so I can run Selenium tests in Cucumber without any shell scripting. Even more, you can go xvfb only when you run tests against Selenium. Other possible uses include pdf generation with wkhtmltopdf, or screenshotting.

Documentation is available at rubydoc.info

Changelog

Installation

On Debian/Ubuntu:

sudo apt-get install xvfb
gem install xvfb

Usage

require 'rubygems'
require 'xvfb'
require 'selenium-webdriver'

xvfb = Xvfb.new
xvfb.start

driver = Selenium::WebDriver.for :firefox
driver.navigate.to 'http://google.com'
puts driver.title

xvfb.destroy

Cucumber

Running cucumber xvfb is now as simple as adding a before and after hook in features/support/env.rb:

# change the condition to fit your setup
if Capybara.current_driver == :selenium
  require 'xvfb'

  xvfb = Xvfb.new
  xvfb.start
end

Running tests in parallel

If you have multiple threads running acceptance tests in parallel, you want to spawn Xvfb before forking, and then reuse that instance with destroy_at_exit: false. You can even spawn a Xvfb instance in one ruby script, and then reuse the same instance in other scripts by specifying the same display number and reuse: true.

# spawn_xvfb.rb
Xvfb.new(display: 100, destroy_at_exit: false).start

# test_suite_that_could_be_ran_multiple_times.rb
Xvfb.new(display: 100, reuse: true, destroy_at_exit: false).start

# reap_xvfb.rb 
xvfb = Xvfb.new(display: 100, reuse: true)
xvfb.destroy

# kill_xvfb_without_waiting.rb 
xvfb = Xvfb.new
xvfb.destroy_without_sync

There's also a different approach that creates a new virtual display for every parallel test process - see this implementation by @rosskevin.

Cucumber with wkhtmltopdf

Note: this is true for other programs which may use xvfb at the same time as cucumber is running

When wkhtmltopdf is using Xvfb, and cucumber is invoking a block of code which uses a xvfb session, make sure to override the default display of cucumber to retain browser focus. Assuming wkhtmltopdf is using the default display of 99, make sure to set the display to a value != 99 in features/support/env.rb file. This may be the cause of Connection refused - connect(2) (Errno::ECONNREFUSED).

xvfb = Xvfb.new(:display => '100')
xvfb.start

Capturing video

Video is captured using ffmpeg. You can install it on Debian/Ubuntu via sudo apt-get install ffmpeg or on OS X via brew install ffmpeg. You can capture video continuously or capture scenarios separately. Here is typical use case:

require 'xvfb'

xvfb = Xvfb.new
xvfb.start

Before do
  xvfb.video.start_capture
end

After do |scenario|
  if scenario.failed?
    xvfb.video.stop_and_save("/tmp/#{BUILD_ID}/#{scenario.name.split.join("_")}.mov")
  else
    xvfb.video.stop_and_discard
  end
end

Video options

When initiating Xvfb you may pass a hash with video options.

xvfb = Xvfb.new(:video => { :frame_rate => 12, :codec => 'libx264' })

Available options:

  • :codec - codec to be used by ffmpeg
  • :frame_rate - frame rate of video capture
  • :provider - ffmpeg provider - either :libav (default) or :ffmpeg
  • :provider_binary_path - Explicit path to avconv or ffmpeg. Only required when the binary cannot be discovered on the system $PATH.
  • :pid_file_path - path to ffmpeg pid file, default: "/tmp/.headless_ffmpeg_#@display.pid"
  • :tmp_file_path - path to tmp video file, default: "/tmp/.headless_ffmpeg_#@display.mov"
  • :log_file_path - ffmpeg log file, default: "/dev/null"
  • :extra - array of extra ffmpeg options, default: []

Troubleshooting

Display socket is taken but lock file is missing

This means that there is an X server that is taking up the chosen display number, but its lock file is missing. This is an exceptional situation. Please stop the server process manually (pkill Xvfb) and open an issue.

Video not recording

If video is not recording, and there are no visible exceptions, try passing the following option to Xvfb to figure out the reason: Xvfb.new(video: {log_file_path: STDERR}). In particular, there are some issues with the version of avconv packaged with Ubuntu 12.04 - an outdated release, but still in use on Travis.