webkit_remote 0.3.2 → 0.4.0

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.
@@ -14,22 +14,46 @@ class Process
14
14
  # the default port is 9292
15
15
  # @option opts [Number] timeout number of seconds to wait for the browser
16
16
  # to start; the default timeout is 10 seconds
17
+ # @option opts [Hash<Symbol, Number>] window set the :left, :top, :width and
18
+ # :height of the browser window; by default, the browser window is
19
+ # 256x256 starting at 0,0.
20
+ # @option opts [Hash<Symbol, Number>, Boolean] xvfb use Xvfb instead of a
21
+ # real screen; set the :display, :width, :height, :depth and :dpi of the
22
+ # server, or use the default display number 20, at 1280x1024x32 with
23
+ # 72dpi
17
24
  def initialize(opts = {})
18
25
  @port = opts[:port] || 9292
19
26
  @timeout = opts[:timeout] || 10
20
27
  @running = false
21
28
  @data_dir = Dir.mktmpdir 'webkit-remote'
22
29
  @pid = nil
30
+ @xvfb_pid = nil
23
31
  @cli = chrome_cli opts
32
+ if opts[:xvfb]
33
+ @xvfb_cli = xvfb_cli opts
34
+ else
35
+ @xvfb_cli = nil
36
+ end
24
37
  end
25
38
 
26
39
  # Starts the browser process.
27
40
  #
28
- # @return [WebkitRemote::Browser] self
41
+ # @return [WebkitRemote::Browser] master session to the started Browser
42
+ # process; the session's auto_close is set to false so that it can be
43
+ # safely discarded; nil if the launch fails
29
44
  def start
30
45
  return self if running?
46
+
47
+ if @xvfb_cli
48
+ unless @xvfb_pid = POSIX::Spawn.spawn(*@xvfb_cli)
49
+ # The Xvfb launch failed.
50
+ return nil
51
+ end
52
+ end
53
+
31
54
  unless @pid = POSIX::Spawn.spawn(*@cli)
32
55
  # The launch failed
56
+ stop
33
57
  return nil
34
58
  end
35
59
 
@@ -51,6 +75,7 @@ class Process
51
75
  end
52
76
  end
53
77
  # The browser failed, or was too slow to start.
78
+ stop
54
79
  nil
55
80
  end
56
81
 
@@ -65,10 +90,28 @@ class Process
65
90
  # @return [WebkitRemote::Process] self
66
91
  def stop
67
92
  return self unless running?
68
- begin
69
- ::Process.kill 'TERM', @pid
70
- ::Process.wait @pid
93
+ if @pid
94
+ begin
95
+ ::Process.kill 'TERM', @pid
96
+ ::Process.wait @pid
97
+ rescue SystemCallError
98
+ # Process died on its own.
99
+ ensure
100
+ @pid = nil
101
+ end
71
102
  end
103
+
104
+ if @xvfb_pid
105
+ begin
106
+ ::Process.kill 'TERM', @xvfb_pid
107
+ ::Process.wait @xvfb_pid
108
+ rescue SystemCallError
109
+ # Process died on its own.
110
+ ensure
111
+ @xvfb_pid = nil
112
+ end
113
+ end
114
+
72
115
  FileUtils.rm_rf @data_dir if File.exists?(@data_dir)
73
116
  @running = false
74
117
  self
@@ -90,6 +133,7 @@ class Process
90
133
  # The Chromium wiki recommends this page for available flags:
91
134
  # http://peter.sh/experiments/chromium-command-line-switches/
