apparition 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 138fab423026ba9b7e93b02a3dbbd4a4e438dfbf8bcf48500126dc9251dabd0d
4
- data.tar.gz: af667994c4bf65674368b6e1e0a7e0a7d29ba4adf2a2d161eeb8dd1b16d98a45
3
+ metadata.gz: f5675a80f435049166cf933b540e00b5569b0f516099597cdc071cc4c7767ff0
4
+ data.tar.gz: 8a63bf45f5376a4eb0a65954bd3863062f1aea67b030cb3ee2b96c7cfe1d108d
5
5
  SHA512:
6
- metadata.gz: 0e1f58fcdf5b3942bb219462ffd72e9f961747c0334c9fad0103e79cd0ff0dc3318a6c80988f5be0f045a1fc40d7c2695630b458ab79328accc0a3d17996a400
7
- data.tar.gz: 4470dcb096886417f7d4b7ddcd0842f5be59d6a574173b3cda69c0878ad27047b9927696b4e7939579fadc90bbddcee6137fa6b91871f1ad7b2c746e3ba73c20
6
+ metadata.gz: 7d0b64f71ec2e0a026bc5f0cea02588e550a5e27768b5368041bfbbd206e758654900cf22c35620d88f0618a76a12b874120513ae3d606e8ee481a5021bf3297
7
+ data.tar.gz: b279c8871baedd04acf1e717fddbf2898ae9796e0c1152fd17c0ccc160deaa10cb0cc86243037448883e10da3a90844351c60e097e0b6b5aebb8e87b0e796f65
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'backports/2.4.0/enumerable/sum'
4
- require 'backports/2.4.0/string/match'
5
3
  require 'capybara'
6
4
 
7
5
  module Capybara
@@ -9,6 +9,7 @@ require 'capybara/apparition/browser/window'
9
9
  require 'capybara/apparition/browser/render'
10
10
  require 'capybara/apparition/browser/cookie'
11
11
  require 'capybara/apparition/browser/modal'
12
+ require 'capybara/apparition/browser/page_manager'
12
13
  require 'capybara/apparition/browser/frame'
13
14
  require 'capybara/apparition/browser/auth'
14
15
  require 'json'
@@ -30,7 +31,7 @@ module Capybara::Apparition
30
31
  def initialize(client, logger = nil)
31
32
  @client = client
32
33
  @current_page_handle = nil
33
- @pages = {}
34
+ @pages = PageManager.new(self)
34
35
  @context_id = nil
35
36
  @js_errors = true
36
37
  @ignore_https_errors = false
@@ -46,7 +47,7 @@ module Capybara::Apparition
46
47
  end
47
48
 
48
49
  def restart
49
- puts 'handle client restart'
50
+ # puts 'handle client restart'
50
51
  # client.restart
51
52
 
52
53
  self.debug = @debug if defined?(@debug)
@@ -79,35 +80,26 @@ module Capybara::Apparition
79
80
 
80
81
  def reset
81
82
  new_context_id = command('Target.createBrowserContext')['browserContextId']
82
- # current_pages = @pages.keys
83
-
84
83
  new_target_response = client.send_cmd('Target.createTarget', url: 'about:blank', browserContextId: new_context_id)
85
- @pages.each do |id, page|
86
- begin
87
- client.send_cmd('Target.disposeBrowserContext', browserContextId: page.browser_context_id).discard_result
88
- rescue WrongWorld
89
- puts 'Unknown browserContextId'
90
- end
91
- @pages.delete(id)
92
- end
84
+
85
+ @pages.reset
93
86
 
94
87
  new_target_id = new_target_response['targetId']
95
88
 
96
89
  session_id = command('Target.attachToTarget', targetId: new_target_id)['sessionId']
97
90
  session = Capybara::Apparition::DevToolsProtocol::Session.new(self, client, session_id)
98
91
 
99
- @pages[new_target_id] = Page.create(self, session, new_target_id, new_context_id,
100
- ignore_https_errors: ignore_https_errors,
101
- js_errors: js_errors, extensions: @extensions,
102
- url_blacklist: @url_blacklist,
103
- url_whitelist: @url_whitelist) # .inherit(@info.delete('inherit'))
104
- @pages[new_target_id].send(:main_frame).loaded!
92
+ @pages.create(new_target_id, session, new_context_id,
93
+ ignore_https_errors: ignore_https_errors,
94
+ js_errors: js_errors, extensions: @extensions,
95
+ url_blacklist: @url_blacklist,
96
+ url_whitelist: @url_whitelist) .send(:main_frame).loaded!
105
97
 
