apparition 0.1.0 → 0.2.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.
@@ -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