92
135
  [
136
+ chrome_env(opts),
93
137
  self.class.chrome_binary,
94
138
  '--disable-default-apps', # no bundled apps
95
139
  '--disable-desktop-shortcuts', # don't mess with the desktop
@@ -115,13 +159,73 @@ class Process
115
159
  "--remote-debugging-port=#{@port}", # Webkit remote debugging
116
160
  "--user-data-dir=#{@data_dir}", # really ensure a clean slate
117
161
  '--window-position=0,0', # remove randomness source
118
- '--window-size=128,128', # remove randomness source
162
+ '--window-size=256,256', # remove randomness source
119
163
  'about:blank', # don't load the homepage
120
164
  {
121
165
  chdir: @data_dir,
122
166
  in: '/dev/null',
123
167
  out: File.join(@data_dir, '.stdout'),
124
- err: File.join(@data_dir, '.stderr'),
168
+ err: File.join(@data_dir, '.stderr')
169
+ },
170
+ ]
171
+ end
172
+
173
+ # Environment variables set when launching Chrome.
174
+ #
175
+ # @param [Hash] opts options passed to the WebkitRemote::Process constructor
176
+ # @return [Hash<String, String>] variables to be added to the environment of
177
+ # the Chrome process before launching
178
+ def chrome_env(opts)
179
+ if opts[:xvfb]
180
+ if opts[:xvfb].respond_to?(:[]) and opts[:xvfb][:display]
181
+ display = opts[:xvfb][:display]
182
+ else
183
+ display = 20
184
+ end
185
+ { 'DISPLAY' => ":#{display}.0" }
186
+ else
187
+ {}
188
+ end
189
+ end
190
+
191
+ # Command-line that launches Xvfb.
192
+ #
193
+ # @param [Hash] opts options passed to the WebkitRemote::Process constructor
194
+ # @return [Array<String>] command line for launching Xvfb
195
+ def xvfb_cli(opts)
196
+ # The OSX man page for Xvfb:
197
+ # http://developer.apple.com/library/mac/documentation/darwin/reference/manpages/man1/Xvfb.1.html
198
+
199
+ xvfb_opts = opts[:xvfb]
200
+ unless xvfb_opts.respond_to? :[]
201
+ xvfb_opts = {}
202
+ end
203
+
204
+ display = xvfb_opts[:display] || 20
205
+ width = xvfb_opts[:width] || 1280
206
+ height = xvfb_opts[:height] || 1024
207
+ depth = xvfb_opts[:depth] || 32
208
+ dpi = xvfb_opts[:dpi] || 72
209
+
210
+ # https://bugs.freedesktop.org/show_bug.cgi?id=17453
211
+ if depth == 32
212
+ depth = '24+32'
213
+ end
214
+
215
+ [
216
+ self.class.xvfb_binary,
217
+ ":#{display}",
218
+ '-screen', '0', "#{width}x#{height}x#{depth}",
219
+ '-auth', File.join(@data_dir, '.Xauthority'),
220
+ '-c',
221
+ '-dpi', dpi.to_s,
222
+ '-terminate',
223
+ '-wr',
224
+ {
225
+ chdir: @data_dir,
226
+ in: '/dev/null',
227
+ out: File.join(@data_dir, '.X_stdout'),
228
+ err: File.join(@data_dir, '.X_stderr'),
125
229
  },
126
230
  ]
127
231
  end
@@ -161,6 +265,20 @@ class Process
161
265
  @chrome_binary ||= nil
162
266
  end
163
267
  @chrome_binary = false
268
+
269
+ # Path to the Xvfb virtual X server binary.
270
+ #
271
+ # @return [String] full-qualified path to a binary that launches Xvfb.
272
+ def self.xvfb_binary
273
+ return @xvfb_binary unless @xvfb_binary == false
274
+
275
+ path = `which Xvfb`
276
+ unless path.empty?
277
+ @xvfb_binary = path.strip
278
+ end
279
+ @xvfb_binary ||= nil
280
+ end
281
+ @xvfb_binary = false
164
282
  end # class WebkitRemote::Browser
165
283
 
166
284
  end # namespace WebkitRemote
@@ -7,7 +7,12 @@ module WebkitRemote
7
7
  # WebKit process; the client will automatically stop the process when
8
8
  # closed
9
9
  def self.local(opts = {})
