capybara 3.20.2 → 3.21.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/History.md +14 -0
- data/README.md +4 -4
- data/lib/capybara.rb +11 -11
- data/lib/capybara/driver/node.rb +4 -0
- data/lib/capybara/helpers.rb +1 -1
- data/lib/capybara/minitest.rb +4 -4
- data/lib/capybara/node/actions.rb +36 -29
- data/lib/capybara/node/element.rb +123 -82
- data/lib/capybara/node/finders.rb +2 -7
- data/lib/capybara/selector/definition/css.rb +4 -1
- data/lib/capybara/selector/regexp_disassembler.rb +2 -2
- data/lib/capybara/selenium/extensions/html5_drag.rb +63 -0
- data/lib/capybara/selenium/node.rb +4 -0
- data/lib/capybara/selenium/nodes/chrome_node.rb +4 -0
- data/lib/capybara/selenium/nodes/firefox_node.rb +4 -13
- data/lib/capybara/selenium/nodes/ie_node.rb +14 -3
- data/lib/capybara/selenium/nodes/safari_node.rb +0 -13
- data/lib/capybara/spec/public/test.js +29 -3
- data/lib/capybara/spec/session/attach_file_spec.rb +13 -5
- data/lib/capybara/spec/session/has_css_spec.rb +4 -3
- data/lib/capybara/spec/session/node_spec.rb +85 -0
- data/lib/capybara/spec/spec_helper.rb +4 -0
- data/lib/capybara/version.rb +1 -1
- data/spec/selenium_spec_firefox.rb +2 -0
- data/spec/selenium_spec_ie.rb +13 -7
- data/spec/selenium_spec_safari.rb +2 -0
- data/spec/shared_selenium_session.rb +1 -51
- metadata +4 -4
@@ -8,19 +8,14 @@ module Capybara
|
|
8
8
|
# Find an {Capybara::Node::Element} based on the given arguments. +find+ will raise an error if the element
|
9
9
|
# is not found.
|
10
10
|
#
|
11
|
-
# @!macro waiting_behavior
|
12
|
-
# If the driver is capable of executing JavaScript, this method will wait for a set amount of time
|
13
|
-
# and continuously retry finding the element until either the element is found or the time
|
14
|
-
# expires. The length of time +find+ will wait is controlled through {Capybara.default_max_wait_time}
|
15
|
-
# and defaults to 2 seconds.
|
16
|
-
# @option options [false, true, Numeric] wait (Capybara.default_max_wait_time) Maximum time to wait for matching element to appear.
|
17
|
-
#
|
18
11
|
# page.find('#foo').find('.bar')
|
19
12
|
# page.find(:xpath, './/div[contains(., "bar")]')
|
20
13
|
# page.find('li', text: 'Quox').click_link('Delete')
|
21
14
|
#
|
22
15
|
# @param (see Capybara::Node::Finders#all)
|
23
16
|
#
|
17
|
+
# @macro waiting_behavior
|
18
|
+
#
|
24
19
|
# @!macro system_filters
|
25
20
|
# @option options [String, Regexp] text Only find elements which contain this text or match this regexp
|
26
21
|
# @option options [String, Boolean] exact_text (Capybara.exact_text) When String the elements contained text must match exactly, when Boolean controls whether the :text option must match exactly
|
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
Capybara.add_selector(:css, locator_type: [String, Symbol], raw_locator: true) do
|
4
|
-
css
|
4
|
+
css do |css|
|
5
|
+
warn "DEPRECATED: Passing a symbol (#{css.inspect}) as the CSS locator is deprecated - please pass a string instead." if css.is_a? Symbol
|
6
|
+
css
|
7
|
+
end
|
5
8
|
end
|
@@ -22,6 +22,69 @@ class Capybara::Selenium::Node
|
|
22
22
|
native.property('draggable')
|
23
23
|
end
|
24
24
|
|
25
|
+
def html5_drop(*args)
|
26
|
+
if args[0].is_a? String
|
27
|
+
input = driver.evaluate_script ATTACH_FILE
|
28
|
+
input.set_file(args)
|
29
|
+
driver.execute_script DROP_FILE, self, input
|
30
|
+
else
|
31
|
+
items = args.each_with_object([]) do |arg, arr|
|
32
|
+
arg.each_with_object(arr) do |(type, data), arr_|
|
33
|
+
arr_ << { type: type, data: data }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
driver.execute_script DROP_STRING, items, self
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
DROP_STRING = <<~JS
|
41
|
+
var strings = arguments[0],
|
42
|
+
el = arguments[1],
|
43
|
+
dt = new DataTransfer(),
|
44
|
+
opts = { cancelable: true, bubbles: true, dataTransfer: dt };
|
45
|
+
for (var i=0; i < strings.length; i++){
|
46
|
+
if (dt.items) {
|
47
|
+
dt.items.add(strings[i]['data'], strings[i]['type']);
|
48
|
+
} else {
|
49
|
+
dt.setData(strings[i]['type'], strings[i]['data']);
|
50
|
+
}
|
51
|
+
}
|
52
|
+
var dropEvent = new DragEvent('drop', opts);
|
53
|
+
el.dispatchEvent(dropEvent);
|
54
|
+
JS
|
55
|
+
|
56
|
+
DROP_FILE = <<~JS
|
57
|
+
var el = arguments[0],
|
58
|
+
input = arguments[1],
|
59
|
+
files = input.files,
|
60
|
+
dt = new DataTransfer(),
|
61
|
+
opts = { cancelable: true, bubbles: true, dataTransfer: dt };
|
62
|
+
input.parentElement.removeChild(input);
|
63
|
+
if (dt.items){
|
64
|
+
for (var i=0; i<files.length; i++){
|
65
|
+
dt.items.add(files[i]);
|
66
|
+
}
|
67
|
+
} else {
|
68
|
+
Object.defineProperty(dt, "files", {
|
69
|
+
value: files,
|
70
|
+
writable: false
|
71
|
+
});
|
72
|
+
}
|
73
|
+
var dropEvent = new DragEvent('drop', opts);
|
74
|
+
el.dispatchEvent(dropEvent);
|
75
|
+
JS
|
76
|
+
|
77
|
+
ATTACH_FILE = <<~JS
|
78
|
+
(function(){
|
79
|
+
var input = document.createElement('INPUT');
|
80
|
+
input.type = "file";
|
81
|
+
input.id = "_capybara_drop_file";
|
82
|
+
input.multiple = true;
|
83
|
+
document.body.appendChild(input);
|
84
|
+
return input;
|
85
|
+
})()
|
86
|
+
JS
|
87
|
+
|
25
88
|
MOUSEDOWN_TRACKER = <<~JS
|
26
89
|
document.addEventListener('mousedown', ev => {
|
27
90
|
window.capybara_mousedown_prevented = ev.defaultPrevented;
|
@@ -137,6 +137,10 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
137
137
|
element.scroll_if_needed { browser_action.move_to(element.native).release.perform }
|
138
138
|
end
|
139
139
|
|
140
|
+
def drop(*_)
|
141
|
+
raise NotImplementedError, 'Out of browser drop emulation is not implemented for the current browser'
|
142
|
+
end
|
143
|
+
|
140
144
|
def tag_name
|
141
145
|
@tag_name ||= native.tag_name.downcase
|
142
146
|
end
|
@@ -52,6 +52,10 @@ class Capybara::Selenium::FirefoxNode < Capybara::Selenium::Node
|
|
52
52
|
html5_drag_to(element)
|
53
53
|
end
|
54
54
|
|
55
|
+
def drop(*args)
|
56
|
+
html5_drop(*args)
|
57
|
+
end
|
58
|
+
|
55
59
|
def hover
|
56
60
|
return super unless browser_version >= 65.0
|
57
61
|
|
@@ -117,19 +121,6 @@ private
|
|
117
121
|
driver.browser.capabilities[:browser_version].to_f
|
118
122
|
end
|
119
123
|
|
120
|
-
DISABLED_BY_FIELDSET_XPATH = XPath.generate do |x|
|
121
|
-
x.parent(:fieldset)[
|
122
|
-
x.attr(:disabled)
|
123
|
-
] + x.ancestor[
|
124
|
-
~x.self(:legend) |
|
125
|
-
x.preceding_sibling(:legend)
|
126
|
-
][
|
127
|
-
x.parent(:fieldset)[
|
128
|
-
x.attr(:disabled)
|
129
|
-
]
|
130
|
-
]
|
131
|
-
end.to_s.freeze
|
132
|
-
|
133
124
|
class ModifierKeysStack
|
134
125
|
def initialize
|
135
126
|
@stack = []
|
@@ -4,8 +4,19 @@ require 'capybara/selenium/extensions/html5_drag'
|
|
4
4
|
|
5
5
|
class Capybara::Selenium::IENode < Capybara::Selenium::Node
|
6
6
|
def disabled?
|
7
|
-
#
|
8
|
-
#
|
9
|
-
|
7
|
+
# super
|
8
|
+
# optimize to one script call
|
9
|
+
driver.evaluate_script <<~JS.delete("\n"), self
|
10
|
+
arguments[0].msMatchesSelector('
|
11
|
+
:disabled,
|
12
|
+
select:disabled *,
|
13
|
+
optgroup:disabled *,
|
14
|
+
fieldset[disabled],
|
15
|
+
fieldset[disabled] > *:not(legend),
|
16
|
+
fieldset[disabled] > *:not(legend) *,
|
17
|
+
fieldset[disabled] > legend:nth-of-type(n+2),
|
18
|
+
fieldset[disabled] > legend:nth-of-type(n+2) *
|
19
|
+
')
|
20
|
+
JS
|
10
21
|
end
|
11
22
|
end
|
@@ -91,19 +91,6 @@ private
|
|
91
91
|
driver.browser.send(:bridge)
|
92
92
|
end
|
93
93
|
|
94
|
-
DISABLED_BY_FIELDSET_XPATH = XPath.generate do |x|
|
95
|
-
x.parent(:fieldset)[
|
96
|
-
x.attr(:disabled)
|
97
|
-
] + x.ancestor[
|
98
|
-
~x.self(:legend) |
|
99
|
-
x.preceding_sibling(:legend)
|
100
|
-
][
|
101
|
-
x.parent(:fieldset)[
|
102
|
-
x.attr(:disabled)
|
103
|
-
]
|
104
|
-
]
|
105
|
-
end.to_s.freeze
|
106
|
-
|
107
94
|
def _send_keys(keys, actions = browser_action, down_keys = ModifierKeysStack.new)
|
108
95
|
case keys
|
109
96
|
when *MODIFIER_KEYS
|
@@ -18,7 +18,33 @@ $(function() {
|
|
18
18
|
});
|
19
19
|
$('#drop_html5, #drop_html5_scroll').on('drop', function(ev){
|
20
20
|
ev.preventDefault();
|
21
|
-
|
21
|
+
var oev = ev.originalEvent;
|
22
|
+
if (oev.dataTransfer.items) {
|
23
|
+
for (var i = 0; i < oev.dataTransfer.items.length; i++){
|
24
|
+
var item = oev.dataTransfer.items[i];
|
25
|
+
if (item.kind === 'file'){
|
26
|
+
var file = item.getAsFile();
|
27
|
+
$(this).append('HTML5 Dropped file: ' + file.name);
|
28
|
+
} else {
|
29
|
+
var _this = this;
|
30
|
+
var callback = (function(type){
|
31
|
+
return function(s){
|
32
|
+
$(_this).append('HTML5 Dropped string: ' + type + ' ' + s)
|
33
|
+
}
|
34
|
+
})(item.type);
|
35
|
+
item.getAsString(callback);
|
36
|
+
}
|
37
|
+
}
|
38
|
+
} else {
|
39
|
+
$(this).html('HTML5 Dropped ' + oev.dataTransfer.getData("text"));
|
40
|
+
for (var i = 0; i < oev.dataTransfer.files.length; i++) {
|
41
|
+
$(this).append('HTML5 Dropped file: ' + oev.dataTransfer.files[i].name);
|
42
|
+
}
|
43
|
+
for (var i = 0; i < oev.dataTransfer.types.length; i++) {
|
44
|
+
var type = oev.dataTransfer.types[i];
|
45
|
+
$(this).append('HTML5 Dropped string: ' + type + ' ' + oev.dataTransfer.getData(type));
|
46
|
+
}
|
47
|
+
}
|
22
48
|
});
|
23
49
|
$('#clickable').click(function(e) {
|
24
50
|
var link = $(this);
|
@@ -180,7 +206,7 @@ $(function() {
|
|
180
206
|
sessionStorage.setItem('session', 'session_value');
|
181
207
|
localStorage.setItem('local', 'local value');
|
182
208
|
});
|
183
|
-
$('#multiple-file').change(function(e){
|
184
|
-
$('body').append($('<p class="file_change"
|
209
|
+
$('#multiple-file, #hidden_file').change(function(e){
|
210
|
+
$('body').append($('<p class="file_change">File input changed</p>'));
|
185
211
|
})
|
186
212
|
});
|
@@ -180,6 +180,12 @@ Capybara::SpecHelper.spec '#attach_file' do
|
|
180
180
|
@session.attach_file('hidden_file', with_os_path_separators(__FILE__), make_visible: true)
|
181
181
|
expect(@session.evaluate_script('arguments[0].style.display', @session.find(:css, '#hidden_file', visible: :all))).to eq 'none'
|
182
182
|
end
|
183
|
+
|
184
|
+
it 'should fire change' do
|
185
|
+
@session.visit('/with_js')
|
186
|
+
@session.attach_file('hidden_file', with_os_path_separators(__FILE__), make_visible: true)
|
187
|
+
expect(@session).to have_css('.file_change')
|
188
|
+
end
|
183
189
|
end
|
184
190
|
|
185
191
|
context 'with a block', requires: %i[js] do
|
@@ -198,11 +204,13 @@ Capybara::SpecHelper.spec '#attach_file' do
|
|
198
204
|
@session.click_button('awesome')
|
199
205
|
expect(extract_results(@session)['hidden_image']).to end_with(File.basename(__FILE__))
|
200
206
|
end
|
201
|
-
end
|
202
207
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
208
|
+
it 'should fire change' do
|
209
|
+
@session.visit('/with_js')
|
210
|
+
@session.attach_file(with_os_path_separators(__FILE__)) do
|
211
|
+
@session.find(:label, 'Label for hidden file input').click
|
212
|
+
end
|
213
|
+
expect(@session).to have_css('.file_change')
|
214
|
+
end
|
207
215
|
end
|
208
216
|
end
|
@@ -10,10 +10,11 @@ Capybara::SpecHelper.spec '#has_css?' do
|
|
10
10
|
expect(@session).to have_css('p a#foo')
|
11
11
|
end
|
12
12
|
|
13
|
-
it 'should
|
13
|
+
it 'should warn when passed a symbol' do
|
14
14
|
# This was never a specifically accepted format but it has worked for a
|
15
|
-
# lot of versions.
|
16
|
-
# TODO:
|
15
|
+
# lot of versions.
|
16
|
+
# TODO: Remove in 4.0
|
17
|
+
expect_any_instance_of(Kernel).to receive(:warn) # rubocop:disable RSpec/AnyInstance
|
17
18
|
expect(@session).to have_css(:p)
|
18
19
|
end
|
19
20
|
|
@@ -242,6 +242,10 @@ Capybara::SpecHelper.spec 'node' do
|
|
242
242
|
expect(@session.find('//div[@id="hidden_attr"]')).not_to be_visible
|
243
243
|
expect(@session.find('//a[@id="hidden_attr_via_ancestor"]')).not_to be_visible
|
244
244
|
expect(@session.find('//input[@id="hidden_input"]')).not_to be_visible
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'template elements should not be visible' do
|
248
|
+
Capybara.ignore_hidden_elements = false
|
245
249
|
expect(@session.find('//template')).not_to be_visible
|
246
250
|
end
|
247
251
|
|
@@ -414,6 +418,87 @@ Capybara::SpecHelper.spec 'node' do
|
|
414
418
|
link.drag_to target
|
415
419
|
expect(@session).to have_xpath('//div[contains(., "Dropped!")]')
|
416
420
|
end
|
421
|
+
|
422
|
+
context 'HTML5', requires: %i[js html5_drag] do
|
423
|
+
it 'should HTML5 drag and drop an object' do
|
424
|
+
@session.visit('/with_js')
|
425
|
+
element = @session.find('//div[@id="drag_html5"]')
|
426
|
+
target = @session.find('//div[@id="drop_html5"]')
|
427
|
+
element.drag_to(target)
|
428
|
+
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain drag_html5")]')
|
429
|
+
end
|
430
|
+
|
431
|
+
it 'should set clientX/Y in dragover events' do
|
432
|
+
@session.visit('/with_js')
|
433
|
+
element = @session.find('//div[@id="drag_html5"]')
|
434
|
+
target = @session.find('//div[@id="drop_html5"]')
|
435
|
+
element.drag_to(target)
|
436
|
+
expect(@session).to have_css('div.log', text: /DragOver with client position: [1-9]\d*,[1-9]\d*/, count: 2)
|
437
|
+
end
|
438
|
+
|
439
|
+
it 'should not HTML5 drag and drop on a non HTML5 drop element' do
|
440
|
+
@session.visit('/with_js')
|
441
|
+
element = @session.find('//div[@id="drag_html5"]')
|
442
|
+
target = @session.find('//div[@id="drop_html5"]')
|
443
|
+
target.execute_script("$(this).removeClass('drop');")
|
444
|
+
element.drag_to(target)
|
445
|
+
sleep 1
|
446
|
+
expect(@session).not_to have_xpath('//div[contains(., "HTML5 Dropped")]')
|
447
|
+
end
|
448
|
+
|
449
|
+
it 'should HTML5 drag and drop when scrolling needed' do
|
450
|
+
@session.visit('/with_js')
|
451
|
+
element = @session.find('//div[@id="drag_html5_scroll"]')
|
452
|
+
target = @session.find('//div[@id="drop_html5_scroll"]')
|
453
|
+
element.drag_to(target)
|
454
|
+
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain drag_html5_scroll")]')
|
455
|
+
end
|
456
|
+
|
457
|
+
it 'should drag HTML5 default draggable elements' do
|
458
|
+
@session.visit('/with_js')
|
459
|
+
link = @session.find_link('drag_link_html5')
|
460
|
+
target = @session.find(:id, 'drop_html5')
|
461
|
+
link.drag_to target
|
462
|
+
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped")]')
|
463
|
+
end
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
describe 'Element#drop', requires: %i[js html5_drag] do
|
468
|
+
it 'can drop a file' do
|
469
|
+
@session.visit('/with_js')
|
470
|
+
target = @session.find('//div[@id="drop_html5"]')
|
471
|
+
target.drop(
|
472
|
+
with_os_path_separators(File.expand_path('../fixtures/capybara.jpg', File.dirname(__FILE__)))
|
473
|
+
)
|
474
|
+
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped file: capybara.jpg")]')
|
475
|
+
end
|
476
|
+
|
477
|
+
it 'can drop multiple files' do
|
478
|
+
@session.visit('/with_js')
|
479
|
+
target = @session.find('//div[@id="drop_html5"]')
|
480
|
+
target.drop(
|
481
|
+
with_os_path_separators(File.expand_path('../fixtures/capybara.jpg', File.dirname(__FILE__))),
|
482
|
+
with_os_path_separators(File.expand_path('../fixtures/test_file.txt', File.dirname(__FILE__)))
|
483
|
+
)
|
484
|
+
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped file: capybara.jpg")]')
|
485
|
+
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped file: test_file.txt")]')
|
486
|
+
end
|
487
|
+
|
488
|
+
it 'can drop strings' do
|
489
|
+
@session.visit('/with_js')
|
490
|
+
target = @session.find('//div[@id="drop_html5"]')
|
491
|
+
target.drop('text/plain' => 'Some dropped text')
|
492
|
+
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain Some dropped text")]')
|
493
|
+
end
|
494
|
+
|
495
|
+
it 'can drop multiple strings' do
|
496
|
+
@session.visit('/with_js')
|
497
|
+
target = @session.find('//div[@id="drop_html5"]')
|
498
|
+
target.drop('text/plain' => 'Some dropped text', 'text/url' => 'http://www.google.com')
|
499
|
+
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain Some dropped text")]')
|
500
|
+
expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/url http://www.google.com")]')
|
501
|
+
end
|
417
502
|
end
|
418
503
|
|
419
504
|
describe '#hover', requires: [:hover] do
|
@@ -124,6 +124,10 @@ module Capybara
|
|
124
124
|
def be_an_invalid_element_error(session)
|
125
125
|
satisfy { |error| session.driver.invalid_element_errors.any? { |e| error.is_a? e } }
|
126
126
|
end
|
127
|
+
|
128
|
+
def with_os_path_separators(path)
|
129
|
+
Gem.win_platform? ? path.to_s.tr('/', '\\') : path.to_s
|
130
|
+
end
|
127
131
|
end
|
128
132
|
end
|
129
133
|
|
data/lib/capybara/version.rb
CHANGED
@@ -64,6 +64,8 @@ Capybara::SpecHelper.run_specs TestSessions::SeleniumFirefox, 'selenium', capyba
|
|
64
64
|
pending "Geckodriver doesn't provide a way to remove cookies outside the current domain"
|
65
65
|
when 'Capybara::Session selenium #attach_file with a block can upload by clicking the file input'
|
66
66
|
pending "Geckodriver doesn't allow clicking on file inputs"
|
67
|
+
when /drag_to.*HTML5/
|
68
|
+
pending "Firefox < 62 doesn't support a DataTransfer constuctor" if firefox_lt?(62.0, @session)
|
67
69
|
end
|
68
70
|
end
|
69
71
|
|
data/spec/selenium_spec_ie.rb
CHANGED
@@ -6,13 +6,13 @@ require 'shared_selenium_session'
|
|
6
6
|
require 'shared_selenium_node'
|
7
7
|
require 'rspec/shared_spec_matchers'
|
8
8
|
|
9
|
-
if ENV['CI']
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
end
|
9
|
+
# if ENV['CI']
|
10
|
+
# if ::Selenium::WebDriver::Service.respond_to? :driver_path=
|
11
|
+
# ::Selenium::WebDriver::IE::Service
|
12
|
+
# else
|
13
|
+
# ::Selenium::WebDriver::IE
|
14
|
+
# end.driver_path = 'C:\Tools\WebDriver\IEDriverServer.exe'
|
15
|
+
# end
|
16
16
|
|
17
17
|
def selenium_host
|
18
18
|
ENV.fetch('SELENIUM_HOST', '192.168.56.102')
|
@@ -110,6 +110,12 @@ Capybara::SpecHelper.run_specs TestSessions::SeleniumIE, 'selenium', capybara_sk
|
|
110
110
|
pending 'IE treats blank href as a parent request (against HTML spec)'
|
111
111
|
when /#attach_file with a block/
|
112
112
|
skip 'Hangs IE testing for unknown reason'
|
113
|
+
when /drag_to.*HTML5/
|
114
|
+
pending "IE doesn't support a DataTransfer constuctor"
|
115
|
+
when /template elements should not be visible/
|
116
|
+
skip "IE doesn't support template elements"
|
117
|
+
when /Element#drop/
|
118
|
+
pending "IE doesn't support DataTransfer constructor"
|
113
119
|
end
|
114
120
|
end
|
115
121
|
|