apparition 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -0
- data/lib/capybara/apparition/dev_tools_protocol/remote_object.rb +88 -0
- data/lib/capybara/apparition/driver.rb +53 -48
- data/lib/capybara/apparition/driver/launcher.rb +44 -44
- data/lib/capybara/apparition/errors.rb +3 -0
- data/lib/capybara/apparition/node.rb +12 -62
- data/lib/capybara/apparition/page.rb +36 -95
- data/lib/capybara/apparition/page/mouse.rb +1 -1
- data/lib/capybara/apparition/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 594a3f464bdb932d89f32f9bc0c3ce132ae5322c167e96016b0e66e292a8d71f
|
4
|
+
data.tar.gz: 4e2bf086855cb7aad5d38727efb96c9945f325e6c4b53f4ab17b4caa5a4caa35
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d4111cb452f2f30f69619bbdfe499b1a44aa00208e255264797440dfed1dd5c98c4045a31ebd6572b902bf16ed62c4873dbfc5229d5ec273296c7ba688cb4c70
|
7
|
+
data.tar.gz: 1c160f3115ec245d2ddb834053913627f1ed34fe681fd8cb892d0a2b9bcd1af7d61f673b392582c84d248d5341239b4b2343ee5eee14c357fd018396f6e919d8
|
data/README.md
CHANGED
@@ -174,6 +174,7 @@ end
|
|
174
174
|
* `:url_whitelist` (Array) - Default session url whitelist - expressed as an array of strings to match against requested URLs.
|
175
175
|
* `:ignore_https_errors` (Boolean) - Ignore certificate errors when connecting to https URLs.
|
176
176
|
* `:browser_options` (Hash) - Extra command line options to pass to Chrome when starting
|
177
|
+
* `:skip_image_loading` (Boolean) - Don't load images
|
177
178
|
|
178
179
|
### URL Blacklisting & Whitelisting ###
|
179
180
|
Apparition supports URL blacklisting, which allows you
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capybara::Apparition
|
4
|
+
module DevToolsProtocol
|
5
|
+
class RemoteObject
|
6
|
+
attr_reader :params
|
7
|
+
|
8
|
+
def initialize(page, params)
|
9
|
+
@params = params
|
10
|
+
@page = page
|
11
|
+
end
|
12
|
+
|
13
|
+
def value
|
14
|
+
cyclic_checked_value({})
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def cyclic_checked_value(object_cache)
|
20
|
+
if object?
|
21
|
+
if array?
|
22
|
+
extract_properties_array(get_remote_object(object_id), object_cache)
|
23
|
+
elsif node?
|
24
|
+
params
|
25
|
+
elsif object_class?
|
26
|
+
extract_properties_object(get_remote_object(object_id), object_cache)
|
27
|
+
elsif window_class?
|
28
|
+
{ object_id: object_id }
|
29
|
+
else
|
30
|
+
params['value']
|
31
|
+
end
|
32
|
+
else
|
33
|
+
params['value']
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def object?; type == 'object' end
|
40
|
+
def array?; subtype == 'array' end
|
41
|
+
def node?; subtype == 'node' end
|
42
|
+
def object_class?; classname == 'Object' end
|
43
|
+
def window_class?; classname == 'Window' end
|
44
|
+
|
45
|
+
def type; params['type'] end
|
46
|
+
def subtype; params['subtype'] end
|
47
|
+
def object_id; params['objectId'] end
|
48
|
+
def classname; params['className'] end
|
49
|
+
|
50
|
+
def extract_properties_array(properties, object_cache)
|
51
|
+
properties.reject { |prop| prop['name'] == 'apparitionId' }
|
52
|
+
.each_with_object([]) do |property, ary|
|
53
|
+
# TODO: We may need to release these objects
|
54
|
+
next unless property['enumerable']
|
55
|
+
|
56
|
+
if property.dig('value', 'subtype') == 'node' # performance shortcut
|
57
|
+
ary.push(property['value'])
|
58
|
+
else
|
59
|
+
ary.push(RemoteObject.new(@page, property['value']).cyclic_checked_value(object_cache))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def extract_properties_object(properties, object_cache)
|
65
|
+
apparition_id = properties&.find { |prop| prop['name'] == 'apparitionId' }
|
66
|
+
&.dig('value', 'value')
|
67
|
+
|
68
|
+
result = if apparition_id
|
69
|
+
return '(cyclic structure)' if object_cache.key?(apparition_id)
|
70
|
+
|
71
|
+
object_cache[apparition_id] = {}
|
72
|
+
end
|
73
|
+
|
74
|
+
properties.reject { |prop| prop['name'] == 'apparitionId' }
|
75
|
+
.each_with_object(result || {}) do |property, hsh|
|
76
|
+
# TODO: We may need to release these objects
|
77
|
+
next unless property['enumerable']
|
78
|
+
|
79
|
+
hsh[property['name']] = RemoteObject.new(@page, property['value']).cyclic_checked_value(object_cache)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_remote_object(id)
|
84
|
+
@page.command('Runtime.getProperties', objectId: id, ownProperties: true)['result']
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -28,6 +28,7 @@ module Capybara::Apparition
|
|
28
28
|
def initialize(app, options = {})
|
29
29
|
@app = app
|
30
30
|
@options = options
|
31
|
+
generate_browser_options
|
31
32
|
@browser = nil
|
32
33
|
@inspector = nil
|
33
34
|
@client = nil
|
@@ -45,10 +46,10 @@ module Capybara::Apparition
|
|
45
46
|
def browser
|
46
47
|
@browser ||= begin
|
47
48
|
browser = Browser.new(client, browser_logger)
|
48
|
-
browser.js_errors = options
|
49
|
-
browser.ignore_https_errors = options
|
49
|
+
browser.js_errors = options.fetch(:js_errors, true)
|
50
|
+
browser.ignore_https_errors = options.fetch(:ignore_https_errors, false)
|
50
51
|
browser.extensions = options.fetch(:extensions, [])
|
51
|
-
browser.debug =
|
52
|
+
browser.debug = options.fetch(:debug, false)
|
52
53
|
browser.url_blacklist = options[:url_blacklist] || []
|
53
54
|
browser.url_whitelist = options[:url_whitelist] || []
|
54
55
|
browser
|
@@ -61,33 +62,15 @@ module Capybara::Apparition
|
|
61
62
|
|
62
63
|
def client
|
63
64
|
@client ||= begin
|
64
|
-
browser_options = {}
|
65
|
-
browser_options['remote-debugging-port'] = options[:port] || 0
|
66
|
-
browser_options['remote-debugging-address'] = options[:host] if options[:host]
|
67
|
-
browser_options['window-size'] = options[:window_size].join(',') if options[:window_size]
|
68
|
-
browser_options.merge! @options[:browser] if @options[:browser]
|
69
65
|
@launcher ||= Browser::Launcher.start(
|
70
|
-
headless: options
|
71
|
-
|
66
|
+
headless: options.fetch(:headless, true),
|
67
|
+
browser_options: browser_options
|
72
68
|
)
|
73
69
|
ws_url = @launcher.ws_url
|
74
70
|
::Capybara::Apparition::ChromeClient.client(ws_url.to_s)
|
75
71
|
end
|
76
72
|
end
|
77
73
|
|
78
|
-
def browser_options
|
79
|
-
list = options[:browser_options] || []
|
80
|
-
# TODO: configure SSL options
|
81
|
-
# PhantomJS defaults to only using SSLv3, which since POODLE (Oct 2014)
|
82
|
-
# many sites have dropped from their supported protocols (eg PayPal,
|
83
|
-
# Braintree).
|
84
|
-
# list += ["--ignore-ssl-errors=yes"] unless list.grep(/ignore-ssl-errors/).any?
|
85
|
-
# list += ["--ssl-protocol=TLSv1"] unless list.grep(/ssl-protocol/).any?
|
86
|
-
# list += ["--remote-debugger-port=#{inspector.port}", "--remote-debugger-autorun=yes"] if inspector
|
87
|
-
# Note: Need to verify what Chrome command line options are valid for this
|
88
|
-
list
|
89
|
-
end
|
90
|
-
|
91
74
|
def quit
|
92
75
|
@client&.stop
|
93
76
|
@launcher&.stop
|
@@ -205,10 +188,10 @@ module Capybara::Apparition
|
|
205
188
|
end
|
206
189
|
|
207
190
|
# TODO: Look at implementing via the CDP Fetch domain when available
|
208
|
-
@options[:
|
209
|
-
@options[:
|
191
|
+
@options[:browser_options] ||= {}
|
192
|
+
@options[:browser_options]['proxy-server'] = "#{type + '=' if type}#{host}:#{port}"
|
210
193
|
bypass = Array(bypass).join(';')
|
211
|
-
@options[:
|
194
|
+
@options[:browser_options]['proxy-bypass-list'] = bypass unless bypass.empty?
|
212
195
|
browser.set_proxy_auth(user, password) if user || password
|
213
196
|
end
|
214
197
|
|
@@ -344,34 +327,56 @@ module Capybara::Apparition
|
|
344
327
|
client.timeout = sec
|
345
328
|
end
|
346
329
|
|
347
|
-
def
|
348
|
-
|
330
|
+
def error_messages
|
331
|
+
console_messages('error')
|
332
|
+
end
|
349
333
|
|
350
|
-
|
351
|
-
when Capybara::Apparition::Node
|
352
|
-
frame_selector
|
353
|
-
when Integer
|
354
|
-
find_css('iframe')[frame_selector]
|
355
|
-
when String
|
356
|
-
find_css("iframe[name='#{frame_selector}']")[0]
|
357
|
-
else
|
358
|
-
raise TypeError, 'Unknown frame selector'
|
359
|
-
# command('FrameFocus')
|
360
|
-
end
|
334
|
+
private
|
361
335
|
|
362
|
-
|
363
|
-
|
364
|
-
yield
|
365
|
-
ensure
|
366
|
-
switch_to_frame(:parent)
|
367
|
-
end
|
336
|
+
def browser_options
|
337
|
+
@options[:browser_options]
|
368
338
|
end
|
369
339
|
|
370
|
-
def
|
371
|
-
|
340
|
+
def generate_browser_options
|
341
|
+
# TODO: configure SSL options
|
342
|
+
# PhantomJS defaults to only using SSLv3, which since POODLE (Oct 2014)
|
343
|
+
# many sites have dropped from their supported protocols (eg PayPal,
|
344
|
+
# Braintree).
|
345
|
+
# list += ["--ignore-ssl-errors=yes"] unless list.grep(/ignore-ssl-errors/).any?
|
346
|
+
# list += ["--ssl-protocol=TLSv1"] unless list.grep(/ssl-protocol/).any?
|
347
|
+
# list += ["--remote-debugger-port=#{inspector.port}", "--remote-debugger-autorun=yes"] if inspector
|
348
|
+
# Note: Need to verify what Chrome command line options are valid for this
|
349
|
+
browser_options = {}
|
350
|
+
browser_options['remote-debugging-port'] = @options[:port] || 0
|
351
|
+
browser_options['remote-debugging-address'] = @options[:host] if @options[:host]
|
352
|
+
browser_options['window-size'] = @options[:window_size].join(',') if @options[:window_size]
|
353
|
+
if @options[:browser]
|
354
|
+
warn ':browser is deprecated, please pass as :browser_options instead.'
|
355
|
+
browser_options.merge! process_browser_options(@options[:browser])
|
356
|
+
end
|
357
|
+
browser_options.merge! process_browser_options(@options[:browser_options]) if @options[:browser_options]
|
358
|
+
if @options[:skip_image_loading]
|
359
|
+
browser_options['blink-settings'] = [browser_options['blink-settings'], 'imagesEnabled=false'].compact.join(',')
|
360
|
+
end
|
361
|
+
@options[:browser_options] = browser_options
|
372
362
|
end
|
373
363
|
|
374
|
-
|
364
|
+
def process_browser_options(options)
|
365
|
+
case options
|
366
|
+
when Array
|
367
|
+
options.compact.each_with_object({}) do |option, hsh|
|
368
|
+
if option.is_a? Hash
|
369
|
+
hsh.merge! process_browser_options(option)
|
370
|
+
else
|
371
|
+
hsh[option.to_s.tr('_', '-')] = nil
|
372
|
+
end
|
373
|
+
end
|
374
|
+
when Hash
|
375
|
+
options.each_with_object({}) { |(option, val), hsh| hsh[option.to_s.tr('_', '-')] = val }
|
376
|
+
else
|
377
|
+
raise ArgumentError, 'browser_options must be an Array or a Hash'
|
378
|
+
end
|
379
|
+
end
|
375
380
|
|
376
381
|
def parse_raw_cookie(raw)
|
377
382
|
parts = raw.split(/;\s*/)
|
@@ -5,49 +5,6 @@ module Capybara::Apparition
|
|
5
5
|
class Launcher
|
6
6
|
KILL_TIMEOUT = 2
|
7
7
|
|
8
|
-
# Chromium command line options
|
9
|
-
# https://peter.sh/experiments/chromium-command-line-switches/
|
10
|
-
DEFAULT_BOOLEAN_OPTIONS = %w[
|
11
|
-
disable-background-networking
|
12
|
-
disable-background-timer-throttling
|
13
|
-
disable-breakpad
|
14
|
-
disable-client-side-phishing-detection
|
15
|
-
disable-default-apps
|
16
|
-
disable-dev-shm-usage
|
17
|
-
disable-extensions
|
18
|
-
disable-features=site-per-process
|
19
|
-
disable-hang-monitor
|
20
|
-
disable-infobars
|
21
|
-
disable-popup-blocking
|
22
|
-
disable-prompt-on-repost
|
23
|
-
disable-sync
|
24
|
-
disable-translate
|
25
|
-
metrics-recording-only
|
26
|
-
no-first-run
|
27
|
-
safebrowsing-disable-auto-update
|
28
|
-
enable-automation
|
29
|
-
password-store=basic
|
30
|
-
use-mock-keychain
|
31
|
-
keep-alive-for-test
|
32
|
-
].freeze
|
33
|
-
# Note: --no-sandbox is not needed if you properly setup a user in the container.
|
34
|
-
# https://github.com/ebidel/lighthouse-ci/blob/master/builder/Dockerfile#L35-L40
|
35
|
-
# no-sandbox
|
36
|
-
# disable-web-security
|
37
|
-
DEFAULT_VALUE_OPTIONS = {
|
38
|
-
'window-size' => '1024,768',
|
39
|
-
'homepage' => 'about:blank',
|
40
|
-
'remote-debugging-address' => '127.0.0.1'
|
41
|
-
}.freeze
|
42
|
-
DEFAULT_OPTIONS = DEFAULT_BOOLEAN_OPTIONS.each_with_object({}) { |opt, hsh| hsh[opt] = nil }
|
43
|
-
.merge(DEFAULT_VALUE_OPTIONS)
|
44
|
-
.freeze
|
45
|
-
HEADLESS_OPTIONS = {
|
46
|
-
'headless' => nil,
|
47
|
-
'hide-scrollbars' => nil,
|
48
|
-
'mute-audio' => nil
|
49
|
-
}.freeze
|
50
|
-
|
51
8
|
def self.start(*args)
|
52
9
|
new(*args).tap(&:start)
|
53
10
|
end
|
@@ -76,7 +33,7 @@ module Capybara::Apparition
|
|
76
33
|
|
77
34
|
def initialize(headless:, **options)
|
78
35
|
@path = ENV['BROWSER_PATH']
|
79
|
-
@options = DEFAULT_OPTIONS.merge(options
|
36
|
+
@options = DEFAULT_OPTIONS.merge(options[:browser_options] || {})
|
80
37
|
if headless
|
81
38
|
@options.merge!(HEADLESS_OPTIONS)
|
82
39
|
@options['disable-gpu'] = nil if Capybara::Apparition.windows?
|
@@ -212,6 +169,49 @@ module Capybara::Apparition
|
|
212
169
|
end
|
213
170
|
end
|
214
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
215
|
end
|
216
216
|
end
|
217
217
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'ostruct'
|
4
|
+
require 'capybara/apparition/dev_tools_protocol/remote_object'
|
4
5
|
require 'capybara/apparition/node/drag'
|
5
6
|
|
6
7
|
module Capybara::Apparition
|
@@ -345,7 +346,6 @@ module Capybara::Apparition
|
|
345
346
|
end
|
346
347
|
|
347
348
|
def scroll_to(element, location, position = nil)
|
348
|
-
# location, element = element, nil if element.is_a? Symbol
|
349
349
|
if element.is_a? Capybara::Apparition::Node
|
350
350
|
scroll_element_to_location(element, location)
|
351
351
|
elsif location.is_a? Symbol
|
@@ -407,39 +407,13 @@ module Capybara::Apparition
|
|
407
407
|
raise exception_details
|
408
408
|
end
|
409
409
|
|
410
|
-
|
411
|
-
if result['type'] == 'object'
|
412
|
-
if result['subtype'] == 'array'
|
413
|
-
remote_object = @page.command('Runtime.getProperties',
|
414
|
-
objectId: result['objectId'],
|
415
|
-
ownProperties: true)
|
416
|
-
|
417
|
-
return extract_properties_array(remote_object['result'])
|
418
|
-
elsif result['subtype'] == 'node'
|
419
|
-
return result
|
420
|
-
elsif result['className'] == 'Object'
|
421
|
-
remote_object = @page.command('Runtime.getProperties',
|
422
|
-
objectId: result['objectId'],
|
423
|
-
ownProperties: true)
|
424
|
-
extract_properties_object(remote_object['result'])
|
425
|
-
else
|
426
|
-
result['value']
|
427
|
-
end
|
428
|
-
else
|
429
|
-
result['value']
|
430
|
-
end
|
410
|
+
DevToolsProtocol::RemoteObject.new(@page, response['result'] || response['object']).value
|
431
411
|
end
|
432
412
|
|
433
413
|
def set_text(value, clear: nil, **_unused)
|
434
414
|
value = value.to_s
|
435
415
|
if value.empty? && clear.nil?
|
436
|
-
evaluate_on
|
437
|
-
() => {
|
438
|
-
this.focus();
|
439
|
-
this.value = '';
|
440
|
-
this.dispatchEvent(new Event('change', { bubbles: true }));
|
441
|
-
}
|
442
|
-
JS
|
416
|
+
evaluate_on CLEAR_ELEMENT_JS
|
443
417
|
elsif clear == :backspace
|
444
418
|
# Clear field by sending the correct number of backspace keys.
|
445
419
|
backspaces = [:backspace] * self.value.to_s.length
|
@@ -460,9 +434,7 @@ module Capybara::Apparition
|
|
460
434
|
end
|
461
435
|
|
462
436
|
def set_files(files)
|
463
|
-
@page.command('DOM.setFileInputFiles',
|
464
|
-
files: Array(files),
|
465
|
-
objectId: id)
|
437
|
+
@page.command('DOM.setFileInputFiles', files: Array(files), objectId: id)
|
466
438
|
end
|
467
439
|
|
468
440
|
def set_date(value)
|
@@ -597,36 +569,6 @@ module Capybara::Apparition
|
|
597
569
|
end
|
598
570
|
private_constant :SettableValue
|
599
571
|
|
600
|
-
def extract_properties_array(properties)
|
601
|
-
properties.each_with_object([]) do |property, results|
|
602
|
-
if property['enumerable']
|
603
|
-
if property.dig('value', 'subtype') == 'node'
|
604
|
-
results.push(property['value'])
|
605
|
-
else
|
606
|
-
# releasePromises.push(helper.releaseObject(@element._client, property.value))
|
607
|
-
results.push(property.dig('value', 'value'))
|
608
|
-
end
|
609
|
-
end
|
610
|
-
# await Promise.all(releasePromises);
|
611
|
-
# id = (@page._elements.push(element)-1 for element from result)[0]
|
612
|
-
#
|
613
|
-
# new Apparition.Node @page, id
|
614
|
-
|
615
|
-
# releasePromises = [helper.releaseObject(@element._client, remote_object)]
|
616
|
-
end
|
617
|
-
end
|
618
|
-
|
619
|
-
def extract_properties_object(properties)
|
620
|
-
properties.each_with_object({}) do |property, object|
|
621
|
-
if property['enumerable']
|
622
|
-
object[property['name']] = property['value']['value']
|
623
|
-
else # rubocop:disable Style/EmptyElse
|
624
|
-
# releasePromises.push(helper.releaseObject(@element._client, property.value))
|
625
|
-
end
|
626
|
-
# releasePromises = [helper.releaseObject(@element._client, remote_object)]
|
627
|
-
end
|
628
|
-
end
|
629
|
-
|
630
572
|
####################
|
631
573
|
# JS snippets
|
632
574
|
####################
|
@@ -821,5 +763,13 @@ module Capybara::Apparition
|
|
821
763
|
}, {})
|
822
764
|
}
|
823
765
|
JS
|
766
|
+
|
767
|
+
CLEAR_ELEMENT_JS = <<~JS
|
768
|
+
() => {
|
769
|
+
this.focus();
|
770
|
+
this.value = '';
|
771
|
+
this.dispatchEvent(new Event('change', { bubbles: true }));
|
772
|
+
}
|
773
|
+
JS
|
824
774
|
end
|
825
775
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'capybara/apparition/dev_tools_protocol/remote_object'
|
3
4
|
require 'capybara/apparition/page/frame_manager'
|
4
5
|
require 'capybara/apparition/page/mouse'
|
5
6
|
require 'capybara/apparition/page/keyboard'
|
@@ -194,42 +195,16 @@ module Capybara::Apparition
|
|
194
195
|
end
|
195
196
|
|
196
197
|
def execute(script, *args)
|
197
|
-
|
198
|
-
_execute_script <<~JS, *args
|
199
|
-
function(){
|
200
|
-
#{script}
|
201
|
-
}
|
202
|
-
JS
|
198
|
+
eval_wrapped_script(EXECUTE_JS, script, args)
|
203
199
|
nil
|
204
200
|
end
|
205
201
|
|
206
202
|
def evaluate(script, *args)
|
207
|
-
|
208
|
-
_execute_script <<~JS, *args
|
209
|
-
function(){
|
210
|
-
let apparitionId=0;
|
211
|
-
return (function ider(obj){
|
212
|
-
if (obj && (typeof obj == 'object') && !obj.apparitionId){
|
213
|
-
obj.apparitionId = ++apparitionId;
|
214
|
-
Reflect.ownKeys(obj).forEach(key => ider(obj[key]))
|
215
|
-
}
|
216
|
-
return obj;
|
217
|
-
})((function(){ return #{script} }).apply(this, arguments))
|
218
|
-
}
|
219
|
-
JS
|
203
|
+
eval_wrapped_script(EVALUATE_WITH_ID_JS, script, args)
|
220
204
|
end
|
221
205
|
|
222
206
|
def evaluate_async(script, _wait_time, *args)
|
223
|
-
|
224
|
-
_execute_script <<~JS, *args
|
225
|
-
function(){
|
226
|
-
var args = Array.prototype.slice.call(arguments);
|
227
|
-
return new Promise((resolve, reject)=>{
|
228
|
-
args.push(resolve);
|
229
|
-
(function(){ #{script} }).apply(this, args);
|
230
|
-
});
|
231
|
-
}
|
232
|
-
JS
|
207
|
+
eval_wrapped_script(EVALUATE_ASYNC_JS, script, args)
|
233
208
|
end
|
234
209
|
|
235
210
|
def refresh
|
@@ -403,6 +378,11 @@ module Capybara::Apparition
|
|
403
378
|
|
404
379
|
private
|
405
380
|
|
381
|
+
def eval_wrapped_script(wrapper, script, args)
|
382
|
+
wait_for_loaded
|
383
|
+
_execute_script format(wrapper, script: script), *args
|
384
|
+
end
|
385
|
+
|
406
386
|
def frame_offset(frame)
|
407
387
|
return { x: 0, y: 0 } if frame.id == main_frame.id
|
408
388
|
|
@@ -716,8 +696,7 @@ module Capybara::Apparition
|
|
716
696
|
end
|
717
697
|
end
|
718
698
|
|
719
|
-
|
720
|
-
decode_result(result)
|
699
|
+
DevToolsProtocol::RemoteObject.new(self, response['result']).value
|
721
700
|
end
|
722
701
|
|
723
702
|
def manual_unexpected_modal(type)
|
@@ -767,72 +746,34 @@ module Capybara::Apparition
|
|
767
746
|
continue_request(interception_id, authChallengeResponse: credentials_response)
|
768
747
|
end
|
769
748
|
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
749
|
+
EVALUATE_WITH_ID_JS = <<~JS
|
750
|
+
function(){
|
751
|
+
let apparitionId=0;
|
752
|
+
return (function ider(obj){
|
753
|
+
if (obj && (typeof obj == 'object') && !(obj instanceof HTMLElement) && !obj.apparitionId){
|
754
|
+
obj.apparitionId = ++apparitionId;
|
755
|
+
Reflect.ownKeys(obj).forEach(key => ider(obj[key]))
|
756
|
+
}
|
757
|
+
return obj;
|
758
|
+
})((function(){ return %<script>s }).apply(this, arguments))
|
759
|
+
}
|
760
|
+
JS
|
782
761
|
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
762
|
+
EVALUATE_ASYNC_JS = <<~JS
|
763
|
+
function(){
|
764
|
+
var args = Array.prototype.slice.call(arguments);
|
765
|
+
return new Promise((resolve, reject)=>{
|
766
|
+
args.push(resolve);
|
767
|
+
(function(){ %<script>s }).apply(this, args);
|
768
|
+
});
|
769
|
+
}
|
770
|
+
JS
|
790
771
|
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
return result
|
797
|
-
elsif result['className'] == 'Object'
|
798
|
-
remote_object = command('Runtime.getProperties',
|
799
|
-
objectId: result['objectId'],
|
800
|
-
ownProperties: true)
|
801
|
-
|
802
|
-
# stable_id went away in Chrome 72 - we add our own id in evaluate
|
803
|
-
# stable_id = remote_object['internalProperties']
|
804
|
-
# &.find { |prop| prop['name'] == '[[StableObjectId]]' }
|
805
|
-
# &.dig('value', 'value')
|
806
|
-
|
807
|
-
# We could actually return cyclic objects here but Capybara would need to be updated to support
|
808
|
-
# return object_cache[stable_id] if object_cache.key?(stable_id) # and update the cache to be a hash
|
809
|
-
|
810
|
-
stable_id = remote_object['result']
|
811
|
-
&.find { |prop| prop['name'] == 'apparitionId'}
|
812
|
-
&.dig('value', 'value')
|
813
|
-
return '(cyclic structure)' if object_cache.key?(stable_id)
|
814
|
-
|
815
|
-
object_cache[stable_id] = {}
|
816
|
-
properties = remote_object['result'].reject { |prop| prop['name'] == "apparitionId"}
|
817
|
-
|
818
|
-
return properties.each_with_object(object_cache[stable_id]) do |property, memo|
|
819
|
-
if property['enumerable']
|
820
|
-
memo[property['name']] = decode_result(property['value'], object_cache)
|
821
|
-
else # rubocop:disable Style/EmptyElse
|
822
|
-
# TODO: Do we need to cleanup these resources?
|
823
|
-
# releasePromises.push(helper.releaseObject(@element._client, property.value))
|
824
|
-
end
|
825
|
-
# TODO: Do we need to cleanup these resources?
|
826
|
-
# releasePromises = [helper.releaseObject(@element._client, remote_object)]
|
827
|
-
end
|
828
|
-
elsif result['className'] == 'Window'
|
829
|
-
return { object_id: result['objectId'] }
|
830
|
-
end
|
831
|
-
nil
|
832
|
-
else
|
833
|
-
result['value']
|
834
|
-
end
|
835
|
-
end
|
772
|
+
EXECUTE_JS = <<~JS
|
773
|
+
function(){
|
774
|
+
%<script>s
|
775
|
+
}
|
776
|
+
JS
|
836
777
|
|
837
778
|
CSS_FIND_JS = <<~JS
|
838
779
|
Array.from(document.querySelectorAll("%s"));
|
@@ -12,7 +12,7 @@ module Capybara::Apparition
|
|
12
12
|
move_to x: x, y: y
|
13
13
|
count.times do |num|
|
14
14
|
@keyboard.with_keys(modifiers) do
|
15
|
-
mouse_params = { x: x, y: y, button: button, count: num+1 }
|
15
|
+
mouse_params = { x: x, y: y, button: button, count: num + 1 }
|
16
16
|
down mouse_params
|
17
17
|
up mouse_params
|
18
18
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: apparition
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Walpole
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-01-
|
11
|
+
date: 2019-01-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: backports
|
@@ -227,6 +227,7 @@ files:
|
|
227
227
|
- lib/capybara/apparition/console.rb
|
228
228
|
- lib/capybara/apparition/cookie.rb
|
229
229
|
- lib/capybara/apparition/cookie_jar.rb
|
230
|
+
- lib/capybara/apparition/dev_tools_protocol/remote_object.rb
|
230
231
|
- lib/capybara/apparition/dev_tools_protocol/session.rb
|
231
232
|
- lib/capybara/apparition/dev_tools_protocol/target.rb
|
232
233
|
- lib/capybara/apparition/dev_tools_protocol/target_manager.rb
|