apparition 0.0.5 → 0.0.6
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 +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
|