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
95
96
97
98
99
100
# 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, {})
  @extensions = options.fetch(:extensions, [])
  @extensions = [@extensions] unless @extensions.is_a? Array

  already_running = begin
    xvfb_running?
  rescue
    false
  end
  @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



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

def dimensions
  @dimensions
end

#displayObject (readonly)

The display number



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

def display
  @display
end

#xvfb_launch_timeoutObject (readonly)

Returns the value of attribute xvfb_launch_timeout.



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

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



146
147
148
149
150
151
152
# File 'lib/headless.rb', line 146

def self.run(options = {}, &block)
  headless = Headless.new(options)
  headless.start
  yield headless
ensure
  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.



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

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)


136
137
138
# File 'lib/headless.rb', line 136

def destroy_at_exit?
  @destroy_at_exit
end

#destroy_syncObject

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



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

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



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

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

#startObject

Switches to the headless server



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

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

#stopObject

Switches back from the headless server



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

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

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



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/headless.rb', line 159

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 :#{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



155
156
157
# File 'lib/headless.rb', line 155

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