106
98
  timer = Capybara::Helpers.timer(expire_in: 10)
107
99
  until @pages[new_target_id].usable?
108
100
  if timer.expired?
109
101
  puts 'Timedout waiting for reset'
110
- raise TimeoutError.new('reset')
102
+ raise TimeoutError, 'reset'
111
103
  end
112
104
  sleep 0.01
113
105
  end
@@ -117,42 +109,11 @@ module Capybara::Apparition
117
109
  end
118
110
 
119
111
  def refresh_pages(opener:)
120
- new_pages = command('Target.getTargets')['targetInfos'].select do |ti|
121
- (ti['openerId'] == opener.target_id) && (ti['type'] == 'page') && (ti['attached'] == false)
122
- end
123
- sessions = new_pages.map do |page|
124
- target_id = page['targetId']
125
- session_result = client.send_cmd('Target.attachToTarget', targetId: target_id)
126
- [target_id, session_result]
127
- end
128
-
129
- sessions = sessions.map do |(target_id, session_result)|
130
- session = Capybara::Apparition::DevToolsProtocol::Session.new(self, client, session_result.result['sessionId'])
131
- [target_id, session]
132
- end
133
-
134
- sessions.each do |(_target_id, session)|
135
- session.async_commands 'Page.enable', 'Network.enable', 'Runtime.enable', 'Security.enable', 'DOM.enable'
136
- end
137
-
138
- # sessions.each do |(target_id, session_result)|
139
- # session = Capybara::Apparition::DevToolsProtocol::Session.new(self, client, session_result.result['sessionId'])
140
- sessions.each do |(target_id, session)|
141
- page_options = { ignore_https_errors: ignore_https_errors, js_errors: js_errors,
142
- url_blacklist: @url_blacklist, url_whitelist: @url_whitelist }
143
- new_page = Page.create(self, session, target_id, opener.browser_context_id, page_options).inherit(opener)
144
- @pages[target_id] = new_page
145
- end
146
-
147
- # new_pages.each do |page|
148
- # target_id = page['targetId']
149
- # session_id = command('Target.attachToTarget', targetId: target_id)['sessionId']
150
- # session = Capybara::Apparition::DevToolsProtocol::Session.new(self, client, session_id)
151
- # page_options = { ignore_https_errors: ignore_https_errors, js_errors: js_errors,
152
- # url_blacklist: @url_blacklist, url_whitelist: @url_whitelist }
153
- # new_page = Page.create(self, session, page['targetId'], opener.browser_context_id, page_options).inherit(opener)
154
- # @pages[target_id] = new_page
155
- # end
112
+ @pages.refresh(opener: opener,
113
+ ignore_https_errors: ignore_https_errors,
114
+ js_errors: js_errors,
115
+ url_blacklist: @url_blacklist,
116
+ url_whitelist: @url_whitelist)
156
117
  end
157
118
 
158
119
  def resize(width, height, screen: nil)
@@ -178,17 +139,11 @@ module Capybara::Apparition
178
139
  end
179
140
 
180
141
  def url_whitelist=(whitelist)
181
- @url_whitelist = whitelist
182
- @pages.each do |_id, page|
183
- page.url_whitelist = whitelist
184
- end
142
+ @url_whitelist = @pages.whitelist = whitelist
185
143
  end
186
144
 
187
145
  def url_blacklist=(blacklist)
188
- @url_blacklist = blacklist
189
- @pages.each do |_id, page|
190
- page.url_blacklist = blacklist
191
- end
146
+ @url_blacklist = @pages.blacklist = blacklist
192
147
  end
193
148
 
194
149
  attr_writer :debug
@@ -234,72 +189,6 @@ module Capybara::Apparition
234
189
  @logger&.puts message if ENV['DEBUG']
235
190
  end
236
191
 
