apparition 0.1.0 → 0.6.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.
- checksums.yaml +4 -4
- data/README.md +40 -4
- data/lib/capybara/apparition.rb +0 -2
- data/lib/capybara/apparition/browser.rb +75 -133
- data/lib/capybara/apparition/browser/cookie.rb +4 -16
- data/lib/capybara/apparition/browser/header.rb +2 -2
- data/lib/capybara/apparition/browser/launcher.rb +25 -0
- data/lib/capybara/apparition/browser/launcher/local.rb +213 -0
- data/lib/capybara/apparition/browser/launcher/remote.rb +55 -0
- data/lib/capybara/apparition/browser/page_manager.rb +90 -0
- data/lib/capybara/apparition/browser/window.rb +29 -29
- data/lib/capybara/apparition/configuration.rb +100 -0
- data/lib/capybara/apparition/console.rb +8 -1
- data/lib/capybara/apparition/dev_tools_protocol/remote_object.rb +23 -7
- data/lib/capybara/apparition/dev_tools_protocol/session.rb +3 -4
- data/lib/capybara/apparition/driver.rb +107 -35
- data/lib/capybara/apparition/driver/chrome_client.rb +13 -8
- data/lib/capybara/apparition/driver/response.rb +1 -1
- data/lib/capybara/apparition/driver/web_socket_client.rb +1 -0
- data/lib/capybara/apparition/errors.rb +3 -3
- data/lib/capybara/apparition/network_traffic/error.rb +1 -0
- data/lib/capybara/apparition/network_traffic/request.rb +5 -5
- data/lib/capybara/apparition/node.rb +142 -50
- data/lib/capybara/apparition/node/drag.rb +165 -65
- data/lib/capybara/apparition/page.rb +180 -142
- data/lib/capybara/apparition/page/frame.rb +3 -0
- data/lib/capybara/apparition/page/frame_manager.rb +2 -1
- data/lib/capybara/apparition/page/keyboard.rb +29 -7
- data/lib/capybara/apparition/page/mouse.rb +20 -6
- data/lib/capybara/apparition/utility.rb +1 -1
- data/lib/capybara/apparition/version.rb +1 -1
- metadata +53 -23
- data/lib/capybara/apparition/dev_tools_protocol/target.rb +0 -64
- data/lib/capybara/apparition/dev_tools_protocol/target_manager.rb +0 -48
- data/lib/capybara/apparition/driver/launcher.rb +0 -217
@@ -47,10 +47,12 @@ module Capybara::Apparition
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def loading(id)
|
50
|
+
puts "Setting loading to #{id}" if ENV['DEBUG']
|
50
51
|
self.loader_id = id
|
51
52
|
end
|
52
53
|
|
53
54
|
def reloading!
|
55
|
+
puts 'Reloading' if ENV['DEBUG']
|
54
56
|
self.loader_id = @prev_loader_id
|
55
57
|
end
|
56
58
|
|
@@ -64,6 +66,7 @@ module Capybara::Apparition
|
|
64
66
|
|
65
67
|
def loaded!
|
66
68
|
@prev_loader_id = loader_id
|
69
|
+
puts "Setting loaded - was #{loader_id}" if ENV['DEBUG']
|
67
70
|
self.loader_id = nil
|
68
71
|
end
|
69
72
|
|
@@ -7,7 +7,7 @@ module Capybara::Apparition
|
|
7
7
|
def initialize(id)
|
8
8
|
@frames = {}
|
9
9
|
@frames_mutex = Mutex.new
|
10
|
-
add(id)
|
10
|
+
add(id).loading(-1)
|
11
11
|
@main_id = @current_id = id
|
12
12
|
end
|
13
13
|
|
@@ -43,6 +43,7 @@ module Capybara::Apparition
|
|
43
43
|
@frames[id]
|
44
44
|
end
|
45
45
|
end
|
46
|
+
alias :[] :get
|
46
47
|
|
47
48
|
def delete(id)
|
48
49
|
@frames_mutex.synchronize do
|
@@ -66,16 +66,30 @@ module Capybara::Apparition
|
|
66
66
|
|
67
67
|
private
|
68
68
|
|
69
|
+
def insert_emoji(str)
|
70
|
+
@page.command('Input.insertText', text: str)
|
71
|
+
end
|
72
|
+
|
69
73
|
def type_with_modifiers(keys, delay:)
|
70
74
|
old_pressed_keys, @pressed_keys = @pressed_keys, {}
|
71
75
|
|
72
76
|
Array(keys).each do |sequence|
|
73
77
|
case sequence
|
74
|
-
when Array
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
78
|
+
when Array
|
79
|
+
type_with_modifiers(sequence, delay: delay)
|
80
|
+
when String
|
81
|
+
clusters = sequence.grapheme_clusters.chunk { |gc| gc.match?(/\p{Emoji Presentation}/) }
|
82
|
+
clusters.each do |emoji, chars|
|
83
|
+
if emoji
|
84
|
+
insert_emoji(chars.join)
|
85
|
+
sleep delay
|
86
|
+
else
|
87
|
+
chars.each do |char|
|
88
|
+
press char
|
89
|
+
sleep delay
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
79
93
|
else
|
80
94
|
press sequence
|
81
95
|
sleep delay
|
@@ -89,7 +103,7 @@ module Capybara::Apparition
|
|
89
103
|
end
|
90
104
|
|
91
105
|
def release_pressed_keys
|
92
|
-
@pressed_keys.
|
106
|
+
@pressed_keys.each_value { |desc| up(desc) }
|
93
107
|
end
|
94
108
|
|
95
109
|
def key_description(key)
|
@@ -102,8 +116,15 @@ module Capybara::Apparition
|
|
102
116
|
location: 0
|
103
117
|
)
|
104
118
|
|
119
|
+
if key.to_sym == :return
|
120
|
+
warn '[DEPRECATION]: Key symbol :return is deprecated in favor of :enter'
|
121
|
+
key = :enter
|
122
|
+
end
|
123
|
+
|
105
124
|
definition = KEY_DEFINITIONS[key.to_sym]
|
106
|
-
raise KeyError, "Unknown key: #{key}" if definition.nil?
|
125
|
+
raise KeyError, "Unknown key: #{key}" if definition.nil? && !key.is_a?(String)
|
126
|
+
|
127
|
+
definition ||= { text: key }
|
107
128
|
|
108
129
|
definition = OpenStruct.new definition
|
109
130
|
|
@@ -321,6 +342,7 @@ module Capybara::Apparition
|
|
321
342
|
'z': { 'keyCode': 90, 'code': 'KeyZ', 'shiftKey': 'Z', 'key': 'z' },
|
322
343
|
'meta': { 'keyCode': 91, 'key': 'Meta', 'code': 'MetaLeft' },
|
323
344
|
'command': { 'keyCode': 91, 'key': 'Meta', 'code': 'MetaLeft' },
|
345
|
+
'cmd': { 'keyCode': 91, 'key': 'Meta', 'code': 'MetaLeft' },
|
324
346
|
'*': { 'keyCode': 106, 'key': '*', 'code': 'NumpadMultiply', 'location': 3 },
|
325
347
|
'+': { 'keyCode': 107, 'key': '+', 'code': 'NumpadAdd', 'location': 3 },
|
326
348
|
'-': { 'keyCode': 109, 'key': '-', 'code': 'NumpadSubtract', 'location': 3 },
|
@@ -6,15 +6,17 @@ module Capybara::Apparition
|
|
6
6
|
@page = page
|
7
7
|
@keyboard = keyboard
|
8
8
|
@current_pos = { x: 0, y: 0 }
|
9
|
+
@current_buttons = BUTTONS[:none]
|
9
10
|
end
|
10
11
|
|
11
|
-
def click_at(x:, y:, button: 'left', count: 1, modifiers: [])
|
12
|
+
def click_at(x:, y:, button: 'left', count: 1, delay: 0, modifiers: [])
|
12
13
|
move_to x: x, y: y
|
13
14
|
count.times do |num|
|
14
15
|
@keyboard.with_keys(modifiers) do
|
15
16
|
mouse_params = { x: x, y: y, button: button, count: num + 1 }
|
16
|
-
down
|
17
|
-
|
17
|
+
down(**mouse_params)
|
18
|
+
sleep(delay || 0)
|
19
|
+
up(**mouse_params)
|
18
20
|
end
|
19
21
|
end
|
20
22
|
self
|
@@ -28,13 +30,15 @@ module Capybara::Apparition
|
|
28
30
|
|
29
31
|
def down(button: 'left', **options)
|
30
32
|
options = @current_pos.merge(button: button).merge(options)
|
31
|
-
mouse_event('mousePressed', options)
|
33
|
+
mouse_event('mousePressed', **options)
|
34
|
+
@current_buttons |= BUTTONS[button.to_sym]
|
32
35
|
self
|
33
36
|
end
|
34
37
|
|
35
38
|
def up(button: 'left', **options)
|
36
39
|
options = @current_pos.merge(button: button).merge(options)
|
37
|
-
|
40
|
+
@current_buttons &= ~BUTTONS[button.to_sym]
|
41
|
+
mouse_event('mouseReleased', **options)
|
38
42
|
self
|
39
43
|
end
|
40
44
|
|
@@ -43,11 +47,21 @@ module Capybara::Apparition
|
|
43
47
|
def mouse_event(type, x:, y:, button: 'none', count: 1)
|
44
48
|
@page.command('Input.dispatchMouseEvent',
|
45
49
|
type: type,
|
46
|
-
button: button,
|
50
|
+
button: button.to_s,
|
51
|
+
buttons: @current_buttons,
|
47
52
|
x: x,
|
48
53
|
y: y,
|
49
54
|
modifiers: @keyboard.modifiers,
|
50
55
|
clickCount: count)
|
51
56
|
end
|
57
|
+
|
58
|
+
BUTTONS = {
|
59
|
+
left: 1,
|
60
|
+
right: 2,
|
61
|
+
middle: 4,
|
62
|
+
back: 8,
|
63
|
+
forward: 16,
|
64
|
+
none: 0
|
65
|
+
}.freeze
|
52
66
|
end
|
53
67
|
end
|
metadata
CHANGED
@@ -1,36 +1,22 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: apparition
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Walpole
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-06-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: backports
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: capybara
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
30
16
|
requirements:
|
31
17
|
- - "~>"
|
32
18
|
- !ruby/object:Gem::Version
|
33
|
-
version: '3.
|
19
|
+
version: '3.13'
|
34
20
|
- - "<"
|
35
21
|
- !ruby/object:Gem::Version
|
36
22
|
version: '4'
|
@@ -40,7 +26,7 @@ dependencies:
|
|
40
26
|
requirements:
|
41
27
|
- - "~>"
|
42
28
|
- !ruby/object:Gem::Version
|
43
|
-
version: '3.
|
29
|
+
version: '3.13'
|
44
30
|
- - "<"
|
45
31
|
- !ruby/object:Gem::Version
|
46
32
|
version: '4'
|
@@ -184,6 +170,48 @@ dependencies:
|
|
184
170
|
- - "~>"
|
185
171
|
- !ruby/object:Gem::Version
|
186
172
|
version: '3.6'
|
173
|
+
- !ruby/object:Gem::Dependency
|
174
|
+
name: rubocop
|
175
|
+
requirement: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - ">="
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0'
|
180
|
+
type: :development
|
181
|
+
prerelease: false
|
182
|
+
version_requirements: !ruby/object:Gem::Requirement
|
183
|
+
requirements:
|
184
|
+
- - ">="
|
185
|
+
- !ruby/object:Gem::Version
|
186
|
+
version: '0'
|
187
|
+
- !ruby/object:Gem::Dependency
|
188
|
+
name: rubocop-performance
|
189
|
+
requirement: !ruby/object:Gem::Requirement
|
190
|
+
requirements:
|
191
|
+
- - ">="
|
192
|
+
- !ruby/object:Gem::Version
|
193
|
+
version: '0'
|
194
|
+
type: :development
|
195
|
+
prerelease: false
|
196
|
+
version_requirements: !ruby/object:Gem::Requirement
|
197
|
+
requirements:
|
198
|
+
- - ">="
|
199
|
+
- !ruby/object:Gem::Version
|
200
|
+
version: '0'
|
201
|
+
- !ruby/object:Gem::Dependency
|
202
|
+
name: rubocop-rspec
|
203
|
+
requirement: !ruby/object:Gem::Requirement
|
204
|
+
requirements:
|
205
|
+
- - ">="
|
206
|
+
- !ruby/object:Gem::Version
|
207
|
+
version: '0'
|
208
|
+
type: :development
|
209
|
+
prerelease: false
|
210
|
+
version_requirements: !ruby/object:Gem::Requirement
|
211
|
+
requirements:
|
212
|
+
- - ">="
|
213
|
+
- !ruby/object:Gem::Version
|
214
|
+
version: '0'
|
187
215
|
- !ruby/object:Gem::Dependency
|
188
216
|
name: selenium-webdriver
|
189
217
|
requirement: !ruby/object:Gem::Requirement
|
@@ -228,19 +256,21 @@ files:
|
|
228
256
|
- lib/capybara/apparition/browser/cookie.rb
|
229
257
|
- lib/capybara/apparition/browser/frame.rb
|
230
258
|
- lib/capybara/apparition/browser/header.rb
|
259
|
+
- lib/capybara/apparition/browser/launcher.rb
|
260
|
+
- lib/capybara/apparition/browser/launcher/local.rb
|
261
|
+
- lib/capybara/apparition/browser/launcher/remote.rb
|
231
262
|
- lib/capybara/apparition/browser/modal.rb
|
263
|
+
- lib/capybara/apparition/browser/page_manager.rb
|
232
264
|
- lib/capybara/apparition/browser/render.rb
|
233
265
|
- lib/capybara/apparition/browser/window.rb
|
266
|
+
- lib/capybara/apparition/configuration.rb
|
234
267
|
- lib/capybara/apparition/console.rb
|
235
268
|
- lib/capybara/apparition/cookie.rb
|
236
269
|
- lib/capybara/apparition/cookie_jar.rb
|
237
270
|
- lib/capybara/apparition/dev_tools_protocol/remote_object.rb
|
238
271
|
- lib/capybara/apparition/dev_tools_protocol/session.rb
|
239
|
-
- lib/capybara/apparition/dev_tools_protocol/target.rb
|
240
|
-
- lib/capybara/apparition/dev_tools_protocol/target_manager.rb
|
241
272
|
- lib/capybara/apparition/driver.rb
|
242
273
|
- lib/capybara/apparition/driver/chrome_client.rb
|
243
|
-
- lib/capybara/apparition/driver/launcher.rb
|
244
274
|
- lib/capybara/apparition/driver/response.rb
|
245
275
|
- lib/capybara/apparition/driver/web_socket_client.rb
|
246
276
|
- lib/capybara/apparition/errors.rb
|
@@ -270,14 +300,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
270
300
|
requirements:
|
271
301
|
- - ">="
|
272
302
|
- !ruby/object:Gem::Version
|
273
|
-
version: 2.
|
303
|
+
version: 2.5.0
|
274
304
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
275
305
|
requirements:
|
276
306
|
- - ">="
|
277
307
|
- !ruby/object:Gem::Version
|
278
308
|
version: '0'
|
279
309
|
requirements: []
|
280
|
-
rubygems_version: 3.
|
310
|
+
rubygems_version: 3.1.2
|
281
311
|
signing_key:
|
282
312
|
specification_version: 4
|
283
313
|
summary: Chrome driver using CDP for Capybara
|
@@ -1,64 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'capybara/apparition/dev_tools_protocol/session'
|
4
|
-
|
5
|
-
module Capybara::Apparition
|
6
|
-
module DevToolsProtocol
|
7
|
-
class Target
|
8
|
-
def initialize(browser, info)
|
9
|
-
@browser = browser
|
10
|
-
@info = info.dup
|
11
|
-
@page = nil
|
12
|
-
end
|
13
|
-
|
14
|
-
def info
|
15
|
-
@info.dup.freeze
|
16
|
-
end
|
17
|
-
|
18
|
-
def update(new_info)
|
19
|
-
@info ||= {}
|
20
|
-
@info.merge!(new_info)
|
21
|
-
info
|
22
|
-
end
|
23
|
-
|
24
|
-
def id
|
25
|
-
@info['targetId']
|
26
|
-
end
|
27
|
-
|
28
|
-
def context_id
|
29
|
-
@info['browserContextId']
|
30
|
-
end
|
31
|
-
|
32
|
-
def title
|
33
|
-
@info['title']
|
34
|
-
end
|
35
|
-
|
36
|
-
def url
|
37
|
-
@info['url']
|
38
|
-
end
|
39
|
-
|
40
|
-
def page
|
41
|
-
@page ||= begin
|
42
|
-
if info['type'] == 'page'
|
43
|
-
Page.create(@browser, create_session, id,
|
44
|
-
ignore_https_errors: @browser.ignore_https_errors,
|
45
|
-
js_errors: @browser.js_errors).inherit(@info.delete('inherit'))
|
46
|
-
else
|
47
|
-
nil
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def close
|
53
|
-
@browser.command('Target.closeTarget', targetId: id)
|
54
|
-
end
|
55
|
-
|
56
|
-
private
|
57
|
-
|
58
|
-
def create_session
|
59
|
-
session_id = @browser.command('Target.attachToTarget', targetId: id)['sessionId']
|
60
|
-
Session.new(@browser, @browser.client, id, session_id)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
@@ -1,48 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'capybara/apparition/dev_tools_protocol/target'
|
4
|
-
|
5
|
-
module Capybara::Apparition
|
6
|
-
module DevToolsProtocol
|
7
|
-
class TargetManager
|
8
|
-
def initialize(browser)
|
9
|
-
@browser = browser
|
10
|
-
@targets = {}
|
11
|
-
end
|
12
|
-
|
13
|
-
def get(id)
|
14
|
-
@targets[id]
|
15
|
-
end
|
16
|
-
|
17
|
-
def of_type(type)
|
18
|
-
@targets.select { |_id, target| target.info['type'] == type }
|
19
|
-
end
|
20
|
-
|
21
|
-
def add(id, target)
|
22
|
-
raise ArgumentError, 'Target already exists' if @targets.key?(id)
|
23
|
-
|
24
|
-
@targets[id] = if target.is_a? DevToolsProtocol::Target
|
25
|
-
target
|
26
|
-
else
|
27
|
-
DevToolsProtocol::Target.new(@browser, target)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def delete(id)
|
32
|
-
@targets.delete(id)
|
33
|
-
end
|
34
|
-
|
35
|
-
def pages
|
36
|
-
@targets.values.select { |target| target.info['type'] == 'page' }.map(&:page)
|
37
|
-
end
|
38
|
-
|
39
|
-
def target?(id)
|
40
|
-
@targets.key?(id)
|
41
|
-
end
|
42
|
-
|
43
|
-
def window_handles
|
44
|
-
@targets.values.select { |target| target.info['type'] == 'page' }.map(&:id).compact
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
@@ -1,217 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Capybara::Apparition
|
4
|
-
class Browser
|
5
|
-
class Launcher
|
6
|
-
KILL_TIMEOUT = 2
|
7
|
-
|
8
|
-
def self.start(*args)
|
9
|
-
new(*args).tap(&:start)
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.process_killer(pid)
|
13
|
-
proc do
|
14
|
-
begin
|
15
|
-
if Capybara::Apparition.windows?
|
16
|
-
::Process.kill('KILL', pid)
|
17
|
-
else
|
18
|
-
::Process.kill('TERM', pid)
|
19
|
-
timer = Capybara::Helpers.timer(expire_in: KILL_TIMEOUT)
|
20
|
-
while ::Process.wait(pid, ::Process::WNOHANG).nil?
|
21
|
-
sleep 0.05
|
22
|
-
next unless timer.expired?
|
23
|
-
|
24
|
-
::Process.kill('KILL', pid)
|
25
|
-
::Process.wait(pid)
|
26
|
-
break
|
27
|
-
end
|
28
|
-
end
|
29
|
-
rescue Errno::ESRCH, Errno::ECHILD # rubocop:disable Lint/HandleExceptions
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def initialize(headless:, **options)
|
35
|
-
@path = ENV['BROWSER_PATH']
|
36
|
-
@options = DEFAULT_OPTIONS.merge(options[:browser_options] || {})
|
37
|
-
if headless
|
38
|
-
@options.merge!(HEADLESS_OPTIONS)
|
39
|
-
@options['disable-gpu'] = nil if Capybara::Apparition.windows?
|
40
|
-
end
|
41
|
-
@options['user-data-dir'] = Dir.mktmpdir
|
42
|
-
end
|
43
|
-
|
44
|
-
def start
|
45
|
-
@output = Queue.new
|
46
|
-
@read_io, @write_io = IO.pipe
|
47
|
-
|
48
|
-
@out_thread = Thread.new do
|
49
|
-
while !@read_io.eof? && (data = @read_io.readpartial(512))
|
50
|
-
@output << data
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
process_options = { in: File::NULL }
|
55
|
-
process_options[:pgroup] = true unless Capybara::Apparition.windows?
|
56
|
-
process_options[:out] = process_options[:err] = @write_io if Capybara::Apparition.mri?
|
57
|
-
|
58
|
-
redirect_stdout do
|
59
|
-
cmd = [path] + @options.map { |k, v| v.nil? ? "--#{k}" : "--#{k}=#{v}" }
|
60
|
-
@pid = ::Process.spawn(*cmd, process_options)
|
61
|
-
ObjectSpace.define_finalizer(self, self.class.process_killer(@pid))
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def stop
|
66
|
-
return unless @pid
|
67
|
-
|
68
|
-
kill
|
69
|
-
ObjectSpace.undefine_finalizer(self)
|
70
|
-
end
|
71
|
-
|
72
|
-
def restart
|
73
|
-
stop
|
74
|
-
start
|
75
|
-
end
|
76
|
-
|
77
|
-
def host
|
78
|
-
@host ||= ws_url.host
|
79
|
-
end
|
80
|
-
|
81
|
-
def port
|
82
|
-
@port ||= ws_url.port
|
83
|
-
end
|
84
|
-
|
85
|
-
def ws_url
|
86
|
-
@ws_url ||= begin
|
87
|
-
regexp = %r{DevTools listening on (ws://.*)}
|
88
|
-
url = nil
|
89
|
-
loop do
|
90
|
-
break if (url = @output.pop.scan(regexp)[0])
|
91
|
-
end
|
92
|
-
@out_thread.kill
|
93
|
-
close_io
|
94
|
-
Addressable::URI.parse(url[0])
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
private
|
99
|
-
|
100
|
-
def redirect_stdout
|
101
|
-
if Capybara::Apparition.mri?
|
102
|
-
yield
|
103
|
-
else
|
104
|
-
begin
|
105
|
-
prev = STDOUT.dup
|
106
|
-
$stdout = @write_io
|
107
|
-
STDOUT.reopen(@write_io)
|
108
|
-
yield
|
109
|
-
ensure
|
110
|
-
STDOUT.reopen(prev)
|
111
|
-
$stdout = STDOUT
|
112
|
-
prev.close
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
def kill
|
118
|
-
self.class.process_killer(@pid).call
|
119
|
-
@pid = nil
|
120
|
-
end
|
121
|
-
|
122
|
-
def close_io
|
123
|
-
[@write_io, @read_io].each do |io|
|
124
|
-
begin
|
125
|
-
io.close unless io.closed?
|
126
|
-
rescue IOError
|
127
|
-
raise unless RUBY_ENGINE == 'jruby'
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
def path
|
133
|
-
host_os = RbConfig::CONFIG['host_os']
|
134
|
-
@path ||= case RbConfig::CONFIG['host_os']
|
135
|
-
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
136
|
-
windows_path
|
137
|
-
when /darwin|mac os/
|
138
|
-
macosx_path
|
139
|
-
when /linux|solaris|bsd/
|
140
|
-
find_first_binary('google-chrome', 'chrome') || '/usr/bin/chrome'
|
141
|
-
else
|
142
|
-
raise ArgumentError, "unknown os: #{host_os.inspect}"
|
143
|
-
end
|
144
|
-
|
145
|
-
raise ArgumentError, 'Unable to find Chrome executeable' unless File.file?(@path.to_s) && File.executable?(@path.to_s)
|
146
|
-
|
147
|
-
@path
|
148
|
-
end
|
149
|
-
|
150
|
-
def windows_path
|
151
|
-
raise ArgumentError, 'Not yet Implemented'
|
152
|
-
end
|
153
|
-
|
154
|
-
def macosx_path
|
155
|
-
path = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
|
156
|
-
path = File.expand_path("~#{path}") unless File.exist?(path)
|
157
|
-
path = find_first_binary('Google Chrome') unless File.exist?(path)
|
158
|
-
path
|
159
|
-
end
|
160
|
-
|
161
|
-
def find_first_binary(*binaries)
|
162
|
-
paths = ENV['PATH'].split(File::PATH_SEPARATOR)
|
163
|
-
|
164
|
-
binaries.each do |binary|
|
165
|
-
paths.each do |path|
|
166
|
-
full_path = File.join(path, binary)
|
167
|
-
exe = Dir.glob(full_path).find { |f| File.executable?(f) }
|
168
|
-
return exe if exe
|
169
|
-
end
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
# Chromium command line options
|
174
|
-
# https://peter.sh/experiments/chromium-command-line-switches/
|
175
|
-
DEFAULT_BOOLEAN_OPTIONS = %w[
|
176
|
-
disable-background-networking
|
177
|
-
disable-background-timer-throttling
|
178
|
-
disable-breakpad
|
179
|
-
disable-client-side-phishing-detection
|
180
|
-
disable-default-apps
|
181
|
-
disable-dev-shm-usage
|
182
|
-
disable-extensions
|
183
|
-
disable-features=site-per-process
|
184
|
-
disable-hang-monitor
|
185
|
-
disable-infobars
|
186
|
-
disable-popup-blocking
|
187
|
-
disable-prompt-on-repost
|
188
|
-
disable-sync
|
189
|
-
disable-translate
|
190
|
-
metrics-recording-only
|
191
|
-
no-first-run
|
192
|
-
safebrowsing-disable-auto-update
|
193
|
-
enable-automation
|
194
|
-
password-store=basic
|
195
|
-
use-mock-keychain
|
196
|
-
keep-alive-for-test
|
197
|
-
].freeze
|
198
|
-
# Note: --no-sandbox is not needed if you properly setup a user in the container.
|
199
|
-
# https://github.com/ebidel/lighthouse-ci/blob/master/builder/Dockerfile#L35-L40
|
200
|
-
# no-sandbox
|
201
|
-
# disable-web-security
|
202
|
-
DEFAULT_VALUE_OPTIONS = {
|
203
|
-
'window-size' => '1024,768',
|
204
|
-
'homepage' => 'about:blank',
|
205
|
-
'remote-debugging-address' => '127.0.0.1'
|
206
|
-
}.freeze
|
207
|
-
DEFAULT_OPTIONS = DEFAULT_BOOLEAN_OPTIONS.each_with_object({}) { |opt, hsh| hsh[opt] = nil }
|
208
|
-
.merge(DEFAULT_VALUE_OPTIONS)
|
209
|
-
.freeze
|
210
|
-
HEADLESS_OPTIONS = {
|
211
|
-
'headless' => nil,
|
212
|
-
'hide-scrollbars' => nil,
|
213
|
-
'mute-audio' => nil
|
214
|
-
}.freeze
|
215
|
-
end
|
216
|
-
end
|
217
|
-
end
|