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.
- data/.travis.yml +0 -2
- data/Gemfile +1 -0
- data/Gemfile.lock +10 -1
- data/README.md +36 -1
- data/VERSION +1 -1
- data/lib/webkit_remote.rb +2 -0
- data/lib/webkit_remote/client/console.rb +131 -0
- data/lib/webkit_remote/client/console_events.rb +24 -95
- data/lib/webkit_remote/client/dom.rb +297 -0
- data/lib/webkit_remote/client/dom_events.rb +18 -0
- data/lib/webkit_remote/client/network_events.rb +2 -2
- data/lib/webkit_remote/client/runtime.rb +66 -47
- data/lib/webkit_remote/process.rb +124 -6
- data/lib/webkit_remote/top_level.rb +5 -0
- data/test/fixtures/html/console.html +4 -0
- data/test/fixtures/html/dom.html +23 -0
- data/test/helper.rb +1 -0
- data/test/webkit_remote/browser_test.rb +1 -1
- data/test/webkit_remote/client/console_test.rb +63 -6
- data/test/webkit_remote/client/dom_test.rb +149 -0
- data/test/webkit_remote/client/{remote_object_group_test.rb → js_object_group_test.rb} +1 -1
- data/test/webkit_remote/client/{remote_object_test.rb → js_object_test.rb} +28 -21
- data/test/webkit_remote/client/runtime_test.rb +6 -6
- data/test/webkit_remote/client_test.rb +1 -1
- data/test/webkit_remote/process_test.rb +82 -30
- data/test/webkit_remote/rpc_test.rb +1 -1
- data/test/webkit_remote_test.rb +2 -2
- data/webkit_remote.gemspec +11 -4
- metadata +25 -5
@@ -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]
|
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
|
-
|
69
|
-
|
70
|
-
|
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=
|
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,
|
@@ -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
@@ -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
|
-
@
|
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
|
53
|
-
@
|
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::
|
85
|
-
@messages[2].params[3].properties[
|
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
|
-
@
|
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
|