237
- # KEY_ALIASES = {
238
- # command: :Meta,
239
- # equals: :Equal,
240
- # control: :Control,
241
- # ctrl: :Control,
242
- # multiply: 'numpad*',
243
- # add: 'numpad+',
244
- # divide: 'numpad/',
245
- # subtract: 'numpad-',
246
- # decimal: 'numpad.',
247
- # left: 'ArrowLeft',
248
- # right: 'ArrowRight',
249
- # down: 'ArrowDown',
250
- # up: 'ArrowUp'
251
- # }.freeze
252
- #
253
- # def normalize_keys(keys)
254
- # keys.map do |key_desc|
255
- # case key_desc
256
- # when Array
257
- # # [:Shift, "s"] => { modifier: "shift", keys: "S" }
258
- # # [:Shift, "string"] => { modifier: "shift", keys: "STRING" }
259
- # # [:Ctrl, :Left] => { modifier: "ctrl", key: 'Left' }
260
- # # [:Ctrl, :Shift, :Left] => { modifier: "ctrl,shift", key: 'Left' }
261
- # # [:Ctrl, :Left, :Left] => { modifier: "ctrl", key: [:Left, :Left] }
262
- # keys_chunks = key_desc.chunk do |k|
263
- # k.is_a?(Symbol) && %w[shift ctrl control alt meta command].include?(k.to_s.downcase)
264
- # end
265
- # modifiers = modifiers_from_chunks(keys_chunks)
266
- # letters = normalize_keys(_keys.next[1].map { |k| k.is_a?(String) ? k.upcase : k })
267
- # { modifier: modifiers, keys: letters }
268
- # when Symbol
269
- # symbol_to_desc(key_desc)
270
- # when String
271
- # key_desc # Plain string, nothing to do
272
- # end
273
- # end
274
- # end
275
- #
276
- # def modifiers_from_chunks(chunks)
277
- # if chunks.peek[0]
278
- # chunks.next[1].map do |k|
279
- # k = k.to_s.downcase
280
- # k = 'control' if k == 'ctrl'
281
- # k = 'meta' if k == 'command'
282
- # k
283
- # end.join(',')
284
- # else
285
- # ''
286
- # end
287
- # end
288
- #
289
- # def symbol_to_desc(symbol)
290
- # if symbol == :space
291
- # res = ' '
292
- # else
293
- # key = KEY_ALIASES.fetch(symbol.downcase, symbol)
294
- # if (match = key.to_s.match(/numpad(.)/))
295
- # res = { keys: match[1], modifier: 'keypad' }
296
- # elsif !/^[A-Z]/.match?(key)
297
- # key = key.to_s.split('_').map(&:capitalize).join
298
- # end
299
- # end
300
- # res || { key: key }
301
- # end
302
-
303
192
  def initialize_handlers
304
193
  # @client.on 'Target.targetCreated' do |info|
305
194
  # byebug
@@ -6,18 +6,9 @@ module Capybara::Apparition
6
6
  class Browser
7
7
  module Cookie
8
8
  def cookies
9
- CookieJar.new(
10
- # current_page.command('Network.getCookies')['cookies'].map { |c| Cookie.new(c) }
11
- self
12
- )
13
- end
14
-
15
- def all_cookies
16
- CookieJar.new(
17
- # current_page.command('Network.getAllCookies')['cookies'].map { |c| Cookie.new(c) }
18
- self
19
- )
9
+ CookieJar.new(self)
20
10
  end
11
+ alias :all_cookies :cookies
21
12
 
22
13
  def get_raw_cookies
23
14
  current_page.command('Network.getAllCookies')['cookies'].map do |c|
@@ -26,10 +17,7 @@ module Capybara::Apparition
26
17
  end
27
18
 
28
19
  def set_cookie(cookie)
29
- if cookie[:expires]
30
- # cookie[:expires] = cookie[:expires].to_i * 1000
31
- cookie[:expires] = cookie[:expires].to_i
32
- end
20
+ cookie[:expires] = cookie[:expires].to_i if cookie[:expires]
33
21
 
34
22
  current_page.command('Network.setCookie', cookie)
35
23
  end
@@ -8,7 +8,7 @@ module Capybara::Apparition
8
8
  end
9
9
 
10
10
  def headers=(headers)
11
- @pages.each do |_id, page|
11
+ @pages.each do |page|
12
12
  page.perm_headers = headers.dup
13
13
  page.temp_headers = {}
14
14
  page.temp_no_redirect_headers = {}
@@ -23,7 +23,7 @@ module Capybara::Apparition
23
23
 
24
24
  def add_header(header, permanent: true, **_options)
25
25
  if permanent == true
26
- @pages.each do |_id, page|
26
+ @pages.each do |page|
27
27
  page.perm_headers.merge! header
28
28
  page.update_headers
29
29
  end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara::Apparition