10
+ # Use Xvfb if no desktop is available.
11
+ if !opts.has_key?(:xvfb) && (!ENV['DISPLAY'] || ENV['DISPLAY'].empty?)
12
+ opts = { xvfb: true }.merge! opts
13
+ end
10
14
  process = WebkitRemote::Process.new opts
15
+
11
16
  browser = process.start
12
17
  browser.stop_process = true
13
18
  client = WebkitRemote::Client.new tab: browser.tabs.first,
@@ -17,6 +17,10 @@
17
17
  })();
18
18
  // Arguments test.
19
19
  console.error("params ", 42, true, { hello: "ruby" });
20
+ // Repetition test.
21
+ for (var i = 0; i < 3; i++) {
22
+ console.warn("one more time");
23
+ }
20
24
  </script>
21
25
  </head>
22
26
  <body>
@@ -0,0 +1,23 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>WebkitRemote DOM test</title>
5
+ </head>
6
+ <body>
7
+ <article id="load-article">
8
+ <section id="load-confirmation-section">
9
+ <p id="load-confirmation" data-purpose="attr-value-test">
10
+ DOM test loaded
11
+ </p>
12
+ </section>
13
+ </article>
14
+ <article id="second-article">
15
+ <section id="second-section">
16
+ <p id="second-paragraph" data-purpose="query-selector-all-test">
17
+ Second paragraph for testing query_selector_all.
18
+ </p>
19
+ </section>
20
+ </article>
21
+ </body>
22
+ </html>
23
+
data/test/helper.rb CHANGED
@@ -17,6 +17,7 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
17
17
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
18
18
  require 'webkit_remote'
19
19
 
20
+ require 'debugger'
20
21
  require 'pp'
21
22
  require 'thread'
22
23
  Thread.abort_on_exception = true
@@ -2,7 +2,7 @@ require File.expand_path('../helper.rb', File.dirname(__FILE__))
2
2
 
3
3
  describe WebkitRemote::Browser do
4
4
  before :each do
5
- @process = WebkitRemote::Process.new port: 9669
5
+ @process = WebkitRemote::Process.new port: 9669, xvfb: true
6
6
  @process.start
7
7
  end
8
8
  after :each do
@@ -40,17 +40,34 @@ describe WebkitRemote::Client::Console do
40
40
  @client.console_events = true
41
41
  @client.navigate_to fixture_url(:console)
42
42
  @events = @client.wait_for type: WebkitRemote::Event::PageLoaded
43
- @messages = @events.select do |event|
43
+ @message_events = @events.select do |event|
44
44
  event.kind_of? WebkitRemote::Event::ConsoleMessage
45
45
  end
46
+ @repeat_events = @events.select do |event|
47
+ event.kind_of? WebkitRemote::Event::ConsoleMessageRepeated
48
+ end
49
+ @messages = @client.console_messages
46
50
  end
47
51
 
48
52
  after :all do
49
53
  @client.clear_all
50
54
  end
51
55
 
52
- it 'receives console events' do
53
- @messages.wont_be :empty?
56
+ it 'receives ConsoleMessage events' do
57
+ @message_events.wont_be :empty?
58
+ end
59
+
60
+ it 'receives ConsoleMessageRepeated events' do
61
+ @repeat_events.wont_be :empty?
62
+ end
63
+
64
+ it 'collects messages into Client#console_messages' do
65
+ @message_events[0].message.must_equal @messages[0]
66
+ @message_events[1].message.must_equal @messages[1]
67
+ @message_events[2].message.must_equal @messages[2]
68
+ @message_events[3].message.must_equal @messages[3]
69
+ @repeat_events[0].message.must_equal @messages[3]
70
+ @repeat_events[1].message.must_equal @messages[3]
54
71
  end
55
72
 
56
73
  it 'parses text correctly' do
@@ -81,11 +98,18 @@ describe WebkitRemote::Client::Console do
81
98
  @messages[2].params[0, 3].must_equal ['params ', 42, true]
