apparition 0.1.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +40 -4
- data/lib/capybara/apparition.rb +0 -2
- data/lib/capybara/apparition/browser.rb +75 -133
- data/lib/capybara/apparition/browser/cookie.rb +4 -16
- data/lib/capybara/apparition/browser/header.rb +2 -2
- data/lib/capybara/apparition/browser/launcher.rb +25 -0
- data/lib/capybara/apparition/browser/launcher/local.rb +213 -0
- data/lib/capybara/apparition/browser/launcher/remote.rb +55 -0
- data/lib/capybara/apparition/browser/page_manager.rb +90 -0
- data/lib/capybara/apparition/browser/window.rb +29 -29
- data/lib/capybara/apparition/configuration.rb +100 -0
- data/lib/capybara/apparition/console.rb +8 -1
- data/lib/capybara/apparition/dev_tools_protocol/remote_object.rb +23 -7
- data/lib/capybara/apparition/dev_tools_protocol/session.rb +3 -4
- data/lib/capybara/apparition/driver.rb +107 -35
- data/lib/capybara/apparition/driver/chrome_client.rb +13 -8
- data/lib/capybara/apparition/driver/response.rb +1 -1
- data/lib/capybara/apparition/driver/web_socket_client.rb +1 -0
- data/lib/capybara/apparition/errors.rb +3 -3
- data/lib/capybara/apparition/network_traffic/error.rb +1 -0
- data/lib/capybara/apparition/network_traffic/request.rb +5 -5
- data/lib/capybara/apparition/node.rb +142 -50
- data/lib/capybara/apparition/node/drag.rb +165 -65
- data/lib/capybara/apparition/page.rb +180 -142
- data/lib/capybara/apparition/page/frame.rb +3 -0
- data/lib/capybara/apparition/page/frame_manager.rb +2 -1
- data/lib/capybara/apparition/page/keyboard.rb +29 -7
- data/lib/capybara/apparition/page/mouse.rb +20 -6
- data/lib/capybara/apparition/utility.rb +1 -1
- data/lib/capybara/apparition/version.rb +1 -1
- metadata +53 -23
- data/lib/capybara/apparition/dev_tools_protocol/target.rb +0 -64
- data/lib/capybara/apparition/dev_tools_protocol/target_manager.rb +0 -48
- data/lib/capybara/apparition/driver/launcher.rb +0 -217
@@ -92,7 +92,7 @@ module Capybara::Apparition
|
|
92
92
|
def send_msg(command, params)
|
93
93
|
msg_id, msg = generate_msg(command, params)
|
94
94
|
@send_mutex.synchronize do
|
95
|
-
puts "#{Time.now.to_i}: sending msg: #{msg}" if ENV['DEBUG']
|
95
|
+
puts "#{Time.now.to_i}: sending msg: #{msg}" if ENV['DEBUG'] == 'V'
|
96
96
|
@ws.send_msg(msg)
|
97
97
|
end
|
98
98
|
msg_id
|
@@ -141,7 +141,7 @@ module Capybara::Apparition
|
|
141
141
|
|
142
142
|
def read_msg
|
143
143
|
msg = JSON.parse(@ws.read_msg)
|
144
|
-
puts "#{Time.now.to_i}: got msg: #{msg}" if ENV['DEBUG']
|
144
|
+
puts "#{Time.now.to_i}: got msg: #{msg}" if ENV['DEBUG'] == 'V'
|
145
145
|
# Check if it's an event and push on event queue
|
146
146
|
@events.push msg.dup if msg['method']
|
147
147
|
|
@@ -162,7 +162,7 @@ module Capybara::Apparition
|
|
162
162
|
@msg_mutex.synchronize do
|
163
163
|
@message_available.wait(@msg_mutex, 0.1)
|
164
164
|
(@responses.keys & @async_ids).each do |msg_id|
|
165
|
-
puts "Cleaning up response for #{msg_id}" if ENV['DEBUG'] == '
|
165
|
+
puts "Cleaning up response for #{msg_id}" if ENV['DEBUG'] == 'V'
|
166
166
|
@responses.delete(msg_id)
|
167
167
|
@async_ids.delete(msg_id)
|
168
168
|
end
|
@@ -206,7 +206,7 @@ module Capybara::Apparition
|
|
206
206
|
event_name = event['method']
|
207
207
|
handlers[event_name].each do |handler|
|
208
208
|
puts "Calling handler for #{event_name}" if ENV['DEBUG'] == 'V'
|
209
|
-
handler.call(event['params'])
|
209
|
+
handler.call(**event['params'].transform_keys(&method(:snake_sym)))
|
210
210
|
end
|
211
211
|
end
|
212
212
|
|
@@ -222,12 +222,17 @@ module Capybara::Apparition
|
|
222
222
|
@async_response_handler.abort_on_exception = true
|
223
223
|
|
224
224
|
@listener = Thread.new do
|
225
|
-
|
226
|
-
|
227
|
-
rescue EOFError # rubocop:disable Lint/HandleExceptions
|
228
|
-
end
|
225
|
+
listen
|
226
|
+
rescue EOFError # rubocop:disable Lint/SuppressedException
|
229
227
|
end
|
230
228
|
# @listener.abort_on_exception = true
|
231
229
|
end
|
230
|
+
|
231
|
+
def snake_sym(str)
|
232
|
+
str.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
233
|
+
.tr('-', '_')
|
234
|
+
.downcase
|
235
|
+
.to_sym
|
236
|
+
end
|
232
237
|
end
|
233
238
|
end
|
@@ -15,7 +15,7 @@ module Capybara::Apparition
|
|
15
15
|
handle_error(resp['error']) if resp['error']
|
16
16
|
resp
|
17
17
|
end.last
|
18
|
-
puts "Processed msg: #{@msg_ids.last} in #{Time.now - @send_time} seconds" if ENV['DEBUG']
|
18
|
+
puts "Processed msg: #{@msg_ids.last} in #{Time.now - @send_time} seconds" if ENV['DEBUG'] == 'V'
|
19
19
|
|
20
20
|
response['result']
|
21
21
|
end
|
@@ -45,7 +45,7 @@ module Capybara
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def message
|
48
|
-
'There was an error inside
|
48
|
+
'There was an error inside Apparition. ' \
|
49
49
|
'If this is the error returned, and not the cause of a more detailed error response, ' \
|
50
50
|
'this is probably a bug, so please report it. ' \
|
51
51
|
"\n\n#{name}: #{error_parameters}"
|
@@ -57,7 +57,7 @@ module Capybara
|
|
57
57
|
# response['args'].first.map { |data| JSErrorItem.new(data['message'], data['stack']) }
|
58
58
|
# end
|
59
59
|
def javascript_errors
|
60
|
-
[response]
|
60
|
+
[message: response]
|
61
61
|
end
|
62
62
|
|
63
63
|
def message
|
@@ -65,7 +65,7 @@ module Capybara
|
|
65
65
|
"If you don't care about these errors, you can ignore them by " \
|
66
66
|
'setting js_errors: false in your Apparition configuration (see ' \
|
67
67
|
'documentation for details).' \
|
68
|
-
"\n\n#{javascript_errors.map
|
68
|
+
"\n\n#{javascript_errors.map { |err| err[:message] }.join("\n")}"
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
@@ -17,23 +17,23 @@ module Capybara::Apparition::NetworkTraffic
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def request_id
|
20
|
-
@data[
|
20
|
+
@data[:request_id]
|
21
21
|
end
|
22
22
|
|
23
23
|
def url
|
24
|
-
@data
|
24
|
+
@data[:request]&.dig('url')
|
25
25
|
end
|
26
26
|
|
27
27
|
def method
|
28
|
-
@data
|
28
|
+
@data[:request]&.dig('method')
|
29
29
|
end
|
30
30
|
|
31
31
|
def headers
|
32
|
-
@data
|
32
|
+
@data[:request]&.dig('headers')
|
33
33
|
end
|
34
34
|
|
35
35
|
def time
|
36
|
-
@data[
|
36
|
+
@data[:timestamp] && Time.parse(@data[:timestamp])
|
37
37
|
end
|
38
38
|
|
39
39
|
def blocked?
|
@@ -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,12 +30,13 @@ 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
|
-
|
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
|
-
raise unless
|
37
|
+
raise unless /is not a valid (XPath expression|selector)/.match? e.name
|
37
38
|
|
38
|
-
raise Capybara::Apparition::InvalidSelector, [method, selector]
|
39
|
+
raise Capybara::Apparition::InvalidSelector, 'args' => [method, selector]
|
39
40
|
end
|
40
41
|
|
41
42
|
def find_xpath(selector)
|
@@ -71,6 +72,18 @@ module Capybara::Apparition
|
|
71
72
|
visible_text
|
72
73
|
end
|
73
74
|
|
75
|
+
# capybara-webkit method
|
76
|
+
def inner_html
|
77
|
+
self[:innerHTML]
|
78
|
+
end
|
79
|
+
|
80
|
+
# capybara-webkit method
|
81
|
+
def inner_html=(value)
|
82
|
+
driver.execute_script <<~JS, self, value
|
83
|
+
arguments[0].innerHTML = arguments[1]
|
84
|
+
JS
|
85
|
+
end
|
86
|
+
|
74
87
|
def property(name)
|
75
88
|
evaluate_on('name => this[name]', value: name)
|
76
89
|
end
|
@@ -117,11 +130,18 @@ module Capybara::Apparition
|
|
117
130
|
set_time(value)
|
118
131
|
when 'datetime-local'
|
119
132
|
set_datetime_local(value)
|
133
|
+
when 'color'
|
134
|
+
set_color(value)
|
135
|
+
when 'range'
|
136
|
+
set_range(value)
|
120
137
|
else
|
121
|
-
set_text(value.to_s, delay:
|
138
|
+
set_text(value.to_s, **{ delay: 0 }.merge(options))
|
122
139
|
end
|
123
140
|
elsif tag_name == 'textarea'
|
124
141
|
set_text(value.to_s)
|
142
|
+
elsif tag_name == 'select'
|
143
|
+
warn "Setting the value of a select element via 'set' is deprecated, please use 'select' or 'select_option'."
|
144
|
+
evaluate_on '()=>{ this.value = arguments[0] }', value: value.to_s
|
125
145
|
elsif self[:isContentEditable]
|
126
146
|
delete_text
|
127
147
|
send_keys(value.to_s, delay: options.fetch(:delay, 0))
|
@@ -150,6 +170,21 @@ module Capybara::Apparition
|
|
150
170
|
evaluate_on VISIBLE_JS
|
151
171
|
end
|
152
172
|
|
173
|
+
def obscured?(**)
|
174
|
+
pos = visible_center(allow_scroll: false)
|
175
|
+
return true if pos.nil?
|
176
|
+
|
177
|
+
hit_node = @page.element_from_point(**pos)
|
178
|
+
return true if hit_node.nil?
|
179
|
+
|
180
|
+
begin
|
181
|
+
return evaluate_on('el => !this.contains(el)', objectId: hit_node['objectId'])
|
182
|
+
rescue WrongWorld # rubocop:disable Lint/SuppressedException
|
183
|
+
end
|
184
|
+
|
185
|
+
true
|
186
|
+
end
|
187
|
+
|
153
188
|
def checked?
|
154
189
|
self[:checked]
|
155
190
|
end
|
@@ -162,20 +197,23 @@ module Capybara::Apparition
|
|
162
197
|
evaluate_on ELEMENT_DISABLED_JS
|
163
198
|
end
|
164
199
|
|
165
|
-
def click(keys = [], button: 'left', count: 1, **options)
|
166
|
-
pos = element_click_pos(options)
|
200
|
+
def click(keys = [], button: 'left', count: 1, delay: 0, **options)
|
201
|
+
pos = element_click_pos(**options)
|
167
202
|
raise ::Capybara::Apparition::MouseEventImpossible.new(self, 'args' => ['click']) if pos.nil?
|
168
203
|
|
169
|
-
test = mouse_event_test(pos)
|
204
|
+
test = mouse_event_test(**pos)
|
170
205
|
raise ::Capybara::Apparition::MouseEventImpossible.new(self, 'args' => ['click']) if test.nil?
|
171
|
-
raise ::Capybara::Apparition::MouseEventFailed.new(self, 'args' => ['click', test.selector, pos]) unless test.success
|
172
206
|
|
173
|
-
|
207
|
+
unless options[:x] && options[:y]
|
208
|
+
raise ::Capybara::Apparition::MouseEventFailed.new(self, 'args' => ['click', test.selector, pos]) unless test.success
|
209
|
+
end
|
210
|
+
|
211
|
+
@page.mouse.click_at(**pos.merge(button: button, count: count, modifiers: keys, delay: delay))
|
174
212
|
if ENV['DEBUG']
|
175
213
|
begin
|
176
|
-
new_pos = element_click_pos(options)
|
214
|
+
new_pos = element_click_pos(**options)
|
177
215
|
puts "Element moved from #{pos} to #{new_pos}" unless pos == new_pos
|
178
|
-
rescue WrongWorld # rubocop:disable Lint/
|
216
|
+
rescue WrongWorld # rubocop:disable Lint/SuppressedException
|
179
217
|
end
|
180
218
|
end
|
181
219
|
# Wait a short time to see if click triggers page load
|
@@ -195,7 +233,7 @@ module Capybara::Apparition
|
|
195
233
|
pos = visible_center
|
196
234
|
raise ::Capybara::Apparition::MouseEventImpossible.new(self, 'args' => ['hover']) if pos.nil?
|
197
235
|
|
198
|
-
@page.mouse.move_to(pos)
|
236
|
+
@page.mouse.move_to(**pos)
|
199
237
|
end
|
200
238
|
|
201
239
|
EVENTS = {
|
@@ -227,15 +265,19 @@ module Capybara::Apparition
|
|
227
265
|
evaluate_on DISPATCH_EVENT_JS, { value: event_type }, { value: name }, value: opts.merge(options)
|
228
266
|
end
|
229
267
|
|
268
|
+
def submit
|
269
|
+
evaluate_on '()=>{ this.submit() }'
|
270
|
+
end
|
271
|
+
|
230
272
|
def ==(other)
|
231
273
|
evaluate_on('el => this == el', objectId: other.id)
|
232
274
|
rescue ObsoleteNode
|
233
275
|
false
|
234
276
|
end
|
235
277
|
|
236
|
-
def send_keys(*keys, delay: 0, **
|
278
|
+
def send_keys(*keys, delay: 0, **opts)
|
237
279
|
click unless evaluate_on CURRENT_NODE_SELECTED_JS
|
238
|
-
|
280
|
+
_send_keys(*keys, delay: delay, **opts)
|
239
281
|
end
|
240
282
|
alias_method :send_key, :send_keys
|
241
283
|
|
@@ -243,9 +285,13 @@ module Capybara::Apparition
|
|
243
285
|
evaluate_on GET_PATH_JS
|
244
286
|
end
|
245
287
|
|
246
|
-
def element_click_pos(x: nil, y: nil, **)
|
288
|
+
def element_click_pos(x: nil, y: nil, offset: nil, **)
|
247
289
|
if x && y
|
248
|
-
|
290
|
+
if offset == :center
|
291
|
+
visible_center
|
292
|
+
else
|
293
|
+
visible_top_left
|
294
|
+
end.tap do |p|
|
249
295
|
p[:x] += x
|
250
296
|
p[:y] += y
|
251
297
|
end
|
@@ -255,7 +301,7 @@ module Capybara::Apparition
|
|
255
301
|
end
|
256
302
|
|
257
303
|
def visible_top_left
|
258
|
-
rect =
|
304
|
+
rect = in_view_client_rect
|
259
305
|
return nil if rect.nil?
|
260
306
|
|
261
307
|
frame_offset = @page.current_frame_offset
|
@@ -288,8 +334,8 @@ module Capybara::Apparition
|
|
288
334
|
end
|
289
335
|
end
|
290
336
|
|
291
|
-
def visible_center
|
292
|
-
rect =
|
337
|
+
def visible_center(allow_scroll: true)
|
338
|
+
rect = in_view_client_rect(allow_scroll: allow_scroll)
|
293
339
|
return nil if rect.nil?
|
294
340
|
|
295
341
|
frame_offset = @page.current_frame_offset
|
@@ -332,12 +378,16 @@ module Capybara::Apparition
|
|
332
378
|
end
|
333
379
|
|
334
380
|
def top_left
|
335
|
-
result = evaluate_on
|
381
|
+
result = evaluate_on GET_CLIENT_RECT_JS
|
336
382
|
return nil if result.nil?
|
337
383
|
|
338
384
|
{ x: result['x'], y: result['y'] }
|
339
385
|
end
|
340
386
|
|
387
|
+
def rect
|
388
|
+
evaluate_on GET_CLIENT_RECT_JS
|
389
|
+
end
|
390
|
+
|
341
391
|
def scroll_by(x, y)
|
342
392
|
evaluate_on <<~JS, { value: x }, value: y
|
343
393
|
(x, y) => this.scrollBy(x,y)
|
@@ -382,9 +432,39 @@ module Capybara::Apparition
|
|
382
432
|
|
383
433
|
private
|
384
434
|
|
385
|
-
def
|
386
|
-
|
387
|
-
|
435
|
+
def focus
|
436
|
+
@page.command('DOM.focus', objectId: id)
|
437
|
+
end
|
438
|
+
|
439
|
+
def keys_to_send(value, clear)
|
440
|
+
case clear
|
441
|
+
when :backspace
|
442
|
+
# Clear field by sending the correct number of backspace keys.
|
443
|
+
[:end] + ([:backspace] * self.value.to_s.length) + [value]
|
444
|
+
when :none
|
445
|
+
[value]
|
446
|
+
when Array
|
447
|
+
clear << value
|
448
|
+
else
|
449
|
+
# Clear field by JavaScript assignment of the value property.
|
450
|
+
# Script can change a readonly element which user input cannot, so
|
451
|
+
# don't execute if readonly.
|
452
|
+
driver.execute_script <<~JS, self
|
453
|
+
if (!arguments[0].readOnly) {
|
454
|
+
arguments[0].value = ''
|
455
|
+
}
|
456
|
+
JS
|
457
|
+
[value]
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
def _send_keys(*keys, delay: 0, **_opts)
|
462
|
+
@page.keyboard.type(keys, delay: delay)
|
463
|
+
end
|
464
|
+
|
465
|
+
def in_view_client_rect(allow_scroll: true)
|
466
|
+
evaluate_on('() => this.scrollIntoViewIfNeeded()') if allow_scroll
|
467
|
+
result = evaluate_on GET_CLIENT_RECT_JS
|
388
468
|
result = result['model'] if result && result['model']
|
389
469
|
result
|
390
470
|
end
|
@@ -410,26 +490,21 @@ module Capybara::Apparition
|
|
410
490
|
DevToolsProtocol::RemoteObject.new(@page, response['result'] || response['object']).value
|
411
491
|
end
|
412
492
|
|
413
|
-
def set_text(value, clear: nil, delay: 0, **_unused)
|
493
|
+
def set_text(value, clear: nil, delay: 0, rapid: nil, **_unused)
|
414
494
|
value = value.to_s
|
415
495
|
if value.empty? && clear.nil?
|
416
496
|
evaluate_on CLEAR_ELEMENT_JS
|
417
|
-
elsif clear == :backspace
|
418
|
-
# Clear field by sending the correct number of backspace keys.
|
419
|
-
backspaces = [:backspace] * self.value.to_s.length
|
420
|
-
send_keys(*([:end] + backspaces + [value]), delay: delay)
|
421
|
-
elsif clear.is_a? Array
|
422
|
-
send_keys(*clear, value, delay: delay)
|
423
497
|
else
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
498
|
+
focus
|
499
|
+
if (rapid && (value.length >= 6)) || ((value.length > 30) && rapid != false)
|
500
|
+
_send_keys(*keys_to_send(value[0..2], clear), delay: delay)
|
501
|
+
driver.execute_script <<~JS, self, value[0...-3]
|
502
|
+
arguments[0].value = arguments[1]
|
503
|
+
JS
|
504
|
+
_send_keys(*keys_to_send(value[-3..-1], :none), delay: delay)
|
505
|
+
else
|
506
|
+
_send_keys(*keys_to_send(value, clear), delay: delay)
|
507
|
+
end
|
433
508
|
end
|
434
509
|
end
|
435
510
|
|
@@ -465,6 +540,14 @@ module Capybara::Apparition
|
|
465
540
|
update_value_js(value.to_datetime_str)
|
466
541
|
end
|
467
542
|
|
543
|
+
def set_color(value)
|
544
|
+
update_value_js(value.to_s)
|
545
|
+
end
|
546
|
+
|
547
|
+
def set_range(value)
|
548
|
+
update_value_js(value.to_s)
|
549
|
+
end
|
550
|
+
|
468
551
|
def update_value_js(value)
|
469
552
|
evaluate_on(<<~JS, value: value)
|
470
553
|
value => {
|
@@ -488,7 +571,8 @@ module Capybara::Apparition
|
|
488
571
|
r_o = @page.element_from_point(x: x, y: y)
|
489
572
|
return nil unless r_o && r_o['objectId']
|
490
573
|
|
491
|
-
|
574
|
+
tag_name = r_o['description'].split(/[.#]/, 2)[0]
|
575
|
+
hit_node = Capybara::Apparition::Node.new(driver, @page, r_o['objectId'], tag_name: tag_name)
|
492
576
|
result = begin
|
493
577
|
evaluate_on(<<~JS, objectId: hit_node.id)
|
494
578
|
(hit_node) => {
|
@@ -658,11 +742,14 @@ module Capybara::Apparition
|
|
658
742
|
sel = sel.parentNode;
|
659
743
|
}
|
660
744
|
let event_options = { bubbles: true, cancelable: true };
|
745
|
+
sel.dispatchEvent(new MouseEvent('mousedown', event_options));
|
661
746
|
sel.dispatchEvent(new FocusEvent('focus', event_options));
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
747
|
+
if (this.selected == false){
|
748
|
+
this.selected = true;
|
749
|
+
sel.dispatchEvent(new Event('change', event_options));
|
750
|
+
}
|
751
|
+
sel.dispatchEvent(new MouseEvent('mouseup', event_options));
|
752
|
+
sel.dispatchEvent(new MouseEvent('click', event_options));
|
666
753
|
sel.dispatchEvent(new FocusEvent('blur', event_options));
|
667
754
|
}
|
668
755
|
JS
|
@@ -686,7 +773,7 @@ module Capybara::Apparition
|
|
686
773
|
# if an area element, check visibility of relevant image
|
687
774
|
VISIBLE_JS = <<~JS
|
688
775
|
function(){
|
689
|
-
el = this;
|
776
|
+
let el = this;
|
690
777
|
if (el.tagName == 'AREA'){
|
691
778
|
const map_name = document.evaluate('./ancestor::map/@name', el, null, XPathResult.STRING_TYPE, null).stringValue;
|
692
779
|
el = document.querySelector(`img[usemap='#${map_name}']`);
|
@@ -704,7 +791,11 @@ module Capybara::Apparition
|
|
704
791
|
(parseFloat(style.opacity) == 0)) {
|
705
792
|
return false;
|
706
793
|
}
|
707
|
-
|
794
|
+
var parent = el.parentElement;
|
795
|
+
if (parent && (parent.tagName == 'DETAILS') && !parent.open && (el.tagName != 'SUMMARY')) {
|
796
|
+
return false;
|
797
|
+
}
|
798
|
+
el = parent;
|
708
799
|
}
|
709
800
|
return true;
|
710
801
|
}
|
@@ -721,9 +812,10 @@ module Capybara::Apparition
|
|
721
812
|
}
|
722
813
|
JS
|
723
814
|
|
724
|
-
|
815
|
+
GET_CLIENT_RECT_JS = <<~JS
|
725
816
|
function(){
|
726
|
-
|
817
|
+
var rects = [...this.getClientRects()]
|
818
|
+
var rect = rects.find(r => (r.height && r.width)) || this.getBoundingClientRect();
|
727
819
|
return rect.toJSON();
|
728
820
|
}
|
729
821
|
JS
|