4
+ class Browser
5
+ class PageManager
6
+ def initialize(browser)
7
+ @browser = browser
8
+ @pages = {}
9
+ end
10
+
11
+ def ids
12
+ @pages.keys
13
+ end
14
+
15
+ def [](id)
16
+ @pages[id]
17
+ end
18
+
19
+ def each(&block)
20
+ @pages.each_value(&block)
21
+ end
22
+
23
+ def reset
24
+ @pages.each do |id, page|
25
+ begin
26
+ @browser.client.send_cmd(
27
+ 'Target.disposeBrowserContext',
28
+ browserContextId: page.browser_context_id
29
+ ).discard_result
30
+ rescue WrongWorld
31
+ puts 'Unknown browserContextId'
32
+ end
33
+ @pages.delete(id)
34
+ end
35
+ end
36
+
37
+ def create(id, session, ctx_id, **options)
38
+ @pages[id] = Page.create(@browser, session, id, ctx_id, **options)
39
+ end
40
+
41
+ def delete(id)
42
+ @pages.delete(id)
43
+ end
44
+
45
+ def refresh(opener:, **page_options)
46
+ new_pages = @browser.command('Target.getTargets')['targetInfos'].select do |ti|
47
+ (ti['openerId'] == opener.target_id) && (ti['type'] == 'page') && (ti['attached'] == false)
48
+ end
49
+
50
+ sessions = new_pages.map do |page|
51
+ target_id = page['targetId']
52
+ session_result = @browser.client.send_cmd('Target.attachToTarget', targetId: target_id)
53
+ [target_id, session_result]
54
+ end
55
+
56
+ sessions = sessions.map do |(target_id, session_result)|
57
+ session = Capybara::Apparition::DevToolsProtocol::Session.new(
58
+ @browser,
59
+ @browser.client,
60
+ session_result.result['sessionId']
61
+ )
62
+ [target_id, session]
63
+ end
64
+
65
+ sessions.each do |(_id, session)|
66
+ session.async_commands 'Page.enable', 'Network.enable', 'Runtime.enable', 'Security.enable', 'DOM.enable'
67
+ end
68
+
69
+ sessions.each do |(target_id, session)|
70
+ new_page = Page.create(@browser, session, target_id, opener.browser_context_id, page_options).inherit(opener)
71
+ @pages[target_id] = new_page
72
+ end
73
+ end
74
+
75
+ def whitelist=(list)
76
+ @pages.each_value { |page| page.url_whitelist = list }
77
+ end
78
+
79
+ def blacklist=(list)
80
+ @pages.each_value { |page| page.url_blacklist = list }
81
+ end
82
+ end
83
+ end
84
+ end
@@ -8,7 +8,7 @@ module Capybara::Apparition
8
8
  end
9
9
 
10
10
  def window_handles
11
- @pages.keys
11
+ @pages.ids
12
12
  end
13
13
 
14
14
  def switch_to_window(handle)
@@ -29,12 +29,12 @@ module Capybara::Apparition
29
29
  target_id = command('Target.createTarget', url: 'about:blank', browserContextId: context_id)['targetId']
30
30
  session_id = command('Target.attachToTarget', targetId: target_id)['sessionId']
31
31
  session = Capybara::Apparition::DevToolsProtocol::Session.new(self, client, session_id)
32
- @pages[target_id] = Page.create(self, session, target_id, context_id,
33
- ignore_https_errors: ignore_https_errors,
34
- js_errors: js_errors,
35
- url_whitelist: @url_whitelist,
36
- extensions: @extensions,
37
- url_blacklist: @url_blacklist).inherit(current_page(allow_nil: true))
32
+ @pages.create(target_id, session, context_id,
33
+ ignore_https_errors: ignore_https_errors,
34
+ js_errors: js_errors,
35
+ url_whitelist: @url_whitelist,
36
+ extensions: @extensions,
37
+ url_blacklist: @url_blacklist).inherit(current_page(allow_nil: true))
38
38
  @pages[target_id].send(:main_frame).loaded!
39
39
  target_id
40
40
  end
@@ -100,7 +100,9 @@ module Capybara::Apparition
100
100
  end
101
101
 
102
102
  def find(method, selector)
103
- browser.find(method, selector).map { |page_id, id| Capybara::Apparition::Node.new(self, page_id, id) }
103
+ browser.find(method, selector).map do |page_id, id, attrs|
104
+ Capybara::Apparition::Node.new(self, page_id, id, attrs)
105
+ end
104
106
  end
105
107
 
106
108
  def find_xpath(selector)
