apparition 0.1.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capybara::Apparition
|
4
|
+
class Configuration
|
5
|
+
class << self
|
6
|
+
private
|
7
|
+
|
8
|
+
def instance
|
9
|
+
@instance ||= new
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.to_hash
|
14
|
+
instance.freeze.to_hash
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.modify
|
18
|
+
raise 'All configuration must take place before the driver starts' if instance.frozen?
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_accessor :allowed_urls
|
22
|
+
attr_writer :block_unknown_urls
|
23
|
+
attr_accessor :blocked_urls
|
24
|
+
attr_accessor :debug
|
25
|
+
attr_writer :ignore_ssl_errors
|
26
|
+
attr_accessor :proxy
|
27
|
+
attr_accessor :stderr
|
28
|
+
attr_accessor :timeout
|
29
|
+
attr_writer :skip_image_loading
|
30
|
+
attr_accessor :raise_javascript_errors
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
@allowed_urls = []
|
34
|
+
@blocked_urls = []
|
35
|
+
@block_unknown_urls = false
|
36
|
+
@debug = false
|
37
|
+
@ignore_ssl_errors = false
|
38
|
+
@proxy = nil
|
39
|
+
@skip_image_loading = false
|
40
|
+
@stderr = $stderr
|
41
|
+
@timeout = -1
|
42
|
+
@raise_javascript_errors = false
|
43
|
+
end
|
44
|
+
|
45
|
+
def allow_url(url)
|
46
|
+
@allowed_urls << url
|
47
|
+
end
|
48
|
+
|
49
|
+
def block_url(url)
|
50
|
+
@blocked_urls << url
|
51
|
+
end
|
52
|
+
|
53
|
+
def block_unknown_urls
|
54
|
+
@block_unknown_urls = true
|
55
|
+
end
|
56
|
+
|
57
|
+
def block_unknown_urls?
|
58
|
+
@block_unknown_urls
|
59
|
+
end
|
60
|
+
|
61
|
+
def allow_unknown_urls
|
62
|
+
allow_url('*')
|
63
|
+
end
|
64
|
+
|
65
|
+
def ignore_ssl_errors
|
66
|
+
@ignore_ssl_errors = true
|
67
|
+
end
|
68
|
+
|
69
|
+
def ignore_ssl_errors?
|
70
|
+
@ignore_ssl_errors
|
71
|
+
end
|
72
|
+
|
73
|
+
def skip_image_loading
|
74
|
+
@skip_image_loading = true
|
75
|
+
end
|
76
|
+
|
77
|
+
def skip_image_loading?
|
78
|
+
@skip_image_loading
|
79
|
+
end
|
80
|
+
|
81
|
+
def use_proxy(proxy)
|
82
|
+
@proxy = proxy
|
83
|
+
end
|
84
|
+
|
85
|
+
def to_hash
|
86
|
+
{
|
87
|
+
url_whitelist: allowed_urls,
|
88
|
+
block_unknown_urls: block_unknown_urls?,
|
89
|
+
url_blacklist: blocked_urls,
|
90
|
+
debug: debug,
|
91
|
+
ignore_ssl_errors: ignore_ssl_errors?,
|
92
|
+
proxy: proxy,
|
93
|
+
skip_image_loading: skip_image_loading?,
|
94
|
+
stderr: stderr,
|
95
|
+
timeout: timeout,
|
96
|
+
js_errors: raise_javascript_errors
|
97
|
+
}
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -8,8 +8,15 @@ module Capybara::Apparition
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def log(type, message, **options)
|
11
|
+
return unless @logger
|
12
|
+
|
11
13
|
@messages << OpenStruct.new(type: type, message: message, **options)
|
12
|
-
|
14
|
+
message_to_log = "#{type}: #{message}"
|
15
|
+
if @logger.respond_to?(:puts)
|
16
|
+
@logger.puts(message_to_log)
|
17
|
+
else
|
18
|
+
@logger.info(message_to_log)
|
19
|
+
end
|
13
20
|
end
|
14
21
|
|
15
22
|
def clear
|
@@ -19,13 +19,18 @@ module Capybara::Apparition
|
|
19
19
|
def cyclic_checked_value(object_cache)
|
20
20
|
if object?
|
21
21
|
if array?
|
22
|
-
extract_properties_array(get_remote_object(
|
22
|
+
extract_properties_array(get_remote_object(id), object_cache)
|
23
23
|
elsif node?
|
24
24
|
params
|
25
|
-
elsif
|
26
|
-
|
25
|
+
elsif date?
|
26
|
+
res = get_date_string(id)
|
27
|
+
DateTime.parse(res)
|
27
28
|
elsif window_class?
|
28
|
-
{ object_id:
|
29
|
+
{ object_id: id }
|
30
|
+
elsif validity_state?
|
31
|
+
extract_properties_object(get_remote_object(id, false), object_cache)
|
32
|
+
elsif object_class? || css_style? || classname?
|
33
|
+
extract_properties_object(get_remote_object(id), object_cache)
|
29
34
|
else
|
30
35
|
params['value']
|
31
36
|
end
|
@@ -38,13 +43,17 @@ module Capybara::Apparition
|
|
38
43
|
|
39
44
|
def object?; type == 'object' end
|
40
45
|
def array?; subtype == 'array' end
|
46
|
+
def date?; subtype == 'date' end
|
41
47
|
def node?; subtype == 'node' end
|
42
48
|
def object_class?; classname == 'Object' end
|
49
|
+
def css_style?; classname == 'CSSStyleDeclaration' end
|
43
50
|
def window_class?; classname == 'Window' end
|
51
|
+
def validity_state?; classname == 'ValidityState' end
|
52
|
+
def classname?; !classname.nil? end
|
44
53
|
|
45
54
|
def type; params['type'] end
|
46
55
|
def subtype; params['subtype'] end
|
47
|
-
def
|
56
|
+
def id; params['objectId'] end
|
48
57
|
def classname; params['className'] end
|
49
58
|
|
50
59
|
def extract_properties_array(properties, object_cache)
|
@@ -80,8 +89,15 @@ module Capybara::Apparition
|
|
80
89
|
end
|
81
90
|
end
|
82
91
|
|
83
|
-
def get_remote_object(id)
|
84
|
-
@page.command('Runtime.getProperties', objectId: id, ownProperties:
|
92
|
+
def get_remote_object(id, own_props = true)
|
93
|
+
@page.command('Runtime.getProperties', objectId: id, ownProperties: own_props)['result']
|
94
|
+
end
|
95
|
+
|
96
|
+
def get_date_string(id)
|
97
|
+
@page.command('Runtime.callFunctionOn',
|
98
|
+
functionDeclaration: 'function(){ return this.toUTCString() }',
|
99
|
+
objectId: id,
|
100
|
+
returnByValue: true).dig('result', 'value')
|
85
101
|
end
|
86
102
|
end
|
87
103
|
end
|
@@ -3,12 +3,11 @@
|
|
3
3
|
module Capybara::Apparition
|
4
4
|
module DevToolsProtocol
|
5
5
|
class Session
|
6
|
-
attr_reader :browser, :connection, :
|
6
|
+
attr_reader :browser, :connection, :session_id
|
7
7
|
|
8
|
-
def initialize(browser, connection,
|
8
|
+
def initialize(browser, connection, session_id)
|
9
9
|
@browser = browser
|
10
10
|
@connection = connection
|
11
|
-
@target_id = target_id
|
12
11
|
@session_id = session_id
|
13
12
|
@handlers = []
|
14
13
|
end
|
@@ -23,7 +22,7 @@ module Capybara::Apparition
|
|
23
22
|
end
|
24
23
|
|
25
24
|
def async_command(name, **params)
|
26
|
-
send_cmd(name, params).discard_result
|
25
|
+
send_cmd(name, **params).discard_result
|
27
26
|
end
|
28
27
|
|
29
28
|
def async_commands(*names)
|
@@ -3,7 +3,8 @@
|
|
3
3
|
require 'uri'
|
4
4
|
require 'forwardable'
|
5
5
|
require 'capybara/apparition/driver/chrome_client'
|
6
|
-
require 'capybara/apparition/
|
6
|
+
require 'capybara/apparition/configuration'
|
7
|
+
require 'capybara/apparition/browser/launcher'
|
7
8
|
|
8
9
|
module Capybara::Apparition
|
9
10
|
class Driver < Capybara::Driver::Base
|
@@ -15,7 +16,7 @@ module Capybara::Apparition
|
|
15
16
|
|
16
17
|
delegate %i[restart current_url status_code body
|
17
18
|
title frame_title frame_url switch_to_frame
|
18
|
-
window_handles close_window
|
19
|
+
window_handles close_window switch_to_window
|
19
20
|
paper_size= zoom_factor=
|
20
21
|
scroll_to
|
21
22
|
network_traffic clear_network_traffic
|
@@ -32,6 +33,7 @@ module Capybara::Apparition
|
|
32
33
|
@browser = nil
|
33
34
|
@inspector = nil
|
34
35
|
@client = nil
|
36
|
+
@launcher = nil
|
35
37
|
@started = false
|
36
38
|
end
|
37
39
|
|
@@ -45,14 +47,14 @@ module Capybara::Apparition
|
|
45
47
|
|
46
48
|
def browser
|
47
49
|
@browser ||= begin
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
50
|
+
Browser.new(client, browser_logger) do |browser|
|
51
|
+
browser.js_errors = options.fetch(:js_errors, true)
|
52
|
+
browser.ignore_https_errors = options.fetch(:ignore_https_errors, false)
|
53
|
+
browser.extensions = options.fetch(:extensions, [])
|
54
|
+
browser.debug = options.fetch(:debug, false)
|
55
|
+
browser.url_blacklist = options[:url_blacklist] || []
|
56
|
+
browser.url_whitelist = options[:url_whitelist] || []
|
57
|
+
end
|
56
58
|
end
|
57
59
|
end
|
58
60
|
|
@@ -62,10 +64,7 @@ module Capybara::Apparition
|
|
62
64
|
|
63
65
|
def client
|
64
66
|
@client ||= begin
|
65
|
-
@launcher ||= Browser::Launcher.start(
|
66
|
-
headless: options.fetch(:headless, true),
|
67
|
-
browser_options: browser_options
|
68
|
-
)
|
67
|
+
@launcher ||= Browser::Launcher.start(options)
|
69
68
|
ws_url = @launcher.ws_url
|
70
69
|
::Capybara::Apparition::ChromeClient.client(ws_url.to_s)
|
71
70
|
end
|
@@ -98,7 +97,9 @@ module Capybara::Apparition
|
|
98
97
|
end
|
99
98
|
|
100
99
|
def find(method, selector)
|
101
|
-
browser.find(method, selector).map
|
100
|
+
browser.find(method, selector).map do |page_id, id, attrs|
|
101
|
+
Capybara::Apparition::Node.new(self, page_id, id, attrs)
|
102
|
+
end
|
102
103
|
end
|
103
104
|
|
104
105
|
def find_xpath(selector)
|
@@ -114,20 +115,26 @@ module Capybara::Apparition
|
|
114
115
|
end
|
115
116
|
|
116
117
|
def evaluate_script(script, *args)
|
117
|
-
|
118
|
+
retry_if_wrong_world do
|
119
|
+
unwrap_script_result(browser.evaluate(script, *native_args(args)))
|
120
|
+
end
|
118
121
|
end
|
119
122
|
|
120
123
|
def evaluate_async_script(script, *args)
|
121
|
-
|
124
|
+
retry_if_wrong_world do
|
125
|
+
unwrap_script_result(browser.evaluate_async(script, session_wait_time, *native_args(args)))
|
126
|
+
end
|
122
127
|
end
|
123
128
|
|
124
129
|
def execute_script(script, *args)
|
125
|
-
|
130
|
+
retry_if_wrong_world do
|
131
|
+
browser.execute(script, *native_args(args))
|
132
|
+
end
|
126
133
|
nil
|
127
134
|
end
|
128
135
|
|
129
136
|
def current_window_handle
|
130
|
-
browser.
|
137
|
+
browser.current_window_handle
|
131
138
|
end
|
132
139
|
|
133
140
|
def no_such_window_error
|
@@ -158,28 +165,33 @@ module Capybara::Apparition
|
|
158
165
|
def resize(width, height)
|
159
166
|
browser.resize(width, height, screen: options[:screen_size])
|
160
167
|
end
|
161
|
-
|
168
|
+
|
169
|
+
def resize_window(width, height)
|
170
|
+
warn '[DEPRECATION] Capybara::Apparition::Driver#resize_window ' \
|
171
|
+
'is deprecated. Please use Capybara::Window#resize_to instead.'
|
172
|
+
resize(width, height)
|
173
|
+
end
|
162
174
|
|
163
175
|
def resize_window_to(handle, width, height)
|
164
|
-
|
176
|
+
_within_window(handle) do
|
165
177
|
resize(width, height)
|
166
178
|
end
|
167
179
|
end
|
168
180
|
|
169
181
|
def maximize_window(handle)
|
170
|
-
|
182
|
+
_within_window(handle) do
|
171
183
|
browser.maximize
|
172
184
|
end
|
173
185
|
end
|
174
186
|
|
175
187
|
def fullscreen_window(handle)
|
176
|
-
|
188
|
+
_within_window(handle) do
|
177
189
|
browser.fullscreen
|
178
190
|
end
|
179
191
|
end
|
180
192
|
|
181
193
|
def window_size(handle)
|
182
|
-
|
194
|
+
_within_window(handle) do
|
183
195
|
evaluate_script('[window.innerWidth, window.innerHeight]')
|
184
196
|
end
|
185
197
|
end
|
@@ -201,14 +213,12 @@ module Capybara::Apparition
|
|
201
213
|
end
|
202
214
|
|
203
215
|
def add_header(name, value, options = {})
|
204
|
-
browser.add_header({ name => value }, { permanent: true }.merge(options))
|
216
|
+
browser.add_header({ name => value }, **{ permanent: true }.merge(options))
|
205
217
|
end
|
206
218
|
alias_method :header, :add_header
|
207
219
|
|
208
220
|
def response_headers
|
209
|
-
browser.response_headers.
|
210
|
-
hsh[key.split('-').map(&:capitalize).join('-')] = value
|
211
|
-
end
|
221
|
+
browser.response_headers.transform_keys { |key| key.split('-').map(&:capitalize).join('-') }
|
212
222
|
end
|
213
223
|
|
214
224
|
def set_cookie(name, value = nil, options = {})
|
@@ -228,7 +238,7 @@ module Capybara::Apparition
|
|
228
238
|
end
|
229
239
|
|
230
240
|
def proxy_authorize(user = nil, password = nil)
|
231
|
-
browser.
|
241
|
+
browser.set_proxy_auth(user, password)
|
232
242
|
end
|
233
243
|
|
234
244
|
def basic_authorize(user = nil, password = nil)
|
@@ -266,12 +276,12 @@ module Capybara::Apparition
|
|
266
276
|
write.close
|
267
277
|
end
|
268
278
|
|
269
|
-
STDERR.puts "Apparition execution paused. Press enter (or run 'kill -CONT #{Process.pid}') to continue."
|
279
|
+
STDERR.puts "Apparition execution paused. Press enter (or run 'kill -CONT #{Process.pid}') to continue." # rubocop:disable Style/StderrPuts
|
270
280
|
|
271
281
|
signal = false
|
272
282
|
old_trap = trap('SIGCONT') do
|
273
283
|
signal = true
|
274
|
-
STDERR.puts "\nSignal SIGCONT received"
|
284
|
+
STDERR.puts "\nSignal SIGCONT received" # rubocop:disable Style/StderrPuts
|
275
285
|
end
|
276
286
|
# wait for data on STDIN or signal SIGCONT received
|
277
287
|
keyboard = IO.select([read], nil, nil, 1) until keyboard || signal
|
@@ -280,13 +290,13 @@ module Capybara::Apparition
|
|
280
290
|
begin
|
281
291
|
input = read.read_nonblock(80) # clear out the read buffer
|
282
292
|
puts unless input&.end_with?("\n")
|
283
|
-
rescue EOFError, IO::WaitReadable
|
293
|
+
rescue EOFError, IO::WaitReadable
|
284
294
|
# Ignore problems reading from STDIN.
|
285
295
|
end
|
286
296
|
end
|
287
297
|
ensure
|
288
298
|
trap('SIGCONT', old_trap) # Restore the previous signal handler, if there was one.
|
289
|
-
STDERR.puts 'Continuing'
|
299
|
+
STDERR.puts 'Continuing' # rubocop:disable Style/StderrPuts
|
290
300
|
end
|
291
301
|
|
292
302
|
def wait?
|
@@ -336,8 +346,53 @@ module Capybara::Apparition
|
|
336
346
|
console_messages('error')
|
337
347
|
end
|
338
348
|
|
349
|
+
def within_window(selector, &block)
|
350
|
+
warn 'Driver#within_window is deprecated, please switch to using Session#within_window instead.'
|
351
|
+
_within_window(selector, &block)
|
352
|
+
orig_window = current_window_handle
|
353
|
+
switch_to_window(selector)
|
354
|
+
begin
|
355
|
+
yield
|
356
|
+
ensure
|
357
|
+
switch_to_window(orig_window)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
def version
|
362
|
+
chrome_version = browser.command('Browser.getVersion')
|
363
|
+
format(VERSION_STRING,
|
364
|
+
capybara: Capybara::VERSION,
|
365
|
+
apparition: Capybara::Apparition::VERSION,
|
366
|
+
chrome: chrome_version['product'])
|
367
|
+
end
|
368
|
+
|
369
|
+
def open_new_window
|
370
|
+
# needed because Capybara does arity detection on this method
|
371
|
+
browser.open_new_window
|
372
|
+
end
|
373
|
+
|
339
374
|
private
|
340
375
|
|
376
|
+
def retry_if_wrong_world
|
377
|
+
timer = Capybara::Helpers.timer(expire_in: session_wait_time)
|
378
|
+
begin
|
379
|
+
yield
|
380
|
+
rescue WrongWorld
|
381
|
+
retry unless timer.expired?
|
382
|
+
raise
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
def _within_window(selector)
|
387
|
+
orig_window = current_window_handle
|
388
|
+
switch_to_window(selector)
|
389
|
+
begin
|
390
|
+
yield
|
391
|
+
ensure
|
392
|
+
switch_to_window(orig_window)
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
341
396
|
def browser_options
|
342
397
|
@options[:browser_options]
|
343
398
|
end
|
@@ -363,7 +418,16 @@ module Capybara::Apparition
|
|
363
418
|
if @options[:skip_image_loading]
|
364
419
|
browser_options['blink-settings'] = [browser_options['blink-settings'], 'imagesEnabled=false'].compact.join(',')
|
365
420
|
end
|
421
|
+
|
366
422
|
@options[:browser_options] = browser_options
|
423
|
+
process_cw_options(@options[:cw_options])
|
424
|
+
end
|
425
|
+
|
426
|
+
def process_cw_options(cw_options)
|
427
|
+
return if cw_options.nil?
|
428
|
+
|
429
|
+
(options[:url_blacklist] ||= []).concat cw_options[:url_blacklist]
|
430
|
+
options[:js_errors] ||= cw_options[:js_errors]
|
367
431
|
end
|
368
432
|
|
369
433
|
def process_browser_options(options)
|
@@ -377,7 +441,7 @@ module Capybara::Apparition
|
|
377
441
|
end
|
378
442
|
end
|
379
443
|
when Hash
|
380
|
-
options.
|
444
|
+
options.transform_keys { |option| option.to_s.tr('_', '-') }
|
381
445
|
else
|
382
446
|
raise ArgumentError, 'browser_options must be an Array or a Hash'
|
383
447
|
end
|
@@ -454,7 +518,8 @@ module Capybara::Apparition
|
|
454
518
|
object_cache[arg]
|
455
519
|
when Hash
|
456
520
|
if (arg['subtype'] == 'node') && arg['objectId']
|
457
|
-
|
521
|
+
tag_name = arg['description'].split(/[.#]/, 2)[0]
|
522
|
+
Capybara::Apparition::Node.new(self, browser.current_page, arg['objectId'], tag_name: tag_name)
|
458
523
|
else
|
459
524
|
object_cache[arg] = {}
|
460
525
|
arg.each { |k, v| object_cache[arg][k] = unwrap_script_result(v, object_cache) }
|
@@ -464,5 +529,12 @@ module Capybara::Apparition
|
|
464
529
|
arg
|
465
530
|
end
|
466
531
|
end
|
532
|
+
|
533
|
+
VERSION_STRING = <<~VERSION
|
534
|
+
Versions in use:
|
535
|
+
Capybara: %<capybara>s
|
536
|
+
Apparition: %<apparition>s
|
537
|
+
Chrome: %<chrome>s
|
538
|
+
VERSION
|
467
539
|
end
|
468
540
|
end
|