headless 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +3 -0
- data/CHANGELOG +6 -1
- data/Gemfile +4 -0
- data/README.md +31 -3
- data/Rakefile +1 -0
- data/headless.gemspec +18 -0
- data/lib/headless.rb +43 -21
- data/lib/headless/cli_util.rb +52 -0
- data/lib/headless/video/video_recorder.rb +46 -0
- data/spec/headless_spec.rb +125 -0
- data/spec/video_recorder_spec.rb +60 -0
- metadata +33 -11
data/.gitignore
ADDED
data/CHANGELOG
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
-
## 0.1
|
1
|
+
## 0.2.1 (2011-08-26)
|
2
|
+
* added ability to capture screenshots (from https://github.com/iafonov/headless)
|
3
|
+
* added ability to capture video (from https://github.com/iafonov/headless)
|
4
|
+
* fixed issue with stray pidfile
|
5
|
+
|
6
|
+
## 0.1.0 (2010-08-15)
|
2
7
|
* introduced options
|
3
8
|
* make it possible to change virtual screen dimensions and pixel depth
|
data/Gemfile
ADDED
data/README.md
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
# Headless
|
2
2
|
|
3
3
|
Headless is a Ruby interface for Xvfb. It allows you to create a headless display straight from Ruby code, hiding some low-level action.
|
4
|
+
It can also capture images and video from the virtual framebuffer.
|
4
5
|
|
5
6
|
I created it so I can run Selenium tests in Cucumber without any shell scripting. Even more, you can go headless only when you run tests against Selenium.
|
6
7
|
Other possible uses include pdf generation with `wkhtmltopdf`, or screenshotting.
|
7
8
|
|
9
|
+
Documentation is available at [rdoc.info](http://rdoc.info/projects/leonid-shevtsov/headless)
|
10
|
+
|
8
11
|
## Installation
|
9
12
|
|
10
13
|
On Debian/Ubuntu:
|
@@ -52,12 +55,37 @@ Running cucumber headless is now as simple as adding a before and after hook in
|
|
52
55
|
|
53
56
|
headless = Headless.new
|
54
57
|
headless.start
|
58
|
+
end
|
59
|
+
|
60
|
+
## Capturing video
|
61
|
+
|
62
|
+
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:
|
55
63
|
|
56
|
-
|
57
|
-
|
64
|
+
require 'headless'
|
65
|
+
|
66
|
+
headless = Headless.new
|
67
|
+
headless.start
|
68
|
+
|
69
|
+
Before do
|
70
|
+
headless.video.start_capture
|
71
|
+
end
|
72
|
+
|
73
|
+
After do |scenario|
|
74
|
+
if scenario.failed?
|
75
|
+
headless.video.stop_and_save("/tmp/#{BUILD_ID}/#{scenario.name.split.join("_")}.mov")
|
76
|
+
else
|
77
|
+
headless.video.stop_and_discard
|
58
78
|
end
|
59
79
|
end
|
60
80
|
|
81
|
+
## Taking screenshots
|
82
|
+
|
83
|
+
Images are captured using `import` utility which is part of `imagemagick` library. You can install it on Ubuntu via `sudo apt-get install imagemagick`. You can call `headless.take_screenshot` at any time. You have to supply full path to target file. File format is determined by supplied file extension.
|
84
|
+
|
85
|
+
## Contributors
|
86
|
+
|
87
|
+
* [Igor Afonov](http://iafonov.github.com) - video and screenshot capturing functionality.
|
88
|
+
|
61
89
|
---
|
62
90
|
|
63
|
-
©
|
91
|
+
© 2011 Leonid Shevtsov, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/headless.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
spec = Gem::Specification.new do |s|
|
2
|
+
s.author = 'Leonid Shevtsov'
|
3
|
+
s.email = 'leonid@shevtsov.me'
|
4
|
+
|
5
|
+
s.name = 'headless'
|
6
|
+
s.version = '0.2.1'
|
7
|
+
s.summary = 'Ruby headless display interface'
|
8
|
+
|
9
|
+
s.description = <<-EOF
|
10
|
+
Headless is a Ruby interface for Xvfb. It allows you to create a headless display straight from Ruby code, hiding some low-level action.
|
11
|
+
EOF
|
12
|
+
s.requirements = 'Xvfb'
|
13
|
+
s.homepage = 'http://leonid.shevtsov.me/en/headless'
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
|
17
|
+
s.add_development_dependency "rspec", "~> 2.6"
|
18
|
+
end
|
data/lib/headless.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'headless/cli_util'
|
2
|
+
require 'headless/video/video_recorder'
|
3
|
+
|
1
4
|
# A class incapsulating the creation and usage of a headless X server
|
2
5
|
#
|
3
6
|
# == Prerequisites
|
@@ -38,7 +41,7 @@
|
|
38
41
|
#++
|
39
42
|
class Headless
|
40
43
|
|
41
|
-
class Exception <
|
44
|
+
class Exception < RuntimeError
|
42
45
|
end
|
43
46
|
|
44
47
|
# The display number
|
@@ -53,29 +56,31 @@ class Headless
|
|
53
56
|
# * +display+ (default 99) - what display number to listen to;
|
54
57
|
# * +reuse+ (default true) - if given display server already exists, should we use it or fail miserably?
|
55
58
|
# * +dimensions+ (default 1280x1024x24) - display dimensions and depth. Not all combinations are possible, refer to +man Xvfb+.
|
59
|
+
# * +destroy_at_exit+ (default true) - if a display is started but not stopped, should it be destroyed when the script finishes?
|
56
60
|
def initialize(options = {})
|
57
|
-
|
61
|
+
CliUtil.ensure_application_exists!('Xvfb', 'Xvfb not found on your system')
|
58
62
|
|
59
63
|
@display = options.fetch(:display, 99).to_i
|
60
64
|
@reuse_display = options.fetch(:reuse, true)
|
61
65
|
@dimensions = options.fetch(:dimensions, '1280x1024x24')
|
66
|
+
@video_capture_options = options.fetch(:video, {})
|
67
|
+
@destroy_at_exit = options.fetch(:destroy_at_exit, true)
|
62
68
|
|
63
69
|
#TODO more logic here, autopicking the display number
|
64
70
|
if @reuse_display
|
65
|
-
launch_xvfb unless
|
66
|
-
elsif
|
67
|
-
raise Exception.new("Display :#{display} is already taken and reuse=false")
|
71
|
+
launch_xvfb unless xvfb_running?
|
72
|
+
elsif xvfb_running?
|
73
|
+
raise Headless::Exception.new("Display :#{display} is already taken and reuse=false")
|
68
74
|
else
|
69
75
|
launch_xvfb
|
70
76
|
end
|
71
|
-
|
72
|
-
raise Exception.new("Xvfb did not launch - something's wrong") unless read_pid
|
73
77
|
end
|
74
78
|
|
75
79
|
# Switches to the headless server
|
76
80
|
def start
|
77
81
|
@old_display = ENV['DISPLAY']
|
78
82
|
ENV['DISPLAY'] = ":#{display}"
|
83
|
+
hook_at_exit
|
79
84
|
end
|
80
85
|
|
81
86
|
# Switches back from the headless server
|
@@ -86,7 +91,7 @@ class Headless
|
|
86
91
|
# Switches back from the headless server and terminates the headless session
|
87
92
|
def destroy
|
88
93
|
stop
|
89
|
-
|
94
|
+
CliUtil.kill_process(pid_filename)
|
90
95
|
end
|
91
96
|
|
92
97
|
# Block syntax:
|
@@ -101,27 +106,44 @@ class Headless
|
|
101
106
|
yield headless
|
102
107
|
headless.destroy
|
103
108
|
end
|
104
|
-
|
105
109
|
class <<self; alias_method :ly, :run; end
|
106
110
|
|
107
|
-
|
108
|
-
|
111
|
+
def video
|
112
|
+
@video_recorder ||= VideoRecorder.new(display, dimensions, @video_capture_options)
|
113
|
+
end
|
114
|
+
|
115
|
+
def take_screenshot(file_path)
|
116
|
+
CliUtil.ensure_application_exists!('import', "imagemagick not found on your system. Please install it using sudo apt-get install imagemagick")
|
109
117
|
|
110
|
-
|
111
|
-
@xvfb = `which Xvfb`.strip
|
112
|
-
raise Exception.new('Xvfb not found on your system') if @xvfb == ''
|
118
|
+
system "#{CliUtil.path_to('import')} -display localhost:#{display} -window root #{file_path}"
|
113
119
|
end
|
114
120
|
|
121
|
+
private
|
122
|
+
|
115
123
|
def launch_xvfb
|
116
124
|
#TODO error reporting
|
117
|
-
system "#{
|
118
|
-
|
125
|
+
result = system "#{CliUtil.path_to("Xvfb")} :#{display} -screen 0 #{dimensions} -ac >/dev/null 2>&1 &"
|
126
|
+
raise Headless::Exception.new("Xvfb did not launch - something's wrong") unless result
|
127
|
+
end
|
128
|
+
|
129
|
+
def xvfb_running?
|
130
|
+
!!read_xvfb_pid
|
119
131
|
end
|
120
132
|
|
121
|
-
def
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
133
|
+
def pid_filename
|
134
|
+
"/tmp/.X#{display}-lock"
|
135
|
+
end
|
136
|
+
|
137
|
+
def read_xvfb_pid
|
138
|
+
CliUtil.read_pid(pid_filename)
|
139
|
+
end
|
140
|
+
|
141
|
+
def hook_at_exit
|
142
|
+
unless @at_exit_hook_installed
|
143
|
+
@at_exit_hook_installed = true
|
144
|
+
at_exit do
|
145
|
+
destroy if @destroy_at_exit
|
146
|
+
end
|
147
|
+
end
|
126
148
|
end
|
127
149
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class Headless
|
2
|
+
class CliUtil
|
3
|
+
def self.application_exists?(app)
|
4
|
+
`which #{app}`.strip != ""
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.ensure_application_exists!(app, error_message)
|
8
|
+
if !self.application_exists?(app)
|
9
|
+
raise Headless::Exception.new(error_message)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.path_to(app)
|
14
|
+
`which #{app}`.strip
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.read_pid(pid_filename)
|
18
|
+
pid = (File.read(pid_filename) rescue "").strip.to_i
|
19
|
+
pid == 0 ? nil : pid
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.fork_process(command, pid_filename, log_filename='/dev/null')
|
23
|
+
pid = fork do
|
24
|
+
STDERR.reopen(log_filename)
|
25
|
+
exec command
|
26
|
+
exit! 127 # safeguard in case exec fails
|
27
|
+
end
|
28
|
+
Process.detach(pid)
|
29
|
+
|
30
|
+
File.open pid_filename, 'w' do |f|
|
31
|
+
f.puts pid
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.kill_process(pid_filename, options={})
|
36
|
+
if pid = self.read_pid(pid_filename)
|
37
|
+
begin
|
38
|
+
Process.kill 'TERM', pid
|
39
|
+
Process.wait pid if options[:wait]
|
40
|
+
rescue Errno::ESRCH
|
41
|
+
# no such process; assume it's already killed
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
begin
|
46
|
+
FileUtils.rm pid_filename
|
47
|
+
rescue Errno::ENOENT
|
48
|
+
# pid file already removed
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
class Headless
|
4
|
+
class VideoRecorder
|
5
|
+
attr_accessor :pid_file_path, :tmp_file_path, :log_file_path
|
6
|
+
|
7
|
+
def initialize(display, dimensions, options = {})
|
8
|
+
CliUtil.ensure_application_exists!('ffmpeg', 'Ffmpeg not found on your system. Install it with sudo apt-get install ffmpeg')
|
9
|
+
|
10
|
+
@display = display
|
11
|
+
@dimensions = dimensions
|
12
|
+
|
13
|
+
@pid_file_path = options.fetch(:pid_file_path, "/tmp/.headless_ffmpeg_#{@display}.pid")
|
14
|
+
@tmp_file_path = options.fetch(:tmp_file_path, "/tmp/.headless_ffmpeg_#{@display}.mov")
|
15
|
+
@log_file_path = options.fetch(:log_file_path, "/dev/null")
|
16
|
+
end
|
17
|
+
|
18
|
+
def capture_running?
|
19
|
+
!!@capture_running
|
20
|
+
end
|
21
|
+
|
22
|
+
def start_capture
|
23
|
+
CliUtil.fork_process("#{CliUtil.path_to('ffmpeg')} -y -r 30 -g 600 -s #{@dimensions} -f x11grab -i :#{@display} -vcodec qtrle #{@tmp_file_path}", @pid_file_path, @log_file_path)
|
24
|
+
@capture_running = true
|
25
|
+
at_exit do
|
26
|
+
stop_and_discard if capture_running?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop_and_save(path)
|
31
|
+
@capture_running = false
|
32
|
+
CliUtil.kill_process(@pid_file_path, :wait => true)
|
33
|
+
FileUtils.mv(@tmp_file_path, path)
|
34
|
+
end
|
35
|
+
|
36
|
+
def stop_and_discard
|
37
|
+
@capture_running = false
|
38
|
+
CliUtil.kill_process(@pid_file_path, :wait => true)
|
39
|
+
begin
|
40
|
+
FileUtils.rm(@tmp_file_path)
|
41
|
+
rescue Errno::ENOENT
|
42
|
+
# that's ok if the file doesn't exist
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'lib/headless'
|
2
|
+
|
3
|
+
describe Headless do
|
4
|
+
before do
|
5
|
+
ENV['DISPLAY'] = ":31337"
|
6
|
+
stub_environment
|
7
|
+
end
|
8
|
+
|
9
|
+
context "instaniation" do
|
10
|
+
context "when Xvfb is not installed" do
|
11
|
+
before do
|
12
|
+
Headless::CliUtil.stub!(:application_exists?).and_return(false)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "raises an error" do
|
16
|
+
lambda { Headless.new }.should raise_error(Headless::Exception)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when Xvfb not started yet" do
|
21
|
+
it "starts Xvfb" do
|
22
|
+
Headless.any_instance.should_receive(:system).with("/usr/bin/Xvfb :99 -screen 0 1280x1024x24 -ac >/dev/null 2>&1 &").and_return(true)
|
23
|
+
|
24
|
+
headless = Headless.new
|
25
|
+
end
|
26
|
+
|
27
|
+
it "allows setting screen dimensions" do
|
28
|
+
Headless.any_instance.should_receive(:system).with("/usr/bin/Xvfb :99 -screen 0 1024x768x16 -ac >/dev/null 2>&1 &").and_return(true)
|
29
|
+
|
30
|
+
headless = Headless.new(:dimensions => "1024x768x16")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "when Xvfb is already running" do
|
35
|
+
before do
|
36
|
+
Headless::CliUtil.stub!(:read_pid).and_return(31337)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "raises an error if reuse display is not allowed" do
|
40
|
+
lambda { Headless.new(:reuse => false) }.should raise_error(Headless::Exception)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "doesn't raise an error if reuse display is allowed" do
|
44
|
+
lambda { Headless.new(:reuse => true) }.should_not raise_error(Headless::Exception)
|
45
|
+
lambda { Headless.new }.should_not raise_error(Headless::Exception)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "lifecycle" do
|
51
|
+
let(:headless) { Headless.new }
|
52
|
+
describe "#start" do
|
53
|
+
it "switches to the headless server" do
|
54
|
+
ENV['DISPLAY'].should == ":31337"
|
55
|
+
headless.start
|
56
|
+
ENV['DISPLAY'].should == ":99"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "#stop" do
|
61
|
+
it "switches back from the headless server" do
|
62
|
+
ENV['DISPLAY'].should == ":31337"
|
63
|
+
headless.start
|
64
|
+
ENV['DISPLAY'].should == ":99"
|
65
|
+
headless.stop
|
66
|
+
ENV['DISPLAY'].should == ":31337"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#destroy" do
|
71
|
+
before do
|
72
|
+
Headless::CliUtil.stub!(:read_pid).and_return(4444)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "switches back from the headless server and terminates the headless session" do
|
76
|
+
Process.should_receive(:kill).with('TERM', 4444)
|
77
|
+
|
78
|
+
ENV['DISPLAY'].should == ":31337"
|
79
|
+
headless.start
|
80
|
+
ENV['DISPLAY'].should == ":99"
|
81
|
+
headless.destroy
|
82
|
+
ENV['DISPLAY'].should == ":31337"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "#video" do
|
88
|
+
let(:headless) { Headless.new }
|
89
|
+
|
90
|
+
it "returns video recorder" do
|
91
|
+
headless.video.should be_a_kind_of(Headless::VideoRecorder)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "returns the same instance" do
|
95
|
+
recorder = headless.video
|
96
|
+
headless.video.should be_eql(recorder)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "#take_screenshot" do
|
101
|
+
let(:headless) { Headless.new }
|
102
|
+
|
103
|
+
it "raises an error if imagemagick is not installed" do
|
104
|
+
Headless::CliUtil.stub!(:application_exists?).and_return(false)
|
105
|
+
|
106
|
+
lambda { headless.take_screenshot }.should raise_error(Headless::Exception)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "issues command to take screenshot" do
|
110
|
+
headless = Headless.new
|
111
|
+
|
112
|
+
Headless.any_instance.should_receive(:system)
|
113
|
+
|
114
|
+
headless.take_screenshot("/tmp/image.png")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def stub_environment
|
121
|
+
Headless::CliUtil.stub!(:application_exists?).and_return(true)
|
122
|
+
Headless::CliUtil.stub!(:read_pid).and_return(nil)
|
123
|
+
Headless::CliUtil.stub!(:path_to).and_return("/usr/bin/Xvfb")
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'lib/headless'
|
2
|
+
|
3
|
+
describe Headless::VideoRecorder do
|
4
|
+
before do
|
5
|
+
stub_environment
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "instaniation" do
|
9
|
+
before do
|
10
|
+
Headless::CliUtil.stub!(:application_exists?).and_return(false)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "throws an error if ffmpeg is not installed" do
|
14
|
+
lambda { Headless::VideoRecorder.new(99, "1024x768x32") }.should raise_error(Headless::Exception)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#capture" do
|
19
|
+
it "starts ffmpeg" do
|
20
|
+
Headless::CliUtil.stub(:path_to, 'ffmpeg').and_return('ffmpeg')
|
21
|
+
Headless::CliUtil.should_receive(:fork_process).with(/ffmpeg -y -r 30 -g 600 -s 1024x768x32 -f x11grab -i :99 -vcodec qtrle/, "/tmp/.headless_ffmpeg_99.pid", '/dev/null')
|
22
|
+
|
23
|
+
recorder = Headless::VideoRecorder.new(99, "1024x768x32")
|
24
|
+
recorder.start_capture
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "stopping video recording" do
|
29
|
+
subject do
|
30
|
+
recorder = Headless::VideoRecorder.new(99, "1024x768x32", :pid_file_path => "/tmp/pid", :tmp_file_path => "/tmp/ci.mov")
|
31
|
+
recorder.start_capture
|
32
|
+
recorder
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "using #stop_and_save" do
|
36
|
+
it "stops video recording and saves file" do
|
37
|
+
Headless::CliUtil.should_receive(:kill_process).with("/tmp/pid", :wait => true)
|
38
|
+
FileUtils.should_receive(:mv).with("/tmp/ci.mov", "/tmp/test.mov")
|
39
|
+
|
40
|
+
subject.stop_and_save("/tmp/test.mov")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "using #stop_and_discard" do
|
45
|
+
it "stops video recording and deletes temporary file" do
|
46
|
+
Headless::CliUtil.should_receive(:kill_process).with("/tmp/pid", :wait => true)
|
47
|
+
FileUtils.should_receive(:rm).with("/tmp/ci.mov")
|
48
|
+
|
49
|
+
subject.stop_and_discard
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def stub_environment
|
57
|
+
Headless::CliUtil.stub!(:application_exists?).and_return(true)
|
58
|
+
Headless::CliUtil.stub!(:fork_process).and_return(true)
|
59
|
+
end
|
60
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: headless
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 21
|
5
|
+
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
+
- 2
|
8
9
|
- 1
|
9
|
-
|
10
|
-
version: 0.1.0
|
10
|
+
version: 0.2.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Leonid Shevtsov
|
@@ -15,10 +15,24 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2011-08-26 00:00:00 +03:00
|
19
19
|
default_executable:
|
20
|
-
dependencies:
|
21
|
-
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rspec
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 15
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 6
|
33
|
+
version: "2.6"
|
34
|
+
type: :development
|
35
|
+
version_requirements: *id001
|
22
36
|
description: " Headless is a Ruby interface for Xvfb. It allows you to create a headless display straight from Ruby code, hiding some low-level action.\n"
|
23
37
|
email: leonid@shevtsov.me
|
24
38
|
executables: []
|
@@ -28,12 +42,20 @@ extensions: []
|
|
28
42
|
extra_rdoc_files: []
|
29
43
|
|
30
44
|
files:
|
31
|
-
-
|
45
|
+
- .gitignore
|
32
46
|
- CHANGELOG
|
33
|
-
-
|
47
|
+
- Gemfile
|
34
48
|
- LICENSE
|
49
|
+
- README.md
|
50
|
+
- Rakefile
|
51
|
+
- headless.gemspec
|
52
|
+
- lib/headless.rb
|
53
|
+
- lib/headless/cli_util.rb
|
54
|
+
- lib/headless/video/video_recorder.rb
|
55
|
+
- spec/headless_spec.rb
|
56
|
+
- spec/video_recorder_spec.rb
|
35
57
|
has_rdoc: true
|
36
|
-
homepage: http://
|
58
|
+
homepage: http://leonid.shevtsov.me/en/headless
|
37
59
|
licenses: []
|
38
60
|
|
39
61
|
post_install_message:
|
@@ -62,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
62
84
|
requirements:
|
63
85
|
- Xvfb
|
64
86
|
rubyforge_project:
|
65
|
-
rubygems_version: 1.
|
87
|
+
rubygems_version: 1.6.2
|
66
88
|
signing_key:
|
67
89
|
specification_version: 3
|
68
90
|
summary: Ruby headless display interface
|