apparition 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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, ignore_https_errors: false, screenshot_task_queue: nil, js_errors: false)
17
- session.command 'Page.enable'
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, ignore_https_errors, screenshot_task_queue, js_errors)
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
- # session.command 'Network.enable'
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.command 'Page.setDownloadBehavior', behavior: 'allow', downloadPath: Capybara.save_path
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, id, _ignore_https_errors, _screenshot_task_queue, js_errors)
39
- @target_id = id
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(id)
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 e.name =~ /is not a valid (XPath expression|selector)/
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
- attr_reader :response_headers
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: 10)
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'] =~ /^iframe/)
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| k =~ /^User-Agent$/i })
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
- # TODO: find a better way to handle this
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| k =~ /accept/i })
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 && (typeof obj == 'object') && !(obj instanceof HTMLElement) && !obj.apparitionId){
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
 
@@ -7,7 +7,7 @@ module Capybara::Apparition
7
7
  def initialize(id)
8
8
  @frames = {}
9
9
  @frames_mutex = Mutex.new
10
- add(id)
10
+ add(id).loading(-1)
11
11
  @main_id = @current_id = id
12
12
  end
13
13
 
@@ -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
 
@@ -4,7 +4,7 @@ module Capybara
4
4
  module Apparition
5
5
  class << self
6
6
  def windows?
7
- RbConfig::CONFIG['host_os'] =~ /mingw|mswin|cygwin/
7
+ RbConfig::CONFIG['host_os'].match?(/mingw|mswin|cygwin/)
8
8
  end
9
9
 
10
10
  def mri?
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Capybara
4
4
  module Apparition
5
- VERSION = '0.1.0'
5
+ VERSION = '0.2.0'
6
6
  end
7
7
  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.1.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-02-05 00:00:00.000000000 Z
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.3.0
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.1
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