apparition 0.1.0 → 0.2.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 +1 -1
- data/lib/capybara/apparition/browser.rb +102 -56
- data/lib/capybara/apparition/browser/header.rb +2 -2
- data/lib/capybara/apparition/browser/window.rb +29 -29
- data/lib/capybara/apparition/configuration.rb +100 -0
- data/lib/capybara/apparition/dev_tools_protocol/remote_object.rb +19 -7
- data/lib/capybara/apparition/dev_tools_protocol/session.rb +2 -3
- data/lib/capybara/apparition/driver.rb +95 -21
- data/lib/capybara/apparition/driver/chrome_client.rb +3 -3
- data/lib/capybara/apparition/driver/launcher.rb +27 -15
- data/lib/capybara/apparition/driver/response.rb +1 -1
- data/lib/capybara/apparition/errors.rb +3 -3
- data/lib/capybara/apparition/node.rb +44 -3
- data/lib/capybara/apparition/node/drag.rb +65 -0
- data/lib/capybara/apparition/page.rb +55 -37
- data/lib/capybara/apparition/page/frame.rb +3 -0
- data/lib/capybara/apparition/page/frame_manager.rb +1 -1
- data/lib/capybara/apparition/page/keyboard.rb +8 -1
- data/lib/capybara/apparition/utility.rb +1 -1
- data/lib/capybara/apparition/version.rb +1 -1
- metadata +5 -6
- data/lib/capybara/apparition/dev_tools_protocol/target.rb +0 -64
- data/lib/capybara/apparition/dev_tools_protocol/target_manager.rb +0 -48
@@ -39,6 +39,71 @@ module Capybara::Apparition
|
|
39
39
|
@page.mouse.up
|
40
40
|
end
|
41
41
|
|
42
|
+
def drop(*args)
|
43
|
+
if args[0].is_a? String
|
44
|
+
input = evaluate_on ATTACH_FILE
|
45
|
+
input = Capybara::Apparition::Node.new(driver, @page, input['objectId'])
|
46
|
+
input.set(args)
|
47
|
+
evaluate_on DROP_FILE, { objectId: input.id }
|
48
|
+
else
|
49
|
+
items = args.each_with_object([]) do |arg, arr|
|
50
|
+
arg.each_with_object(arr) do |(type, data), arr_|
|
51
|
+
arr_ << { type: type, data: data }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
evaluate_on DROP_STRING, { value: items }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
DROP_STRING = <<~JS
|
59
|
+
function(strings){
|
60
|
+
var dt = new DataTransfer(),
|
61
|
+
opts = { cancelable: true, bubbles: true, dataTransfer: dt };
|
62
|
+
for (var i=0; i < strings.length; i++){
|
63
|
+
if (dt.items) {
|
64
|
+
dt.items.add(strings[i]['data'], strings[i]['type']);
|
65
|
+
} else {
|
66
|
+
dt.setData(strings[i]['type'], strings[i]['data']);
|
67
|
+
}
|
68
|
+
}
|
69
|
+
var dropEvent = new DragEvent('drop', opts);
|
70
|
+
this.dispatchEvent(dropEvent);
|
71
|
+
}
|
72
|
+
JS
|
73
|
+
|
74
|
+
DROP_FILE = <<~JS
|
75
|
+
function(input){
|
76
|
+
var files = input.files,
|
77
|
+
dt = new DataTransfer(),
|
78
|
+
opts = { cancelable: true, bubbles: true, dataTransfer: dt };
|
79
|
+
input.parentElement.removeChild(input);
|
80
|
+
if (dt.items){
|
81
|
+
for (var i=0; i<files.length; i++){
|
82
|
+
dt.items.add(files[i]);
|
83
|
+
}
|
84
|
+
} else {
|
85
|
+
Object.defineProperty(dt, "files", {
|
86
|
+
value: files,
|
87
|
+
writable: false
|
88
|
+
});
|
89
|
+
}
|
90
|
+
var dropEvent = new DragEvent('drop', opts);
|
91
|
+
this.dispatchEvent(dropEvent);
|
92
|
+
}
|
93
|
+
JS
|
94
|
+
|
95
|
+
ATTACH_FILE = <<~JS
|
96
|
+
function(){
|
97
|
+
var input = document.createElement('INPUT');
|
98
|
+
input.type = "file";
|
99
|
+
input.id = "_capybara_drop_file";
|
100
|
+
input.multiple = true;
|
101
|
+
document.body.appendChild(input);
|
102
|
+
return input;
|
103
|
+
}
|
104
|
+
JS
|
105
|
+
|
106
|
+
|
42
107
|
private
|
43
108
|
|
44
109
|
def html5_drag_to(element)
|
@@ -10,44 +10,43 @@ module Capybara::Apparition
|
|
10
10
|
attr_reader :modal_messages
|
11
11
|
attr_reader :mouse, :keyboard
|
12
12
|
attr_reader :viewport_size
|
13
|
+
attr_reader :browser_context_id
|
13
14
|
attr_accessor :perm_headers, :temp_headers, :temp_no_redirect_headers
|
14
15
|
attr_reader :network_traffic
|
16
|
+
attr_reader :target_id
|
15
17
|
|
16
|
-
def self.create(browser, session, id,
|
17
|
-
|
18
|
+
def self.create(browser, session, id, browser_context_id,
|
19
|
+
ignore_https_errors: false, **options)
|
20
|
+
session.async_command 'Page.enable'
|
18
21
|
|
19
22
|
# Provides a lot of info - but huge overhead
|
20
23
|
# session.command 'Page.setLifecycleEventsEnabled', enabled: true
|
21
24
|
|
22
|
-
page = Page.new(browser, session, id,
|
25
|
+
page = Page.new(browser, session, id, browser_context_id, options)
|
23
26
|
|
24
27
|
session.async_commands 'Network.enable', 'Runtime.enable', 'Security.enable', 'DOM.enable'
|
25
|
-
|
26
|
-
# session.command 'Runtime.enable'
|
27
|
-
# session.command 'Security.enable'
|
28
|
-
# session.command 'Security.setOverrideCertificateErrors', override: true if ignore_https_errors
|
29
|
-
session.command 'Security.setIgnoreCertificateErrors', ignore: !!ignore_https_errors
|
30
|
-
# session.command 'DOM.enable'
|
31
|
-
# session.command 'Log.enable'
|
28
|
+
session.async_command 'Security.setIgnoreCertificateErrors', ignore: !!ignore_https_errors
|
32
29
|
if Capybara.save_path
|
33
|
-
session.
|
30
|
+
session.async_command 'Page.setDownloadBehavior', behavior: 'allow', downloadPath: Capybara.save_path
|
34
31
|
end
|
35
32
|
page
|
36
33
|
end
|
37
34
|
|
38
|
-
def initialize(browser, session,
|
39
|
-
|
35
|
+
def initialize(browser, session, target_id, browser_context_id,
|
36
|
+
js_errors: false, url_blacklist: [], url_whitelist: [], extensions: [])
|
37
|
+
@target_id = target_id
|
38
|
+
@browser_context_id = browser_context_id
|
40
39
|
@browser = browser
|
41
40
|
@session = session
|
42
41
|
@keyboard = Keyboard.new(self)
|
43
42
|
@mouse = Mouse.new(self, @keyboard)
|
44
43
|
@modals = []
|
45
44
|
@modal_messages = []
|
46
|
-
@frames = Capybara::Apparition::FrameManager.new(
|
45
|
+
@frames = Capybara::Apparition::FrameManager.new(@target_id)
|
47
46
|
@response_headers = {}
|
48
47
|
@status_code = 0
|
49
|
-
@url_blacklist = []
|
50
|
-
@url_whitelist = []
|
48
|
+
@url_blacklist = url_blacklist || []
|
49
|
+
@url_whitelist = url_whitelist || []
|
51
50
|
@credentials = nil
|
52
51
|
@auth_attempts = []
|
53
52
|
@proxy_credentials = nil
|
@@ -67,6 +66,10 @@ module Capybara::Apparition
|
|
67
66
|
|
68
67
|
register_js_error_handler # if js_errors
|
69
68
|
|
69
|
+
extensions.each do |name|
|
70
|
+
add_extension(name)
|
71
|
+
end
|
72
|
+
|
70
73
|
setup_network_interception if browser.proxy_auth
|
71
74
|
end
|
72
75
|
|
@@ -84,6 +87,12 @@ module Capybara::Apparition
|
|
84
87
|
@perm_headers = {}
|
85
88
|
end
|
86
89
|
|
90
|
+
def add_extension(filename)
|
91
|
+
command('Page.addScriptToEvaluateOnNewDocument', source: File.read(filename))
|
92
|
+
rescue Errno::ENOENT
|
93
|
+
raise ::Capybara::Apparition::BrowserError.new('name' => "Unable to load extension: #{filename}", 'args' => nil)
|
94
|
+
end
|
95
|
+
|
87
96
|
def add_modal(modal_response)
|
88
97
|
@last_modal_message = nil
|
89
98
|
@modals.push(modal_response)
|
@@ -190,9 +199,9 @@ module Capybara::Apparition
|
|
190
199
|
result = _raw_evaluate(query % js_escaped_selector)
|
191
200
|
(result || []).map { |r_o| [self, r_o['objectId']] }
|
192
201
|
rescue ::Capybara::Apparition::BrowserError => e
|
193
|
-
raise unless
|
202
|
+
raise unless /is not a valid (XPath expression|selector)/.match? e.name
|
194
203
|
|
195
|
-
raise Capybara::Apparition::InvalidSelector, [method, selector]
|
204
|
+
raise Capybara::Apparition::InvalidSelector, 'args' => [method, selector]
|
196
205
|
end
|
197
206
|
|
198
207
|
def execute(script, *args)
|
@@ -225,12 +234,14 @@ module Capybara::Apparition
|
|
225
234
|
go_history(+1)
|
226
235
|
end
|
227
236
|
|
228
|
-
|
237
|
+
def response_headers
|
238
|
+
@response_headers[current_frame.id] || {}
|
239
|
+
end
|
229
240
|
|
230
241
|
attr_reader :status_code
|
231
242
|
|
232
243
|
def wait_for_loaded(allow_obsolete: false)
|
233
|
-
timer = Capybara::Helpers.timer(expire_in:
|
244
|
+
timer = Capybara::Helpers.timer(expire_in: 30)
|
234
245
|
cf = current_frame
|
235
246
|
until cf.usable? || (allow_obsolete && cf.obsolete?) || @js_error
|
236
247
|
if timer.expired?
|
@@ -276,7 +287,7 @@ module Capybara::Apparition
|
|
276
287
|
|
277
288
|
def element_from_point(x:, y:)
|
278
289
|
r_o = _raw_evaluate("document.elementFromPoint(#{x}, #{y})", context_id: main_frame.context_id)
|
279
|
-
while r_o && (r_o['description']
|
290
|
+
while r_o && (/^iframe/.match? r_o['description'])
|
280
291
|
frame_node = command('DOM.describeNode', objectId: r_o['objectId'])
|
281
292
|
frame = @frames.get(frame_node.dig('node', 'frameId'))
|
282
293
|
fo = frame_offset(frame)
|
@@ -291,7 +302,7 @@ module Capybara::Apparition
|
|
291
302
|
end
|
292
303
|
|
293
304
|
def set_viewport(width:, height:, screen: nil)
|
294
|
-
wait_for_loaded
|
305
|
+
# wait_for_loaded
|
295
306
|
@viewport_size = { width: width, height: height }
|
296
307
|
result = @browser.command('Browser.getWindowForTarget', targetId: @target_id)
|
297
308
|
begin
|
@@ -351,7 +362,7 @@ module Capybara::Apparition
|
|
351
362
|
|
352
363
|
def update_headers(async: false)
|
353
364
|
method = async ? :async_command : :command
|
354
|
-
if (ua = extra_headers.find { |k, _v|
|
365
|
+
if (ua = extra_headers.find { |k, _v| /^User-Agent$/i.match? k })
|
355
366
|
send(method, 'Network.setUserAgentOverride', userAgent: ua[1])
|
356
367
|
end
|
357
368
|
send(method, 'Network.setExtraHTTPHeaders', headers: extra_headers)
|
@@ -377,6 +388,14 @@ module Capybara::Apparition
|
|
377
388
|
|
378
389
|
attr_reader :url_blacklist, :url_whitelist
|
379
390
|
|
391
|
+
def current_frame
|
392
|
+
@frames.current
|
393
|
+
end
|
394
|
+
|
395
|
+
def main_frame
|
396
|
+
@frames.main
|
397
|
+
end
|
398
|
+
|
380
399
|
private
|
381
400
|
|
382
401
|
def eval_wrapped_script(wrapper, script, args)
|
@@ -420,8 +439,7 @@ module Capybara::Apparition
|
|
420
439
|
|
421
440
|
@session.on 'Page.windowOpen' do |params|
|
422
441
|
puts "**** windowOpen was called with: #{params}" if ENV['DEBUG']
|
423
|
-
|
424
|
-
sleep 0.4 # wait a bit so the window has time to start loading
|
442
|
+
@browser.refresh_pages(opener: self)
|
425
443
|
end
|
426
444
|
|
427
445
|
@session.on 'Page.frameAttached' do |params|
|
@@ -441,13 +459,16 @@ module Capybara::Apparition
|
|
441
459
|
puts "**** creating frome for #{frame_params['id']}" if ENV['DEBUG']
|
442
460
|
@frames.add(frame_params['id'], frame_params)
|
443
461
|
end
|
462
|
+
@frames.get(frame_params['id'])&.loading(frame_params['loaderId'] || -1)
|
444
463
|
end
|
445
464
|
|
446
465
|
@session.on 'Page.frameStartedLoading' do |params|
|
466
|
+
puts "Setting loading for #{params['frameId']}" if ENV['DEBUG']
|
447
467
|
@frames.get(params['frameId'])&.loading(-1)
|
448
468
|
end
|
449
469
|
|
450
470
|
@session.on 'Page.frameStoppedLoading' do |params|
|
471
|
+
puts "Setting loaded for #{params['frameId']}" if ENV['DEBUG']
|
451
472
|
@frames.get(params['frameId'])&.loaded!
|
452
473
|
end
|
453
474
|
|
@@ -516,7 +537,7 @@ module Capybara::Apparition
|
|
516
537
|
|
517
538
|
@session.on 'Network.responseReceived' do |params|
|
518
539
|
if params['type'] == 'Document'
|
519
|
-
@response_headers = params['response']['headers']
|
540
|
+
@response_headers[params['frameId']] = params['response']['headers']
|
520
541
|
@status_code = params['response']['status']
|
521
542
|
end
|
522
543
|
end
|
@@ -596,7 +617,7 @@ module Capybara::Apparition
|
|
596
617
|
unless @temp_no_redirect_headers.empty? || !navigation
|
597
618
|
headers.delete_if { |name, value| @temp_no_redirect_headers[name] == value }
|
598
619
|
end
|
599
|
-
if (accept = perm_headers.keys.find { |k|
|
620
|
+
if (accept = perm_headers.keys.find { |k| /accept/i.match? k })
|
600
621
|
headers[accept] = perm_headers[accept]
|
601
622
|
end
|
602
623
|
|
@@ -621,14 +642,6 @@ module Capybara::Apparition
|
|
621
642
|
async_command 'Network.continueInterceptedRequest', errorReason: reason, interceptionId: id
|
622
643
|
end
|
623
644
|
|
624
|
-
def current_frame
|
625
|
-
@frames.current
|
626
|
-
end
|
627
|
-
|
628
|
-
def main_frame
|
629
|
-
@frames.main
|
630
|
-
end
|
631
|
-
|
632
645
|
def go_history(delta)
|
633
646
|
history = command('Page.getNavigationHistory')
|
634
647
|
entry = history['entries'][history['currentIndex'] + delta]
|
@@ -667,7 +680,8 @@ module Capybara::Apparition
|
|
667
680
|
executionContextId: context_id,
|
668
681
|
arguments: args,
|
669
682
|
returnByValue: false,
|
670
|
-
awaitPromise: true
|
683
|
+
awaitPromise: true,
|
684
|
+
userGesture: true)
|
671
685
|
process_response(response)
|
672
686
|
end
|
673
687
|
|
@@ -754,7 +768,11 @@ module Capybara::Apparition
|
|
754
768
|
function(){
|
755
769
|
let apparitionId=0;
|
756
770
|
return (function ider(obj){
|
757
|
-
if (obj &&
|
771
|
+
if (obj &&
|
772
|
+
(typeof obj == 'object') &&
|
773
|
+
!(obj instanceof HTMLElement) &&
|
774
|
+
!(obj instanceof CSSStyleDeclaration) &&
|
775
|
+
!obj.apparitionId){
|
758
776
|
obj.apparitionId = ++apparitionId;
|
759
777
|
Reflect.ownKeys(obj).forEach(key => ider(obj[key]))
|
760
778
|
}
|
@@ -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
|
|
@@ -102,8 +102,15 @@ module Capybara::Apparition
|
|
102
102
|
location: 0
|
103
103
|
)
|
104
104
|
|
105
|
+
if key.to_sym == :return
|
106
|
+
warn '[DEPRECATION]: Key symbol :return is deprecated in favor of :enter'
|
107
|
+
key = :enter
|
108
|
+
end
|
109
|
+
|
105
110
|
definition = KEY_DEFINITIONS[key.to_sym]
|
106
|
-
raise KeyError, "Unknown key: #{key}" if definition.nil?
|
111
|
+
raise KeyError, "Unknown key: #{key}" if definition.nil? && !key.is_a?(String)
|
112
|
+
|
113
|
+
definition ||= { text: key }
|
107
114
|
|
108
115
|
definition = OpenStruct.new definition
|
109
116
|
|
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.
|
4
|
+
version: 0.2.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: 2019-
|
11
|
+
date: 2019-05-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: backports
|
@@ -231,13 +231,12 @@ files:
|
|
231
231
|
- lib/capybara/apparition/browser/modal.rb
|
232
232
|
- lib/capybara/apparition/browser/render.rb
|
233
233
|
- lib/capybara/apparition/browser/window.rb
|
234
|
+
- lib/capybara/apparition/configuration.rb
|
234
235
|
- lib/capybara/apparition/console.rb
|
235
236
|
- lib/capybara/apparition/cookie.rb
|
236
237
|
- lib/capybara/apparition/cookie_jar.rb
|
237
238
|
- lib/capybara/apparition/dev_tools_protocol/remote_object.rb
|
238
239
|
- 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
240
|
- lib/capybara/apparition/driver.rb
|
242
241
|
- lib/capybara/apparition/driver/chrome_client.rb
|
243
242
|
- lib/capybara/apparition/driver/launcher.rb
|
@@ -270,14 +269,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
270
269
|
requirements:
|
271
270
|
- - ">="
|
272
271
|
- !ruby/object:Gem::Version
|
273
|
-
version: 2.
|
272
|
+
version: 2.4.0
|
274
273
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
275
274
|
requirements:
|
276
275
|
- - ">="
|
277
276
|
- !ruby/object:Gem::Version
|
278
277
|
version: '0'
|
279
278
|
requirements: []
|
280
|
-
rubygems_version: 3.0.
|
279
|
+
rubygems_version: 3.0.3
|
281
280
|
signing_key:
|
282
281
|
specification_version: 4
|
283
282
|
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
|