Class: Headless

Inherits:
Object
  • Object
show all
Defined in:
lib/headless.rb,
lib/headless/cli_util.rb,
lib/headless/video/video_recorder.rb

Overview

A class incapsulating the creation and usage of a headless X server

Prerequisites

  • X Window System

  • Xvfb

Usage

Block mode:

require 'rubygems'
require 'headless'
require 'selenium-webdriver'

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

Object mode:

require 'rubygems'
require 'headless'
require 'selenium-webdriver'

headless = Headless.new
headless.start

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

headless.destroy

– TODO test that reuse actually works with an existing xvfb session ++

Defined Under Namespace

Classes: CliUtil, Exception, VideoRecorder

Constant Summary collapse

DEFAULT_DISPLAY_NUMBER =
99
MAX_DISPLAY_NUMBER =
10_000
DEFAULT_DISPLAY_DIMENSIONS =
'1280x1024x24'
DEFAULT_XVFB_LAUNCH_TIMEOUT =
10

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Headless

Creates a new headless server, but does NOT switch to it immediately. Call #start for that

List of available options:

  • display (default 99) - what display number to listen to;

  • reuse (default true) - if given display server already exists, should we use it or try another?

  • autopick (default true if display number isn’t explicitly set) - if Headless should automatically pick a display, or fail if the given one is not available.

  • dimensions (default 1280x1024x24) - display dimensions and depth. Not all combinations are possible, refer to man Xvfb.

  • destroy_at_exit - if a display is started but not stopped, should it be destroyed when the script finishes? (default true unless reuse is true and a server is already running)

  • xvfb_launch_timeout - how long should we wait for Xvfb to open a display, before assuming that it is frozen (in seconds, default is 10)

  • video - options to be passed to the ffmpeg video recorder. See Headless::VideoRecorder#initialize for documentation



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/headless.rb', line 77

def initialize(options = {})
  CliUtil.ensure_application_exists!('Xvfb', 'Xvfb not found on your system')

  @display = options.fetch(:display, DEFAULT_DISPLAY_NUMBER).to_i
  @xvfb_launch_timeout = options.fetch(:xvfb_launch_timeout, DEFAULT_XVFB_LAUNCH_TIMEOUT).to_i
  @autopick_display = options.fetch(:autopick, !options.key?(:display))
  @reuse_display = options.fetch(:reuse, true)
  @dimensions = options.fetch(:dimensions, DEFAULT_DISPLAY_DIMENSIONS)
  @video_capture_options = options.fetch(:video, {})

  already_running = xvfb_running? rescue false
  @destroy_at_exit = options.fetch(:destroy_at_exit, !(@reuse_display && already_running))

  @pid = nil # the pid of the running Xvfb process

  # FIXME Xvfb launch should not happen inside the constructor
  attach_xvfb
end

Instance Attribute Details

#dimensionsObject (readonly)

The display dimensions



56
57
58
# File 'lib/headless.rb', line 56

def dimensions
  @dimensions
end

#displayObject (readonly)

The display number



53
54
55
# File 'lib/headless.rb', line 53

def display
  @display
end

#xvfb_launch_timeoutObject (readonly)

Returns the value of attribute xvfb_launch_timeout.



57
58
59
# File 'lib/headless.rb', line 57

def xvfb_launch_timeout
  @xvfb_launch_timeout
end

Class Method Details

.run(options = {}, &block) ⇒ Object Also known as: ly

Block syntax:

Headless.run do
  # perform operations in headless mode
end

See #new for options



140
141
142
143
144
145
146
# File 'lib/headless.rb', line 140

def self.run(options={}, &block)
  headless = Headless.new(options)
  headless.start
  yield headless
ensure
  headless && headless.destroy
end

Instance Method Details

#destroyObject

Switches back from the headless server and terminates the headless session while waiting for Xvfb process to terminate.



110
111
112
113
# File 'lib/headless.rb', line 110

def destroy
  stop
  CliUtil.kill_process(pid_filename, preserve_pid_file: true, wait: true)
end

#destroy_at_exit?Boolean

Whether the headless display will be destroyed when the script finishes.

Returns:

  • (Boolean)


130
131
132
# File 'lib/headless.rb', line 130

def destroy_at_exit?
  @destroy_at_exit
end

#destroy_syncObject

Deprecated. Same as destroy. Kept for backward compatibility in June 2015.



118
119
120
# File 'lib/headless.rb', line 118

def destroy_sync
  destroy
end

#destroy_without_syncObject

Same as the old destroy function – doesn’t wait for Xvfb to die. Can cause zombies: stackoverflow.com/a/31003621/1651458



124
125
126
127
# File 'lib/headless.rb', line 124

def destroy_without_sync
  stop
  CliUtil.kill_process(pid_filename, preserve_pid_file: true)
end

#startObject

Switches to the headless server



97
98
99
100
101
# File 'lib/headless.rb', line 97

def start
  @old_display = ENV['DISPLAY']
  ENV['DISPLAY'] = ":#{display}"
  hook_at_exit
end

#stopObject

Switches back from the headless server



104
105
106
# File 'lib/headless.rb', line 104

def stop
  ENV['DISPLAY'] = @old_display
end

#take_screenshot(file_path, options = {}) ⇒ Object



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/headless.rb', line 153

def take_screenshot(file_path, options={})
  using = options.fetch(:using, :imagemagick)
  case using
  when :imagemagick
    CliUtil.ensure_application_exists!('import', "imagemagick is not found on your system. Please install it using sudo apt-get install imagemagick")
    system "#{CliUtil.path_to('import')} -display localhost:#{display} -window root #{file_path}"
  when :xwd
    CliUtil.ensure_application_exists!('xwd', "xwd is not found on your system. Please install it using sudo apt-get install X11-apps")
    system "#{CliUtil.path_to('xwd')} -display localhost:#{display} -silent -root -out #{file_path}"
  when :graphicsmagick, :gm
    CliUtil.ensure_application_exists!('gm', "graphicsmagick is not found on your system. Please install it.")
    system "#{CliUtil.path_to('gm')} import -display localhost:#{display} -window root #{file_path}"
  else
    raise Headless::Exception.new('Unknown :using option value')
  end
end

#videoObject



149
150
151
# File 'lib/headless.rb', line 149

def video
  @video_recorder ||= VideoRecorder.new(display, dimensions, @video_capture_options)
end