capybara 3.39.2 → 3.40.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 +22 -0
- data/README.md +173 -29
- data/lib/capybara/helpers.rb +1 -1
- data/lib/capybara/minitest/spec.rb +16 -4
- data/lib/capybara/minitest.rb +14 -1
- data/lib/capybara/node/matchers.rb +25 -0
- data/lib/capybara/node/whitespace_normalizer.rb +5 -5
- data/lib/capybara/queries/selector_query.rb +2 -1
- data/lib/capybara/rack_test/browser.rb +3 -2
- data/lib/capybara/rack_test/node.rb +5 -12
- data/lib/capybara/registration_container.rb +2 -2
- data/lib/capybara/registrations/drivers.rb +1 -1
- data/lib/capybara/registrations/servers.rb +8 -7
- data/lib/capybara/result.rb +2 -2
- data/lib/capybara/rspec/matchers/have_selector.rb +4 -12
- data/lib/capybara/rspec/matchers.rb +7 -2
- data/lib/capybara/selector/css.rb +5 -5
- data/lib/capybara/selector/definition/table.rb +1 -1
- data/lib/capybara/selector/definition/table_row.rb +2 -2
- data/lib/capybara/selector.rb +251 -0
- data/lib/capybara/selenium/driver.rb +11 -51
- data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +1 -6
- data/lib/capybara/selenium/node.rb +4 -31
- data/lib/capybara/selenium/nodes/chrome_node.rb +3 -19
- data/lib/capybara/selenium/nodes/edge_node.rb +0 -16
- data/lib/capybara/server/animation_disabler.rb +1 -1
- data/lib/capybara/server.rb +1 -1
- data/lib/capybara/session.rb +3 -2
- data/lib/capybara/spec/session/all_spec.rb +1 -1
- data/lib/capybara/spec/session/click_link_spec.rb +1 -1
- data/lib/capybara/spec/session/find_spec.rb +8 -0
- data/lib/capybara/spec/session/has_element_spec.rb +47 -0
- data/lib/capybara/spec/session/has_table_spec.rb +13 -2
- data/lib/capybara/spec/session/node_spec.rb +6 -0
- data/lib/capybara/spec/session/node_wrapper_spec.rb +1 -1
- data/lib/capybara/spec/session/uncheck_spec.rb +1 -1
- data/lib/capybara/spec/spec_helper.rb +2 -2
- data/lib/capybara/spec/test_app.rb +1 -1
- data/lib/capybara/spec/views/form.erb +5 -0
- data/lib/capybara/spec/views/with_html.erb +2 -0
- data/lib/capybara/version.rb +1 -1
- data/lib/capybara.rb +7 -6
- data/spec/minitest_spec.rb +8 -1
- data/spec/result_spec.rb +9 -0
- data/spec/rspec/shared_spec_matchers.rb +26 -2
- data/spec/rspec_spec.rb +1 -1
- data/spec/sauce_spec_chrome.rb +1 -1
- data/spec/selector_spec.rb +1 -1
- data/spec/selenium_spec_chrome.rb +7 -5
- data/spec/selenium_spec_chrome_remote.rb +0 -5
- data/spec/selenium_spec_edge.rb +11 -4
- data/spec/selenium_spec_firefox.rb +4 -3
- data/spec/selenium_spec_firefox_remote.rb +2 -3
- data/spec/server_spec.rb +12 -0
- data/spec/shared_selenium_session.rb +0 -2
- data/spec/spec_helper.rb +2 -2
- metadata +25 -32
- data/lib/capybara/selenium/logger_suppressor.rb +0 -44
- data/lib/capybara/selenium/patches/action_pauser.rb +0 -26
@@ -77,12 +77,17 @@ module Capybara
|
|
77
77
|
#
|
78
78
|
# @see Capybara::Node::Matchers#matches_css?
|
79
79
|
|
80
|
-
%i[link button field select table].each do |selector|
|
80
|
+
%i[link button field select table element].each do |selector|
|
81
81
|
define_method "have_#{selector}" do |locator = nil, **options, &optional_filter_block|
|
82
82
|
Matchers::HaveSelector.new(selector, locator, **options, &optional_filter_block)
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
86
|
+
# @!method have_element(locator = nil, **options, &optional_filter_block)
|
87
|
+
# RSpec matcher for elements.
|
88
|
+
#
|
89
|
+
# @see Capybara::Node::Matchers#has_element?
|
90
|
+
|
86
91
|
# @!method have_link(locator = nil, **options, &optional_filter_block)
|
87
92
|
# RSpec matcher for links.
|
88
93
|
#
|
@@ -161,7 +166,7 @@ module Capybara
|
|
161
166
|
|
162
167
|
%w[selector css xpath text title current_path link button
|
163
168
|
field checked_field unchecked_field select table
|
164
|
-
sibling ancestor].each do |matcher_type|
|
169
|
+
sibling ancestor element].each do |matcher_type|
|
165
170
|
define_method "have_no_#{matcher_type}" do |*args, **kw_args, &optional_filter_block|
|
166
171
|
Matchers::NegatedMatcher.new(send("have_#{matcher_type}", *args, **kw_args, &optional_filter_block))
|
167
172
|
end
|
@@ -23,11 +23,11 @@ module Capybara
|
|
23
23
|
end
|
24
24
|
|
25
25
|
S = '\u{80}-\u{D7FF}\u{E000}-\u{FFFD}\u{10000}-\u{10FFFF}'
|
26
|
-
H = /[0-9a-fA-F]
|
27
|
-
UNICODE = /\\#{H}{1,6}[ \t\r\n\f]
|
28
|
-
NONASCII = /[#{S}]
|
29
|
-
ESCAPE = /#{UNICODE}|\\[ -~#{S}]
|
30
|
-
NMSTART = /[_a-zA-Z]|#{NONASCII}|#{ESCAPE}
|
26
|
+
H = /[0-9a-fA-F]/
|
27
|
+
UNICODE = /\\#{H}{1,6}[ \t\r\n\f]?/
|
28
|
+
NONASCII = /[#{S}]/
|
29
|
+
ESCAPE = /#{UNICODE}|\\[ -~#{S}]/
|
30
|
+
NMSTART = /[_a-zA-Z]|#{NONASCII}|#{ESCAPE}/
|
31
31
|
|
32
32
|
class Splitter
|
33
33
|
def split(css)
|
@@ -93,7 +93,7 @@ Capybara.add_selector(:table, locator_type: [String, Symbol]) do
|
|
93
93
|
row.map do |header, cell|
|
94
94
|
header_xp = XPath.ancestor(:table)[1].descendant(:tr)[1].descendant(:th)[XPath.string.n.is(header)]
|
95
95
|
XPath.descendant(:td)[
|
96
|
-
XPath.string.n.is(cell) & XPath.position.equals(header_xp.preceding_sibling.count.plus(1))
|
96
|
+
XPath.string.n.is(cell) & header_xp.boolean & XPath.position.equals(header_xp.preceding_sibling.count.plus(1))
|
97
97
|
]
|
98
98
|
end.reduce(:&)
|
99
99
|
end
|
@@ -7,14 +7,14 @@ Capybara.add_selector(:table_row, locator_type: [Array, Hash]) do
|
|
7
7
|
locator.reduce(xpath) do |xp, (header, cell)|
|
8
8
|
header_xp = XPath.ancestor(:table)[1].descendant(:tr)[1].descendant(:th)[XPath.string.n.is(header)]
|
9
9
|
cell_xp = XPath.descendant(:td)[
|
10
|
-
XPath.string.n.is(cell) & XPath.position.equals(header_xp.preceding_sibling.count.plus(1))
|
10
|
+
XPath.string.n.is(cell) & header_xp.boolean & XPath.position.equals(header_xp.preceding_sibling.count.plus(1))
|
11
11
|
]
|
12
12
|
xp.where(cell_xp)
|
13
13
|
end
|
14
14
|
else
|
15
15
|
initial_td = XPath.descendant(:td)[XPath.string.n.is(locator.shift)]
|
16
16
|
tds = locator.reverse.map { |cell| XPath.following_sibling(:td)[XPath.string.n.is(cell)] }
|
17
|
-
.reduce { |xp, cell|
|
17
|
+
.reduce { |xp, cell| cell.where(xp) }
|
18
18
|
xpath[initial_td[tds]]
|
19
19
|
end
|
20
20
|
end
|
data/lib/capybara/selector.rb
CHANGED
@@ -21,12 +21,30 @@ require 'capybara/selector/definition'
|
|
21
21
|
# * **:xpath** - Select elements by XPath expression
|
22
22
|
# * Locator: An XPath expression
|
23
23
|
#
|
24
|
+
# ```ruby
|
25
|
+
# page.html # => '<input>'
|
26
|
+
#
|
27
|
+
# page.find :xpath, './/input'
|
28
|
+
# ```
|
29
|
+
#
|
24
30
|
# * **:css** - Select elements by CSS selector
|
25
31
|
# * Locator: A CSS selector
|
26
32
|
#
|
33
|
+
# ```ruby
|
34
|
+
# page.html # => '<input>'
|
35
|
+
#
|
36
|
+
# page.find :css, 'input'
|
37
|
+
# ```
|
38
|
+
#
|
27
39
|
# * **:id** - Select element by id
|
28
40
|
# * Locator: (String, Regexp, XPath::Expression) The id of the element to match
|
29
41
|
#
|
42
|
+
# ```ruby
|
43
|
+
# page.html # => '<input id="field">'
|
44
|
+
#
|
45
|
+
# page.find :id, 'content'
|
46
|
+
# ```
|
47
|
+
#
|
30
48
|
# * **:field** - Select field elements (input [not of type submit, image, or hidden], textarea, select)
|
31
49
|
# * Locator: Matches against the id, {Capybara.configure test_id} attribute, name, placeholder, or
|
32
50
|
# associated label text
|
@@ -43,12 +61,30 @@ require 'capybara/selector/definition'
|
|
43
61
|
# * :valid (Boolean) - Match fields that are valid/invalid according to HTML5 form validation
|
44
62
|
# * :validation_message (String, Regexp) - Matches the elements current validationMessage
|
45
63
|
#
|
64
|
+
# ```ruby
|
65
|
+
# page.html # => '<label for="article_title">Title</label>
|
66
|
+
# <input id="article_title" name="article[title]" value="Hello world">'
|
67
|
+
#
|
68
|
+
# page.find :field, 'article_title'
|
69
|
+
# page.find :field, 'article[title]'
|
70
|
+
# page.find :field, 'Title'
|
71
|
+
# page.find :field, 'Title', type: 'text', with: 'Hello world'
|
72
|
+
# ```
|
73
|
+
#
|
46
74
|
# * **:fieldset** - Select fieldset elements
|
47
75
|
# * Locator: Matches id, {Capybara.configure test_id}, or contents of wrapped legend
|
48
76
|
# * Filters:
|
49
77
|
# * :legend (String) - Matches contents of wrapped legend
|
50
78
|
# * :disabled (Boolean) - Match disabled fieldset?
|
51
79
|
#
|
80
|
+
# ```ruby
|
81
|
+
# page.html # => '<fieldset disabled>
|
82
|
+
# <legend>Fields (disabled)</legend>
|
83
|
+
# </fieldset>'
|
84
|
+
#
|
85
|
+
# page.find :fieldset, 'Fields (disabled)', disabled: true
|
86
|
+
# ```
|
87
|
+
#
|
52
88
|
# * **:link** - Find links (`<a>` elements with an href attribute)
|
53
89
|
# * Locator: Matches the id, {Capybara.configure test_id}, or title attributes, or the string content of the link,
|
54
90
|
# or the alt attribute of a contained img element. By default this selector requires a link to have an href attribute.
|
@@ -57,6 +93,17 @@ require 'capybara/selector/definition'
|
|
57
93
|
# * :alt (String) - Matches the alt attribute of a contained img element
|
58
94
|
# * :href (String, Regexp, nil, false) - Matches the normalized href of the link, if nil will find `<a>` elements with no href attribute, if false ignores href presence
|
59
95
|
#
|
96
|
+
# ```ruby
|
97
|
+
# page.html # => '<a href="/">Home</a>'
|
98
|
+
#
|
99
|
+
# page.find :link, 'Home', href: '/'
|
100
|
+
#
|
101
|
+
# page.html # => '<a href="/"><img src="/logo.png" alt="The logo"></a>'
|
102
|
+
#
|
103
|
+
# page.find :link, 'The logo', href: '/'
|
104
|
+
# page.find :link, alt: 'The logo', href: '/'
|
105
|
+
# ```
|
106
|
+
#
|
60
107
|
# * **:button** - Find buttons ( input [of type submit, reset, image, button] or button elements )
|
61
108
|
# * Locator: Matches the id, {Capybara.configure test_id} attribute, name, value, or title attributes, string content of a button, or the alt attribute of an image type button or of a descendant image of a button
|
62
109
|
# * Filters:
|
@@ -66,11 +113,31 @@ require 'capybara/selector/definition'
|
|
66
113
|
# * :type (String) - Matches the type attribute
|
67
114
|
# * :disabled (Boolean, :all) - Match disabled buttons (Default: false)
|
68
115
|
#
|
116
|
+
# ```ruby
|
117
|
+
# page.html # => '<button>Submit</button>'
|
118
|
+
#
|
119
|
+
# page.find :button, 'Submit'
|
120
|
+
#
|
121
|
+
# page.html # => '<button name="article[state]" value="draft">Save as draft</button>'
|
122
|
+
#
|
123
|
+
# page.find :button, 'Save as draft', name: 'article[state]', value: 'draft'
|
124
|
+
# ```
|
125
|
+
#
|
69
126
|
# * **:link_or_button** - Find links or buttons
|
70
127
|
# * Locator: See :link and :button selectors
|
71
128
|
# * Filters:
|
72
129
|
# * :disabled (Boolean, :all) - Match disabled buttons? (Default: false)
|
73
130
|
#
|
131
|
+
# ```ruby
|
132
|
+
# page.html # => '<a href="/">Home</a>'
|
133
|
+
#
|
134
|
+
# page.find :link_or_button, 'Home'
|
135
|
+
#
|
136
|
+
# page.html # => '<button>Submit</button>'
|
137
|
+
#
|
138
|
+
# page.find :link_or_button, 'Submit'
|
139
|
+
# ```
|
140
|
+
#
|
74
141
|
# * **:fillable_field** - Find text fillable fields ( textarea, input [not of type submit, image, radio, checkbox, hidden, file] )
|
75
142
|
# * Locator: Matches against the id, {Capybara.configure test_id} attribute, name, placeholder, or associated label text
|
76
143
|
# * Filters:
|
@@ -83,6 +150,16 @@ require 'capybara/selector/definition'
|
|
83
150
|
# * :valid (Boolean) - Match fields that are valid/invalid according to HTML5 form validation
|
84
151
|
# * :validation_message (String, Regexp) - Matches the elements current validationMessage
|
85
152
|
#
|
153
|
+
# ```ruby
|
154
|
+
# page.html # => '<label for="article_body">Body</label>
|
155
|
+
# <textarea id="article_body" name="article[body]"></textarea>'
|
156
|
+
#
|
157
|
+
# page.find :fillable_field, 'article_body'
|
158
|
+
# page.find :fillable_field, 'article[body]'
|
159
|
+
# page.find :fillable_field, 'Body'
|
160
|
+
# page.find :field, 'Body', type: 'textarea'
|
161
|
+
# ```
|
162
|
+
#
|
86
163
|
# * **:radio_button** - Find radio buttons
|
87
164
|
# * Locator: Match id, {Capybara.configure test_id} attribute, name, or associated label text
|
88
165
|
# * Filters:
|
@@ -93,6 +170,18 @@ require 'capybara/selector/definition'
|
|
93
170
|
# * :option (String, Regexp) - Match the current value
|
94
171
|
# * :with - Alias of :option
|
95
172
|
#
|
173
|
+
# ```ruby
|
174
|
+
# page.html # => '<input type="radio" id="article_state_published" name="article[state]" value="published" checked>
|
175
|
+
# <label for="article_state_published">Published</label>
|
176
|
+
# <input type="radio" id="article_state_draft" name="article[state]" value="draft">
|
177
|
+
# <label for="article_state_draft">Draft</label>'
|
178
|
+
#
|
179
|
+
# page.find :radio_button, 'article_state_published'
|
180
|
+
# page.find :radio_button, 'article[state]', option: 'published'
|
181
|
+
# page.find :radio_button, 'Published', checked: true
|
182
|
+
# page.find :radio_button, 'Draft', unchecked: true
|
183
|
+
# ```
|
184
|
+
#
|
96
185
|
# * **:checkbox** - Find checkboxes
|
97
186
|
# * Locator: Match id, {Capybara.configure test_id} attribute, name, or associated label text
|
98
187
|
# * Filters:
|
@@ -103,6 +192,15 @@ require 'capybara/selector/definition'
|
|
103
192
|
# * :with (String, Regexp) - Match the current value
|
104
193
|
# * :option - Alias of :with
|
105
194
|
#
|
195
|
+
# ```ruby
|
196
|
+
# page.html # => '<input type="checkbox" id="registration_terms" name="registration[terms]" value="true">
|
197
|
+
# <label for="registration_terms">I agree to terms and conditions</label>'
|
198
|
+
#
|
199
|
+
# page.find :checkbox, 'registration_terms'
|
200
|
+
# page.find :checkbox, 'registration[terms]'
|
201
|
+
# page.find :checkbox, 'I agree to terms and conditions', unchecked: true
|
202
|
+
# ```
|
203
|
+
#
|
106
204
|
# * **:select** - Find select elements
|
107
205
|
# * Locator: Match id, {Capybara.configure test_id} attribute, name, placeholder, or associated label text
|
108
206
|
# * Filters:
|
@@ -117,12 +215,40 @@ require 'capybara/selector/definition'
|
|
117
215
|
# * :selected (String, Array<String>) - Match the selection(s)
|
118
216
|
# * :with_selected (String, Array<String>) - Partial match the selection(s)
|
119
217
|
#
|
218
|
+
# ```ruby
|
219
|
+
# page.html # => '<label for="article_category">Category</label>
|
220
|
+
# <select id="article_category" name="article[category]">
|
221
|
+
# <option value="General" checked></option>
|
222
|
+
# <option value="Other"></option>
|
223
|
+
# </select>'
|
224
|
+
#
|
225
|
+
# page.find :select, 'article_category'
|
226
|
+
# page.find :select, 'article[category]'
|
227
|
+
# page.find :select, 'Category'
|
228
|
+
# page.find :select, 'Category', selected: 'General'
|
229
|
+
# page.find :select, with_options: ['General']
|
230
|
+
# page.find :select, with_options: ['Other']
|
231
|
+
# page.find :select, options: ['General', 'Other']
|
232
|
+
# page.find :select, options: ['General'] # => raises Capybara::ElementNotFound
|
233
|
+
# ```
|
234
|
+
#
|
120
235
|
# * **:option** - Find option elements
|
121
236
|
# * Locator: Match text of option
|
122
237
|
# * Filters:
|
123
238
|
# * :disabled (Boolean) - Match disabled option
|
124
239
|
# * :selected (Boolean) - Match selected option
|
125
240
|
#
|
241
|
+
# ```ruby
|
242
|
+
# page.html # => '<option value="General" checked></option>
|
243
|
+
# <option value="Disabled" disabled></option>
|
244
|
+
# <option value="Other"></option>'
|
245
|
+
#
|
246
|
+
# page.find :option, 'General'
|
247
|
+
# page.find :option, 'General', selected: true
|
248
|
+
# page.find :option, 'Disabled', disabled: true
|
249
|
+
# page.find :option, 'Other', selected: false
|
250
|
+
# ```
|
251
|
+
#
|
126
252
|
# * **:datalist_input** - Find input field with datalist completion
|
127
253
|
# * Locator: Matches against the id, {Capybara.configure test_id} attribute, name,
|
128
254
|
# placeholder, or associated label text
|
@@ -133,11 +259,42 @@ require 'capybara/selector/definition'
|
|
133
259
|
# * :options (Array<String>) - Exact match options
|
134
260
|
# * :with_options (Array<String>) - Partial match options
|
135
261
|
#
|
262
|
+
# ```ruby
|
263
|
+
# page.html # => '<label for="ice_cream_flavor">Flavor</label>
|
264
|
+
# <input list="ice_cream_flavors" id="ice_cream_flavor" name="ice_cream[flavor]">
|
265
|
+
# <datalist id="ice_cream_flavors">
|
266
|
+
# <option value="Chocolate"></option>
|
267
|
+
# <option value="Strawberry"></option>
|
268
|
+
# <option value="Vanilla"></option>
|
269
|
+
# </datalist>'
|
270
|
+
#
|
271
|
+
# page.find :datalist_input, 'ice_cream_flavor'
|
272
|
+
# page.find :datalist_input, 'ice_cream[flavor]'
|
273
|
+
# page.find :datalist_input, 'Flavor'
|
274
|
+
# page.find :datalist_input, with_options: ['Chocolate', 'Strawberry']
|
275
|
+
# page.find :datalist_input, options: ['Chocolate', 'Strawberry', 'Vanilla']
|
276
|
+
# page.find :datalist_input, options: ['Chocolate'] # => raises Capybara::ElementNotFound
|
277
|
+
# ```
|
278
|
+
#
|
136
279
|
# * **:datalist_option** - Find datalist option
|
137
280
|
# * Locator: Match text or value of option
|
138
281
|
# * Filters:
|
139
282
|
# * :disabled (Boolean) - Match disabled option
|
140
283
|
#
|
284
|
+
# ```ruby
|
285
|
+
# page.html # => '<datalist>
|
286
|
+
# <option value="Chocolate"></option>
|
287
|
+
# <option value="Strawberry"></option>
|
288
|
+
# <option value="Vanilla"></option>
|
289
|
+
# <option value="Forbidden" disabled></option>
|
290
|
+
# </datalist>'
|
291
|
+
#
|
292
|
+
# page.find :datalist_option, 'Chocolate'
|
293
|
+
# page.find :datalist_option, 'Strawberry'
|
294
|
+
# page.find :datalist_option, 'Vanilla'
|
295
|
+
# page.find :datalist_option, 'Forbidden', disabled: true
|
296
|
+
# ```
|
297
|
+
#
|
141
298
|
# * **:file_field** - Find file input elements
|
142
299
|
# * Locator: Match id, {Capybara.configure test_id} attribute, name, or associated label text
|
143
300
|
# * Filters:
|
@@ -145,11 +302,31 @@ require 'capybara/selector/definition'
|
|
145
302
|
# * :disabled (Boolean, :all) - Match disabled field? (Default: false)
|
146
303
|
# * :multiple (Boolean) - Match field that accepts multiple values
|
147
304
|
#
|
305
|
+
# ```ruby
|
306
|
+
# page.html # => '<label for="article_banner_image">Banner Image</label>
|
307
|
+
# <input type="file" id="article_banner_image" name="article[banner_image]">'
|
308
|
+
#
|
309
|
+
# page.find :file_field, 'article_banner_image'
|
310
|
+
# page.find :file_field, 'article[banner_image]'
|
311
|
+
# page.find :file_field, 'Banner Image'
|
312
|
+
# page.find :file_field, 'Banner Image', name: 'article[banner_image]'
|
313
|
+
# page.find :field, 'Banner Image', type: 'file'
|
314
|
+
# ```
|
315
|
+
#
|
148
316
|
# * **:label** - Find label elements
|
149
317
|
# * Locator: Match id, {Capybara.configure test_id}, or text contents
|
150
318
|
# * Filters:
|
151
319
|
# * :for (Element, String, Regexp) - The element or id of the element associated with the label
|
152
320
|
#
|
321
|
+
# ```ruby
|
322
|
+
# page.html # => '<label for="article_title">Title</label>
|
323
|
+
# <input id="article_title" name="article[title]">'
|
324
|
+
#
|
325
|
+
# page.find :label, 'Title'
|
326
|
+
# page.find :label, 'Title', for: 'article_title'
|
327
|
+
# page.find :label, 'Title', for: page.find('article[title]')
|
328
|
+
# ```
|
329
|
+
#
|
153
330
|
# * **:table** - Find table elements
|
154
331
|
# * Locator: id, {Capybara.configure test_id}, or caption text of table
|
155
332
|
# * Filters:
|
@@ -159,19 +336,93 @@ require 'capybara/selector/definition'
|
|
159
336
|
# * :with_cols (Array<Array<String>>, Array<Hash<String, String>>) - Partial match `<td>` data - visibility of `<td>` elements is not considered
|
160
337
|
# * :cols (Array<Array<String>>) - Match all `<td>`s - visibility of `<td>` elements is not considered
|
161
338
|
#
|
339
|
+
# ```ruby
|
340
|
+
# page.html # => '<table>
|
341
|
+
# <caption>A table</caption>
|
342
|
+
# <tr>
|
343
|
+
# <th>A</th>
|
344
|
+
# <th>B</th>
|
345
|
+
# </tr>
|
346
|
+
# <tr>
|
347
|
+
# <td>1</td>
|
348
|
+
# <td>2</td>
|
349
|
+
# </tr>
|
350
|
+
# <tr>
|
351
|
+
# <td>3</td>
|
352
|
+
# <td>4</td>
|
353
|
+
# </tr>
|
354
|
+
# </table>'
|
355
|
+
#
|
356
|
+
# page.find :table, 'A table'
|
357
|
+
# page.find :table, with_rows: [
|
358
|
+
# { 'A' => '1', 'B' => '2' },
|
359
|
+
# { 'A' => '3', 'B' => '4' },
|
360
|
+
# ]
|
361
|
+
# page.find :table, with_rows: [
|
362
|
+
# ['1', '2'],
|
363
|
+
# ['3', '4'],
|
364
|
+
# ]
|
365
|
+
# page.find :table, rows: [
|
366
|
+
# { 'A' => '1', 'B' => '2' },
|
367
|
+
# { 'A' => '3', 'B' => '4' },
|
368
|
+
# ]
|
369
|
+
# page.find :table, rows: [
|
370
|
+
# ['1', '2'],
|
371
|
+
# ['3', '4'],
|
372
|
+
# ]
|
373
|
+
# page.find :table, rows: [ ['1', '2'] ] # => raises Capybara::ElementNotFound
|
374
|
+
# ```
|
375
|
+
#
|
162
376
|
# * **:table_row** - Find table row
|
163
377
|
# * Locator: Array<String>, Hash<String, String> table row `<td>` contents - visibility of `<td>` elements is not considered
|
164
378
|
#
|
379
|
+
# ```ruby
|
380
|
+
# page.html # => '<table>
|
381
|
+
# <tr>
|
382
|
+
# <th>A</th>
|
383
|
+
# <th>B</th>
|
384
|
+
# </tr>
|
385
|
+
# <tr>
|
386
|
+
# <td>1</td>
|
387
|
+
# <td>2</td>
|
388
|
+
# </tr>
|
389
|
+
# <tr>
|
390
|
+
# <td>3</td>
|
391
|
+
# <td>4</td>
|
392
|
+
# </tr>
|
393
|
+
# </table>'
|
394
|
+
#
|
395
|
+
# page.find :table_row, 'A' => '1', 'B' => '2'
|
396
|
+
# page.find :table_row, 'A' => '3', 'B' => '4'
|
397
|
+
# ```
|
398
|
+
#
|
165
399
|
# * **:frame** - Find frame/iframe elements
|
166
400
|
# * Locator: Match id, {Capybara.configure test_id} attribute, or name
|
167
401
|
# * Filters:
|
168
402
|
# * :name (String) - Match name attribute
|
169
403
|
#
|
404
|
+
# ```ruby
|
405
|
+
# page.html # => '<iframe id="embed_frame" name="embed" src="https://example.com/embed"></iframe>'
|
406
|
+
#
|
407
|
+
# page.find :frame, 'embed_frame'
|
408
|
+
# page.find :frame, 'embed'
|
409
|
+
# page.find :frame, name: 'embed'
|
410
|
+
# ```
|
411
|
+
#
|
170
412
|
# * **:element**
|
171
413
|
# * Locator: Type of element ('div', 'a', etc) - if not specified defaults to '*'
|
172
414
|
# * Filters:
|
173
415
|
# * :\<any> (String, Regexp) - Match on any specified element attribute
|
174
416
|
#
|
417
|
+
# ```ruby
|
418
|
+
# page.html # => '<button type="button" role="menuitemcheckbox" aria-checked="true">Check me</button>
|
419
|
+
#
|
420
|
+
# page.find :element, 'button'
|
421
|
+
# page.find :element, type: 'button', text: 'Check me'
|
422
|
+
# page.find :element, role: 'menuitemcheckbox'
|
423
|
+
# page.find :element, role: /checkbox/, 'aria-checked': 'true'
|
424
|
+
# ```
|
425
|
+
#
|
175
426
|
class Capybara::Selector; end # rubocop:disable Lint/EmptyClass
|
176
427
|
|
177
428
|
Capybara::Selector::FilterSet.add(:_field) do
|
@@ -12,7 +12,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
12
12
|
clear_session_storage: nil
|
13
13
|
}.freeze
|
14
14
|
SPECIAL_OPTIONS = %i[browser clear_local_storage clear_session_storage timeout native_displayed].freeze
|
15
|
-
CAPS_VERSION = Gem::Requirement.new('
|
15
|
+
CAPS_VERSION = Gem::Requirement.new('< 4.8.0')
|
16
16
|
|
17
17
|
attr_reader :app, :options
|
18
18
|
|
@@ -21,10 +21,8 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
21
21
|
|
22
22
|
def load_selenium
|
23
23
|
require 'selenium-webdriver'
|
24
|
-
require 'capybara/selenium/logger_suppressor'
|
25
24
|
require 'capybara/selenium/patches/atoms'
|
26
25
|
require 'capybara/selenium/patches/is_displayed'
|
27
|
-
require 'capybara/selenium/patches/action_pauser'
|
28
26
|
|
29
27
|
# Look up the version of `selenium-webdriver` to
|
30
28
|
# see if it's a version we support.
|
@@ -43,8 +41,8 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
43
41
|
Gem::Version.new(Selenium::WebDriver::VERSION)
|
44
42
|
end
|
45
43
|
|
46
|
-
unless Gem::Requirement.new('>=
|
47
|
-
warn "Warning: You're using an unsupported version of selenium-webdriver, please upgrade."
|
44
|
+
unless Gem::Requirement.new('>= 4.8').satisfied_by? @selenium_webdriver_version
|
45
|
+
warn "Warning: You're using an unsupported version of selenium-webdriver, please upgrade to 4.8+."
|
48
46
|
end
|
49
47
|
|
50
48
|
@selenium_webdriver_version
|
@@ -72,16 +70,9 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
72
70
|
::Capybara::Selenium::PersistentClient.new
|
73
71
|
end
|
74
72
|
end
|
75
|
-
processed_options = options.
|
76
|
-
|
77
|
-
@browser =
|
78
|
-
RUBY_VERSION >= '3.0' &&
|
79
|
-
Capybara::Selenium::Driver.selenium_webdriver_version <= Gem::Version.new('4.0.0.alpha1')
|
80
|
-
# selenium-webdriver 3.x doesn't correctly pass options through for Firefox with Ruby 3 so workaround that
|
81
|
-
Selenium::WebDriver::Firefox::Driver.new(**processed_options)
|
82
|
-
else
|
83
|
-
Selenium::WebDriver.for(options[:browser], processed_options)
|
84
|
-
end
|
73
|
+
processed_options = options.except(*SPECIAL_OPTIONS)
|
74
|
+
|
75
|
+
@browser = Selenium::WebDriver.for(options[:browser], processed_options)
|
85
76
|
|
86
77
|
specialize_driver
|
87
78
|
setup_exit_handler
|
@@ -157,8 +148,8 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
157
148
|
native_active_element.send_keys(*args)
|
158
149
|
end
|
159
150
|
|
160
|
-
def save_screenshot(path, **
|
161
|
-
browser.save_screenshot(path)
|
151
|
+
def save_screenshot(path, **options)
|
152
|
+
browser.save_screenshot(path, **options)
|
162
153
|
end
|
163
154
|
|
164
155
|
def reset!
|
@@ -313,16 +304,6 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
313
304
|
::Selenium::WebDriver::Error::NoSuchElementError, # IE
|
314
305
|
::Selenium::WebDriver::Error::InvalidArgumentError # IE
|
315
306
|
].tap do |errors|
|
316
|
-
unless selenium_4?
|
317
|
-
::Selenium::WebDriver.logger.suppress_deprecations do
|
318
|
-
errors.push(
|
319
|
-
::Selenium::WebDriver::Error::UnhandledError,
|
320
|
-
::Selenium::WebDriver::Error::ElementNotVisibleError,
|
321
|
-
::Selenium::WebDriver::Error::InvalidElementStateError,
|
322
|
-
::Selenium::WebDriver::Error::ElementNotSelectableError
|
323
|
-
)
|
324
|
-
end
|
325
|
-
end
|
326
307
|
if defined?(::Selenium::WebDriver::Error::DetachedShadowRootError)
|
327
308
|
errors.push(::Selenium::WebDriver::Error::DetachedShadowRootError)
|
328
309
|
end
|
@@ -335,10 +316,6 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
335
316
|
|
336
317
|
private
|
337
318
|
|
338
|
-
def selenium_4?
|
339
|
-
defined?(Selenium::WebDriver::VERSION) && (Selenium::WebDriver::VERSION.to_f >= 4)
|
340
|
-
end
|
341
|
-
|
342
319
|
def native_args(args)
|
343
320
|
args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg }
|
344
321
|
end
|
@@ -361,10 +338,7 @@ private
|
|
361
338
|
end
|
362
339
|
|
363
340
|
def unhandled_alert_errors
|
364
|
-
@unhandled_alert_errors ||=
|
365
|
-
[Selenium::WebDriver::Error::UnexpectedAlertOpenError],
|
366
|
-
'UnhandledAlertError'
|
367
|
-
)
|
341
|
+
@unhandled_alert_errors ||= [Selenium::WebDriver::Error::UnexpectedAlertOpenError]
|
368
342
|
end
|
369
343
|
|
370
344
|
def delete_all_cookies
|
@@ -454,17 +428,7 @@ private
|
|
454
428
|
end
|
455
429
|
|
456
430
|
def find_modal_errors
|
457
|
-
@find_modal_errors ||=
|
458
|
-
end
|
459
|
-
|
460
|
-
def with_legacy_error(errors, legacy_error)
|
461
|
-
errors.tap do |errs|
|
462
|
-
unless selenium_4?
|
463
|
-
::Selenium::WebDriver.logger.suppress_deprecations do
|
464
|
-
errs << Selenium::WebDriver::Error.const_get(legacy_error)
|
465
|
-
end
|
466
|
-
end
|
467
|
-
end
|
431
|
+
@find_modal_errors ||= [Selenium::WebDriver::Error::TimeoutError]
|
468
432
|
end
|
469
433
|
|
470
434
|
def silenced_unknown_error_message?(msg)
|
@@ -476,16 +440,12 @@ private
|
|
476
440
|
end
|
477
441
|
|
478
442
|
def unwrap_script_result(arg)
|
479
|
-
# TODO: move into the case when we drop support for Selenium < 4.1
|
480
|
-
element_types = [Selenium::WebDriver::Element]
|
481
|
-
element_types.push(Selenium::WebDriver::ShadowRoot) if defined?(Selenium::WebDriver::ShadowRoot)
|
482
|
-
|
483
443
|
case arg
|
484
444
|
when Array
|
485
445
|
arg.map { |arr| unwrap_script_result(arr) }
|
486
446
|
when Hash
|
487
447
|
arg.transform_values! { |value| unwrap_script_result(value) }
|
488
|
-
when
|
448
|
+
when Selenium::WebDriver::Element, Selenium::WebDriver::ShadowRoot
|
489
449
|
build_node(arg)
|
490
450
|
else
|
491
451
|
arg
|
@@ -4,15 +4,10 @@ require 'capybara/selenium/nodes/firefox_node'
|
|
4
4
|
|
5
5
|
module Capybara::Selenium::Driver::FirefoxDriver
|
6
6
|
def self.extended(driver)
|
7
|
-
driver.extend Capybara::Selenium::Driver::W3CFirefoxDriver
|
7
|
+
driver.extend Capybara::Selenium::Driver::W3CFirefoxDriver
|
8
8
|
bridge = driver.send(:bridge)
|
9
9
|
bridge.extend Capybara::Selenium::IsDisplayed unless bridge.send(:commands, :is_element_displayed)
|
10
10
|
end
|
11
|
-
|
12
|
-
def self.w3c?(driver)
|
13
|
-
(defined?(Selenium::WebDriver::VERSION) && (Gem::Version.new(Selenium::WebDriver::VERSION) >= Gem::Version.new('4'))) ||
|
14
|
-
driver.browser.capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities)
|
15
|
-
end
|
16
11
|
end
|
17
12
|
|
18
13
|
module Capybara::Selenium::Driver::W3CFirefoxDriver
|
@@ -132,13 +132,11 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
132
132
|
target = click_options.coords? ? nil : native
|
133
133
|
if click_options.delay.zero?
|
134
134
|
action.context_click(target)
|
135
|
-
|
135
|
+
else
|
136
136
|
action.move_to(target) if target
|
137
137
|
action.pointer_down(:right).then do |act|
|
138
138
|
action_pause(act, click_options.delay)
|
139
139
|
end.pointer_up(:right)
|
140
|
-
else
|
141
|
-
raise ArgumentError, 'Delay is not supported when right clicking with legacy (non-w3c) selenium driver'
|
142
140
|
end
|
143
141
|
end
|
144
142
|
end
|
@@ -219,8 +217,6 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
219
217
|
end
|
220
218
|
|
221
219
|
def shadow_root
|
222
|
-
raise 'You must be using Selenium 4.1+ for shadow_root support' unless native.respond_to? :shadow_root
|
223
|
-
|
224
220
|
root = native.shadow_root
|
225
221
|
root && build_node(native.shadow_root)
|
226
222
|
end
|
@@ -416,14 +412,8 @@ private
|
|
416
412
|
actions = browser_action.tap do |acts|
|
417
413
|
if click_options.coords?
|
418
414
|
if click_options.center_offset?
|
419
|
-
|
420
|
-
|
421
|
-
else
|
422
|
-
::Selenium::WebDriver.logger.suppress_deprecations do
|
423
|
-
acts.move_to(native).move_by(*click_options.coords)
|
424
|
-
end
|
425
|
-
end
|
426
|
-
elsif Gem::Version.new(Selenium::WebDriver::VERSION) >= Gem::Version.new('4.3')
|
415
|
+
acts.move_to(native, *click_options.coords)
|
416
|
+
else
|
427
417
|
right_by, down_by = *click_options.coords
|
428
418
|
size = native.size
|
429
419
|
left_offset = (size[:width] / 2).to_i
|
@@ -431,10 +421,6 @@ private
|
|
431
421
|
left = -left_offset + right_by
|
432
422
|
top = -top_offset + down_by
|
433
423
|
acts.move_to(native, left, top)
|
434
|
-
else
|
435
|
-
::Selenium::WebDriver.logger.suppress_deprecations do
|
436
|
-
acts.move_to(native, *click_options.coords)
|
437
|
-
end
|
438
424
|
end
|
439
425
|
else
|
440
426
|
acts.move_to(native)
|
@@ -475,21 +461,8 @@ private
|
|
475
461
|
browser.capabilities
|
476
462
|
end
|
477
463
|
|
478
|
-
def w3c?
|
479
|
-
(defined?(Selenium::WebDriver::VERSION) && (Gem::Version.new(Selenium::WebDriver::VERSION) >= Gem::Version.new('4'))) ||
|
480
|
-
capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities)
|
481
|
-
end
|
482
|
-
|
483
464
|
def action_pause(action, duration)
|
484
|
-
|
485
|
-
if Gem::Version.new(Selenium::WebDriver::VERSION) >= Gem::Version.new('4.2')
|
486
|
-
action.pause(device: action.pointer_inputs.first, duration: duration)
|
487
|
-
else
|
488
|
-
action.pause(action.pointer_inputs.first, duration)
|
489
|
-
end
|
490
|
-
else
|
491
|
-
action.pause(duration)
|
492
|
-
end
|
465
|
+
action.pause(device: action.pointer_inputs.first, duration: duration)
|
493
466
|
end
|
494
467
|
|
495
468
|
def normalize_keys(keys)
|