apparition 0.1.0 → 0.6.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 +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
|