82
99
  @messages[2].params.length.must_equal 4
83
100
 
84
- @messages[2].params[3].must_be_kind_of WebkitRemote::Client::RemoteObject
85
- @messages[2].params[3].properties[:hello].value.must_equal 'ruby'
101
+ @messages[2].params[3].must_be_kind_of WebkitRemote::Client::JsObject
102
+ @messages[2].params[3].properties['hello'].value.must_equal 'ruby'
86
103
  @messages[2].params[3].group.name.must_equal nil
87
104
  end
88
105
 
106
+ it 'parses repeated messages correctly' do
107
+ @messages[3].text.must_equal 'one more time'
108
+ @messages[3].count.must_equal 3
109
+ @repeat_events[0].count.must_equal 2
110
+ @repeat_events[1].count.must_equal 3
111
+ end
112
+
89
113
  describe 'clear_console' do
90
114
  before :all do
91
115
  @client.clear_console
@@ -108,8 +132,41 @@ describe WebkitRemote::Client::Console do
108
132
  end
109
133
 
110
134
  it 'releases the objects in ConsoleMessage instances' do
111
- @messages[2].params[3].released?.must_equal true
135
+ @message_events[2].message.params[3].released?.must_equal true
136
+ end
137
+ end
138
+ end
139
+
140
+ describe 'with console and network events enabled' do
141
+ before :all do
142
+ @client.console_events = true
143
+ @client.network_events = true
144
+ @client.navigate_to fixture_url(:network)
145
+ @events = @client.wait_for type: WebkitRemote::Event::ConsoleMessage,
146
+ level: :log
147
+ @message_events = @events.select do |event|
148
+ event.kind_of? WebkitRemote::Event::ConsoleMessage
112
149
  end
150
+ @messages = @client.console_messages
151
+ end
152
+
153
+ after :all do
154
+ @client.clear_all
155
+ end
156
+
157
+ it 'receives ConsoleMessage events' do
158
+ @message_events.wont_be :empty?
159
+ end
160
+
161
+ it 'associates messages with network requests' do
162
+ @messages[0].text.must_match /not found/i
163
+ @messages[0].network_resource.wont_equal nil
164
+ @messages[0].network_resource.document_url.
165
+ must_equal fixture_url(:network)
166
+ @messages[0].level.must_equal :error
167
+ @messages[0].count.must_equal 1
168
+ @messages[0].reason.must_equal :network
169
+ @messages[0].type.must_equal :log
113
170
  end
114
171
  end
115
172
  end