@@ -521,7 +523,8 @@ module Capybara::Apparition
521
523
  object_cache[arg]
522
524
  when Hash
523
525
  if (arg['subtype'] == 'node') && arg['objectId']
524
- Capybara::Apparition::Node.new(self, browser.current_page, arg['objectId'])
526
+ tag_name = arg['description'].split(/[\.#]/, 2)[0]
527
+ Capybara::Apparition::Node.new(self, browser.current_page, arg['objectId'], tag_name: tag_name)
525
528
  else
526
529
  object_cache[arg] = {}
527
530
  arg.each { |k, v| object_cache[arg][k] = unwrap_script_result(v, object_cache) }
@@ -11,8 +11,8 @@ module Capybara::Apparition
11
11
 
12
12
  attr_reader :page_id
13
13
 
14
- def initialize(driver, page, remote_object)
15
- super(driver, self)
14
+ def initialize(driver, page, remote_object, initial_cache)
15
+ super(driver, self, initial_cache)
16
16
  @page = page
17
17
  @remote_object = remote_object
18
18
  end
@@ -30,7 +30,8 @@ module Capybara::Apparition
30
30
  def find(method, selector)
31
31
  js = method == :css ? FIND_CSS_JS : FIND_XPATH_JS
32
32
  evaluate_on(js, value: selector).map do |r_o|
33
- Capybara::Apparition::Node.new(driver, @page, r_o['objectId'])
33
+ tag_name = r_o['description'].split(/[\.#]/, 2)[0]
34
+ Capybara::Apparition::Node.new(driver, @page, r_o['objectId'], tag_name: tag_name)
34
35
  end
35
36
  rescue ::Capybara::Apparition::BrowserError => e
36
37
  raise unless /is not a valid (XPath expression|selector)/.match? e.name
@@ -165,8 +166,19 @@ module Capybara::Apparition
165
166
  evaluate_on VISIBLE_JS
166
167
  end
167
168
 
168
- def obscured?(x: nil, y: nil)
169
- evaluate_on(OBSCURED_JS) == true
169
+ def obscured?(**)
170
+ pos = visible_center(allow_scroll: false)
171
+ return true if pos.nil?
172
+
173
+ hit_node = @page.element_from_point(pos)
174
+ return true if hit_node.nil?
175
+
176
+ begin
177
+ return evaluate_on('el => !this.contains(el)', objectId: hit_node['objectId'])
178
+ rescue WrongWorld # rubocop:disable Lint/HandleExceptions
179
+ end
180
+
181
+ true
170
182
  end
171
183
 
172
184
  def checked?
@@ -256,9 +268,9 @@ module Capybara::Apparition
256
268
  false
257
269
  end
258
270
 
259
- def send_keys(*keys, delay: 0, **_opts)
271
+ def send_keys(*keys, delay: 0, **opts)
260
272
  click unless evaluate_on CURRENT_NODE_SELECTED_JS
261
- @page.keyboard.type(keys, delay: delay)
273
+ _send_keys(*keys, delay: delay, **opts)
262
274
  end
263
275
  alias_method :send_key, :send_keys
264
276
 
@@ -311,8 +323,8 @@ module Capybara::Apparition
311
323
  end
312
324
  end
313
325
 
314
- def visible_center
315
- rect = in_view_bounding_rect
326
+ def visible_center(allow_scroll: true)
327
+ rect = in_view_bounding_rect(allow_scroll: allow_scroll)
316
328
  return nil if rect.nil?
317
329
 
318
330
  frame_offset = @page.current_frame_offset
@@ -405,8 +417,38 @@ module Capybara::Apparition
405
417
 
406
418
  private
407
419
 
408
- def in_view_bounding_rect
409
- evaluate_on('() => this.scrollIntoViewIfNeeded()')
420
+ def focus
421
+ @page.command('DOM.focus', objectId: id)
422
+ end
423
+
424
+ def keys_to_send(value, clear)
425
+ case clear
426
+ when :backspace
427
+ # Clear field by sending the correct number of backspace keys.
428
+ [:end] + ([:backspace] * self.value.to_s.length) + [value]
429
+ when :none
430
+ [value]
431
+ when Array
432
+ clear << value
433
+ else
434
+ # Clear field by JavaScript assignment of the value property.
435
+ # Script can change a readonly element which user input cannot, so
436
+ # don't execute if readonly.
437
+ driver.execute_script <<~JS, self
438
+ if (!arguments[0].readOnly) {
439
+ arguments[0].value = ''
440
+ }
441
+ JS
442
+ [value]
443
+ end
444
+ end
445
+
446
+ def _send_keys(*keys, delay: 0, **_opts)
447
+ @page.keyboard.type(keys, delay: delay)
448
+ end
449
+
450
+ def in_view_bounding_rect(allow_scroll: true)
451
+ evaluate_on('() => this.scrollIntoViewIfNeeded()') if allow_scroll
410
452
  result = evaluate_on GET_BOUNDING_CLIENT_RECT_JS
411
453
  result = result['model'] if result && result['model']
412
454
  result
@@ -437,22 +479,9 @@ module Capybara::Apparition
437
479
  value = value.to_s
438
480
  if value.empty? && clear.nil?
439
481
  evaluate_on CLEAR_ELEMENT_JS
440
- elsif clear == :backspace
441
- # Clear field by sending the correct number of backspace keys.
442
- backspaces = [:backspace] * self.value.to_s.length
443
- send_keys(*([:end] + backspaces + [value]), delay: delay)
444
- elsif clear.is_a? Array
445
- send_keys(*clear, value, delay: delay)
446
482
  else
447
- # Clear field by JavaScript assignment of the value property.
448
- # Script can change a readonly element which user input cannot, so
449
- # don't execute if readonly.
450
- driver.execute_script <<~JS, self unless clear == :none
451
- if (!arguments[0].readOnly) {
452
- arguments[0].value = ''
453
- }
454
- JS
455
- send_keys(value, delay: delay)
483
+ focus
484
+ _send_keys(*keys_to_send(value, clear), delay: delay)
456
485
  end
457
486
  end
458
487
 
@@ -511,7 +540,8 @@ module Capybara::Apparition
511
540
  r_o = @page.element_from_point(x: x, y: y)
512
541
  return nil unless r_o && r_o['objectId']
513
542
 
514
- hit_node = Capybara::Apparition::Node.new(driver, @page, r_o['objectId'])
543
+ tag_name = r_o['description'].split(/[\.#]/, 2)[0]
544
+ hit_node = Capybara::Apparition::Node.new(driver, @page, r_o['objectId'], tag_name: tag_name)
515
545
  result = begin
516
546
  evaluate_on(<<~JS, objectId: hit_node.id)
517
547
  (hit_node) => {
@@ -681,11 +711,14 @@ module Capybara::Apparition
681
711
  sel = sel.parentNode;
682
712
  }
683
713
  let event_options = { bubbles: true, cancelable: true };
714
+ sel.dispatchEvent(new MouseEvent('mousedown', event_options));
684
715
  sel.dispatchEvent(new FocusEvent('focus', event_options));
685
-
686
- this.selected = true
687
-
688
- sel.dispatchEvent(new Event('change', event_options));
716
+ if (this.selected == false){
717
+ this.selected = true;
718
+ sel.dispatchEvent(new Event('change', event_options));
719
+ }
720
+ sel.dispatchEvent(new MouseEvent('mouseup', event_options));
721
+ sel.dispatchEvent(new MouseEvent('click', event_options));
689
722
  sel.dispatchEvent(new FocusEvent('blur', event_options));
690
723
  }
691
724
  JS
@@ -733,24 +766,6 @@ module Capybara::Apparition
733
766
  }
734
767
  JS
735
768
 
736
- OBSCURED_JS = <<~JS
737
- function(x, y) {
738
- var box = this.getBoundingClientRect();
739
- if (x == null) x = box.width/2;
740
- if (y == null) y = box.height/2 ;
741
-
742
- var px = box.left + x,
743
- py = box.top + y,
744
- e = document.elementFromPoint(px, py);
745
-
746
- if (!this.contains(e))
747
- return true;
748
-
749
- return { x: px, y: py };
750
- }
751
- JS
752
-
753
-
754
769
  DELETE_TEXT_JS = <<~JS
755
770
  function(){
756
771
  range = document.createRange();
@@ -42,16 +42,17 @@ module Capybara::Apparition
42
42
  def drop(*args)
43
43
  if args[0].is_a? String
44
44
  input = evaluate_on ATTACH_FILE
45
- input = Capybara::Apparition::Node.new(driver, @page, input['objectId'])
45
+ tag_name = input['description'].split(/[\.#]/, 2)[0]
46
+ input = Capybara::Apparition::Node.new(driver, @page, input['objectId'], tag_name: tag_name)
46
47
  input.set(args)
47
- evaluate_on DROP_FILE, { objectId: input.id }
48
+ evaluate_on DROP_FILE, objectId: input.id
48
49
  else
49
50
  items = args.each_with_object([]) do |arg, arr|
50
51
  arg.each_with_object(arr) do |(type, data), arr_|
51
52
  arr_ << { type: type, data: data }
52
53
  end
53
54
  end
54
- evaluate_on DROP_STRING, { value: items }
55
+ evaluate_on DROP_STRING, value: items
55
56
  end
56
57
  end
57
58
 
@@ -103,7 +104,6 @@ module Capybara::Apparition
103
104
  }
104
105
  JS
105
106
 
106
-
107
107
  private
108
108
 
109
109
  def html5_drag_to(element)
@@ -145,10 +145,11 @@ module Capybara::Apparition
145
145
  pixel_ratio = evaluate('window.devicePixelRatio')
146
146
  scale = (@browser.zoom_factor || 1).to_f / pixel_ratio
147
147
  if options[:format].to_s == 'pdf'
148
- params = {}
149
- params[:paperWidth] = @browser.paper_size[:width].to_f if @browser.paper_size
150
- params[:paperHeight] = @browser.paper_size[:height].to_f if @browser.paper_size
151
- params[:scale] = scale
148
+ params = { scale: scale }
149
+ if @browser.paper_size
150
+ params[:paperWidth] = @browser.paper_size[:width].to_f
151
+ params[:paperHeight] = @browser.paper_size[:height].to_f
152
+ end
152
153
  command('Page.printToPDF', params)
153
154
  else
154
155
  clip_options = if options[:selector]
@@ -173,15 +174,14 @@ module Capybara::Apparition
173
174
  frame_id = node['node']['frameId']
174
175
 
175
176
  timer = Capybara::Helpers.timer(expire_in: 10)
176
- while (frame = @frames.get(frame_id)).nil? || frame.loading?
177
+ while (frame = @frames[frame_id]).nil? || frame.loading?
177
178
  # Wait for the frame creation messages to be processed
178
179
  if timer.expired?
179
- puts 'Timed out waiting from frame to be ready'
180
+ puts 'Timed out waiting for frame to be ready'
180
181
  raise TimeoutError.new('push_frame')
181
182
  end
182
183
  sleep 0.1
183
184
  end
184
- return unless frame
185
185
 
186
186
  frame.element_id = frame_el.base.id
187
187
  @frames.push_frame(frame.id)
@@ -196,8 +196,8 @@ module Capybara::Apparition
196
196
  wait_for_loaded
197
197
  js_escaped_selector = selector.gsub('\\', '\\\\\\').gsub('"', '\"')
198
198
  query = method == :css ? CSS_FIND_JS : XPATH_FIND_JS
199
- result = _raw_evaluate(query % js_escaped_selector)
200
- (result || []).map { |r_o| [self, r_o['objectId']] }
199
+ result = _raw_evaluate(format(query, selector: js_escaped_selector))
200
+ (result || []).map { |r_o| [self, r_o['objectId'], tag_name: r_o['description'].split(/[\.#]/, 2)[0]] }
201
201
  rescue ::Capybara::Apparition::BrowserError => e
202
202
  raise unless /is not a valid (XPath expression|selector)/.match? e.name
203
203
 
@@ -241,15 +241,29 @@ module Capybara::Apparition
241
241
  attr_reader :status_code
242
242
 
243
243
  def wait_for_loaded(allow_obsolete: false)
244
+ # We can't reliably detect if the page is loaded, so just ensure the context
245
+ # is usable
244
246
  timer = Capybara::Helpers.timer(expire_in: 30)
245
- cf = current_frame
246
- until cf.usable? || (allow_obsolete && cf.obsolete?) || @js_error
247
- if timer.expired?
248
- puts 'Timedout waiting for page to be loaded'
249
- raise TimeoutError.new('wait_for_loaded')
247
+ page_function = '(function(){ return 1 == 1; })()'
248
+ begin
249
+ response = command('Runtime.evaluate',
250
+ expression: page_function,
251
+ contextId: current_frame.context_id,
252
+ returnByValue: false,
253
+ awaitPromise: true)
254
+ process_response(response)
255
+ current_frame.loaded!
256
+ rescue # rubocop:disable Style/RescueStandardError
257
+ return if allow_obsolete && current_frame.obsolete?
258
+
259
+ unless timer.expired?
260
+ sleep 0.05
261
+ retry
250
262
  end
251
- sleep 0.05
263
+ puts 'Timedout waiting for page to be loaded' if ENV['DEBUG']
264
+ raise TimeoutError.new('wait_for_loaded')
252
265
  end
266
+
253
267
  raise JavascriptError.new(js_error) if @js_error
254
268
  end
255
269
 
@@ -702,8 +716,8 @@ module Capybara::Apparition
702
716
  def process_response(response)
703
717
  return nil if response.nil?
704
718
 
705
- exception_details = response['exceptionDetails']
706
- if (exception = exception_details&.dig('exception'))
719
+ exception = response['exceptionDetails']&.dig('exception')
720
+ if exception
707
721
  case exception['className']
708
722
  when 'DOMException'
709
723
  raise ::Capybara::Apparition::BrowserError.new('name' => exception['description'], 'args' => nil)
@@ -798,12 +812,12 @@ module Capybara::Apparition
798
812
  JS
799
813
 
800
814
  CSS_FIND_JS = <<~JS
801
- Array.from(document.querySelectorAll("%s"));
815
+ Array.from(document.querySelectorAll("%<selector>s"));
802
816
  JS
803
817
 
804
818
  XPATH_FIND_JS = <<~JS
805
819
  (function(){
806
- const xpath = document.evaluate("%s", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
820
+ const xpath = document.evaluate("%<selector>s", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
807
821
  let results = [];
808
822
  for (let i=0; i < xpath.snapshotLength; i++){
809
823
  results.push(xpath.snapshotItem(i))
@@ -43,6 +43,7 @@ module Capybara::Apparition
43
43
  @frames[id]
44
44
  end
45
45
  end
46
+ alias :[] :get
46
47
 
47
48
  def delete(id)
48
49
  @frames_mutex.synchronize do
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Capybara
4
4
  module Apparition
5
- VERSION = '0.2.0'
5
+ VERSION = '0.3.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,36 +1,22 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apparition
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.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-05-18 00:00:00.000000000 Z
11
+ date: 2019-05-28 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: backports
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: capybara
29
15
  requirement: !ruby/object:Gem::Requirement
30
16
  requirements:
31
17
  - - "~>"
32
18
  - !ruby/object:Gem::Version
33
- version: '3.12'
19
+ version: '3.13'
34
20
  - - "<"
35
21
  - !ruby/object:Gem::Version
36
22
  version: '4'
@@ -40,7 +26,7 @@ dependencies:
40
26
  requirements:
41
27
  - - "~>"
42
28
  - !ruby/object:Gem::Version
43
- version: '3.12'
29
+ version: '3.13'
44
30
  - - "<"
45
31
  - !ruby/object:Gem::Version
46
32
  version: '4'
@@ -184,6 +170,48 @@ dependencies:
184
170
  - - "~>"
185
171
  - !ruby/object:Gem::Version
186
172
  version: '3.6'
173
+ - !ruby/object:Gem::Dependency
174
+ name: rubocop
175
+ requirement: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
180
+ type: :development
181
+ prerelease: false
182
+ version_requirements: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ - !ruby/object:Gem::Dependency
188
+ name: rubocop-performance
189
+ requirement: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - ">="
192
+ - !ruby/object:Gem::Version
193
+ version: '0'
194
+ type: :development
195
+ prerelease: false
196
+ version_requirements: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: '0'
201
+ - !ruby/object:Gem::Dependency
202
+ name: rubocop-rspec
203
+ requirement: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - ">="
206
+ - !ruby/object:Gem::Version
207
+ version: '0'
208
+ type: :development
209
+ prerelease: false
210
+ version_requirements: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - ">="
213
+ - !ruby/object:Gem::Version
214
+ version: '0'
187
215
  - !ruby/object:Gem::Dependency
188
216
  name: selenium-webdriver
189
217
  requirement: !ruby/object:Gem::Requirement
@@ -229,6 +257,7 @@ files:
229
257
  - lib/capybara/apparition/browser/frame.rb
230
258
  - lib/capybara/apparition/browser/header.rb
231
259
  - lib/capybara/apparition/browser/modal.rb
260
+ - lib/capybara/apparition/browser/page_manager.rb
232
261
  - lib/capybara/apparition/browser/render.rb
233
262
  - lib/capybara/apparition/browser/window.rb
234
263
  - lib/capybara/apparition/configuration.rb