apparition 0.2.0 → 0.3.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.
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