@@ -0,0 +1,149 @@
1
+ require File.expand_path('../../helper.rb', File.dirname(__FILE__))
2
+
3
+ describe WebkitRemote::Client::Dom do
4
+ before :all do
5
+ @client = WebkitRemote.local port: 9669
6
+ @client.page_events = true
7
+ @client.navigate_to fixture_url(:dom)
8
+ @client.wait_for type: WebkitRemote::Event::PageLoaded
9
+ end
10
+ after :all do
11
+ @client.close
12
+ end
13
+
14
+ describe '#dom_root' do
15
+ before :all do
16
+ @root = @client.dom_root
17
+ end
18
+
19
+ it 'returns a WebkitRemote::Client::DomNode for a document' do
20
+ @root.must_be_kind_of WebkitRemote::Client::DomNode
21
+ @root.node_type.must_equal :document
22
+ @root.name.must_equal '#document'
23
+ @root.document_url.must_equal fixture_url(:dom)
24
+ end
25
+ end
26
+ end
27
+
28
+ describe WebkitRemote::Client::DomNode do
29
+ before :all do
30
+ @client = WebkitRemote.local port: 9669
31
+ @client.page_events = true
32
+ @client.navigate_to fixture_url(:dom)
33
+ @client.wait_for type: WebkitRemote::Event::PageLoaded
34
+ @root = @client.dom_root
35
+ end
36
+ after :all do
37
+ @client.close
38
+ end
39
+
40
+ describe 'querySelector' do
41
+ before :all do
42
+ @p = @root.query_selector 'p#load-confirmation'
43
+ end
44
+
45
+ it 'returns a WebkitRemote::Client::DomNode' do
46
+ @p.must_be_kind_of WebkitRemote::Client::DomNode
47
+ end
48
+
49
+ it 'returns a WebkitRemote::Client::DomNode with correct attributes' do
50
+ skip 'On-demand node processing not implemented'
51
+ @p.node_type.must_equal :element
52
+ @p.name.must_equal 'P'
53
+ end
54
+ end
55
+
56
+ describe 'querySelectorAll' do
57
+ before :all do
58
+ @p_array = @root.query_selector_all 'p'
59
+ end
60
+
61
+ it 'returns an array of WebkitRemote::Client::DomNodes' do
62
+ @p_array.must_respond_to :[]
63
+ @p_array.each { |p| p.must_be_kind_of WebkitRemote::Client::DomNode }
64
+ end
65
+
66
+ it 'returns the correct WebkitRemote::Client::DomNodes' do
67
+ @p_array.map { |p| p.attributes['id'] }.
68
+ must_equal ['load-confirmation', 'second-paragraph']
69
+ end
70
+ end
71
+
72
+ describe 'attributes' do
73
+ before :all do
74
+ @p = @root.query_selector 'p#load-confirmation'
75
+ end
76
+
77
+ it 'produces a Hash of attributes' do
78
+ @p.attributes.must_include 'data-purpose'
79
+ @p.attributes['data-purpose'].must_equal 'attr-value-test'
80
+ end
81
+ end
82
+
83
+ describe 'outer_html' do
84
+ before :all do
85
+ @p = @root.query_selector 'p#load-confirmation'
86
+ end
87
+
88
+ it 'returns the original HTML behind the element' do
89
+ @p.outer_html.strip.must_equal <<DOM_END.strip
90
+ <p id="load-confirmation" data-purpose="attr-value-test">
91
+ DOM test loaded
92
+ </p>
93
+ DOM_END
94
+ end
95
+ end
96
+
97
+ describe 'remove' do
98
+ before :all do
99
+ @p = @root.query_selector 'p#load-confirmation'
100
+ @p.remove
101
+ end
102
+
103
+ it 'removes the node from the DOM tree' do
104
+ @root.query_selector_all('p').length.must_equal 1
105
+ end
106
+ end
107
+
108
+ describe 'remove_attribute' do
109
+ describe 'without cached data' do
110
+ before :all do
111
+ @p = @root.query_selector 'p#load-confirmation'
112
+ @p.remove_attribute 'data-purpose'
113
+ end
114
+
115
+ it 'strips the attribute from the element' do
116
+ @p.attributes!.wont_include 'data-purpose'
117
+ end
118
+ end
119
+
120
+ describe 'with cached data' do
121
+ before :all do
122
+ @p = @root.query_selector 'p#load-confirmation'
123
+ @p.attributes
124
+ @p.remove_attribute 'data-purpose'
125
+ end
126
+
127
+ it 'strips the attribute from the element' do
128
+ @p.attributes.wont_include 'data-purpose'
129
+ end
130
+ end
131
+ end
132
+
133
+ describe 'js_object' do
134
+ before :all do
135
+ @p = @root.query_selector 'p#load-confirmation'
136
+ @js_object = @p.js_object
137
+ end
138
+
139
+ it 'returns the corresponding WebkitRemote::Client::JsObject' do
140
+ @js_object.must_be_kind_of WebkitRemote::Client::JsObject
141
+ @js_object.properties['tagName'].value.must_equal 'P'
142
+ @js_object.properties['baseURI'].value.must_equal fixture_url(:dom)
143
+ end
144
+
145
+ it 'dom_node returns the DomNode back' do
146
+ @js_object.dom_node.must_equal @p
147
+ end
148
+ end
149
+ end