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
@@ -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
|