capybara 3.35.0 → 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 +168 -5
- data/README.md +199 -39
- data/lib/capybara/config.rb +16 -4
- data/lib/capybara/driver/base.rb +4 -0
- data/lib/capybara/driver/node.rb +5 -1
- data/lib/capybara/dsl.rb +4 -10
- data/lib/capybara/helpers.rb +9 -14
- data/lib/capybara/minitest/spec.rb +18 -6
- data/lib/capybara/minitest.rb +14 -1
- data/lib/capybara/node/actions.rb +14 -9
- data/lib/capybara/node/base.rb +2 -1
- data/lib/capybara/node/document.rb +2 -2
- data/lib/capybara/node/element.rb +13 -2
- data/lib/capybara/node/finders.rb +11 -2
- data/lib/capybara/node/matchers.rb +25 -0
- data/lib/capybara/node/simple.rb +5 -1
- data/lib/capybara/node/whitespace_normalizer.rb +81 -0
- data/lib/capybara/queries/active_element_query.rb +18 -0
- data/lib/capybara/queries/ancestor_query.rb +2 -1
- data/lib/capybara/queries/base_query.rb +2 -2
- data/lib/capybara/queries/current_path_query.rb +1 -1
- data/lib/capybara/queries/selector_query.rb +40 -11
- data/lib/capybara/queries/sibling_query.rb +2 -1
- data/lib/capybara/queries/text_query.rb +1 -1
- data/lib/capybara/rack_test/browser.rb +64 -8
- data/lib/capybara/rack_test/driver.rb +4 -4
- data/lib/capybara/rack_test/form.rb +29 -7
- data/lib/capybara/rack_test/node.rb +32 -33
- data/lib/capybara/registration_container.rb +2 -5
- data/lib/capybara/registrations/drivers.rb +7 -7
- data/lib/capybara/registrations/servers.rb +37 -16
- data/lib/capybara/result.rb +2 -2
- data/lib/capybara/rspec/matcher_proxies.rb +6 -6
- data/lib/capybara/rspec/matchers/base.rb +8 -6
- data/lib/capybara/rspec/matchers/compound.rb +1 -1
- data/lib/capybara/rspec/matchers/have_selector.rb +9 -17
- data/lib/capybara/rspec/matchers.rb +21 -16
- data/lib/capybara/selector/builders/css_builder.rb +1 -1
- data/lib/capybara/selector/builders/xpath_builder.rb +1 -1
- data/lib/capybara/selector/css.rb +6 -6
- data/lib/capybara/selector/definition/button.rb +10 -5
- data/lib/capybara/selector/definition/checkbox.rb +1 -1
- data/lib/capybara/selector/definition/file_field.rb +1 -1
- data/lib/capybara/selector/definition/fillable_field.rb +1 -1
- data/lib/capybara/selector/definition/link.rb +2 -1
- data/lib/capybara/selector/definition/radio_button.rb +1 -1
- data/lib/capybara/selector/definition/table.rb +1 -1
- data/lib/capybara/selector/definition/table_row.rb +2 -2
- data/lib/capybara/selector/definition.rb +4 -2
- data/lib/capybara/selector/filter_set.rb +4 -7
- data/lib/capybara/selector/regexp_disassembler.rb +2 -5
- data/lib/capybara/selector/selector.rb +5 -1
- data/lib/capybara/selector.rb +252 -0
- data/lib/capybara/selenium/driver.rb +31 -54
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +1 -1
- data/lib/capybara/selenium/driver_specializations/edge_driver.rb +9 -5
- data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +2 -7
- data/lib/capybara/selenium/extensions/html5_drag.rb +5 -4
- data/lib/capybara/selenium/node.rb +60 -38
- data/lib/capybara/selenium/nodes/chrome_node.rb +4 -16
- data/lib/capybara/selenium/nodes/edge_node.rb +19 -13
- data/lib/capybara/selenium/nodes/firefox_node.rb +3 -3
- data/lib/capybara/selenium/nodes/safari_node.rb +4 -4
- data/lib/capybara/selenium/patches/atoms.rb +1 -1
- data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -1
- data/lib/capybara/server/animation_disabler.rb +40 -23
- data/lib/capybara/server/middleware.rb +1 -1
- data/lib/capybara/server.rb +1 -1
- data/lib/capybara/session/config.rb +4 -2
- data/lib/capybara/session.rb +34 -34
- data/lib/capybara/spec/public/test.js +4 -0
- data/lib/capybara/spec/session/active_element_spec.rb +31 -0
- data/lib/capybara/spec/session/all_spec.rb +11 -15
- data/lib/capybara/spec/session/assert_text_spec.rb +17 -17
- data/lib/capybara/spec/session/attach_file_spec.rb +6 -0
- data/lib/capybara/spec/session/check_spec.rb +10 -0
- data/lib/capybara/spec/session/choose_spec.rb +6 -0
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +9 -0
- data/lib/capybara/spec/session/click_link_spec.rb +12 -1
- data/lib/capybara/spec/session/current_scope_spec.rb +1 -1
- data/lib/capybara/spec/session/fill_in_spec.rb +6 -0
- data/lib/capybara/spec/session/find_link_spec.rb +10 -0
- data/lib/capybara/spec/session/find_spec.rb +15 -1
- data/lib/capybara/spec/session/first_spec.rb +1 -1
- data/lib/capybara/spec/session/frame/within_frame_spec.rb +2 -0
- data/lib/capybara/spec/session/has_all_selectors_spec.rb +5 -5
- data/lib/capybara/spec/session/has_ancestor_spec.rb +2 -2
- data/lib/capybara/spec/session/has_any_selectors_spec.rb +6 -2
- data/lib/capybara/spec/session/has_button_spec.rb +30 -0
- data/lib/capybara/spec/session/has_current_path_spec.rb +3 -3
- data/lib/capybara/spec/session/has_element_spec.rb +47 -0
- data/lib/capybara/spec/session/has_field_spec.rb +25 -1
- data/lib/capybara/spec/session/has_link_spec.rb +40 -0
- data/lib/capybara/spec/session/has_none_selectors_spec.rb +7 -7
- data/lib/capybara/spec/session/has_select_spec.rb +10 -4
- data/lib/capybara/spec/session/has_selector_spec.rb +15 -0
- data/lib/capybara/spec/session/has_table_spec.rb +13 -2
- data/lib/capybara/spec/session/has_text_spec.rb +6 -14
- data/lib/capybara/spec/session/matches_style_spec.rb +2 -0
- data/lib/capybara/spec/session/node_spec.rb +88 -1
- data/lib/capybara/spec/session/node_wrapper_spec.rb +1 -1
- data/lib/capybara/spec/session/reset_session_spec.rb +13 -0
- data/lib/capybara/spec/session/scroll_spec.rb +7 -5
- data/lib/capybara/spec/session/uncheck_spec.rb +1 -1
- data/lib/capybara/spec/session/visit_spec.rb +20 -0
- data/lib/capybara/spec/session/window/window_spec.rb +1 -1
- data/lib/capybara/spec/session/window/windows_spec.rb +1 -1
- data/lib/capybara/spec/session/within_spec.rb +13 -0
- data/lib/capybara/spec/spec_helper.rb +12 -5
- data/lib/capybara/spec/test_app.rb +91 -14
- data/lib/capybara/spec/views/animated.erb +1 -1
- data/lib/capybara/spec/views/form.erb +34 -4
- data/lib/capybara/spec/views/frame_child.erb +1 -1
- data/lib/capybara/spec/views/frame_one.erb +1 -1
- data/lib/capybara/spec/views/frame_parent.erb +1 -1
- data/lib/capybara/spec/views/frame_two.erb +1 -1
- data/lib/capybara/spec/views/initial_alert.erb +2 -1
- data/lib/capybara/spec/views/layout.erb +10 -0
- data/lib/capybara/spec/views/obscured.erb +1 -1
- data/lib/capybara/spec/views/offset.erb +2 -1
- data/lib/capybara/spec/views/path.erb +2 -2
- data/lib/capybara/spec/views/popup_one.erb +1 -1
- data/lib/capybara/spec/views/popup_two.erb +1 -1
- data/lib/capybara/spec/views/react.erb +2 -2
- data/lib/capybara/spec/views/scroll.erb +2 -1
- data/lib/capybara/spec/views/spatial.erb +1 -1
- data/lib/capybara/spec/views/with_animation.erb +2 -3
- data/lib/capybara/spec/views/with_base_tag.erb +2 -2
- data/lib/capybara/spec/views/with_dragula.erb +2 -2
- data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
- data/lib/capybara/spec/views/with_hover.erb +2 -2
- data/lib/capybara/spec/views/with_html.erb +5 -3
- data/lib/capybara/spec/views/with_jquery_animation.erb +1 -1
- data/lib/capybara/spec/views/with_js.erb +2 -3
- data/lib/capybara/spec/views/with_jstree.erb +1 -1
- data/lib/capybara/spec/views/with_namespace.erb +1 -0
- data/lib/capybara/spec/views/with_scope.erb +2 -2
- data/lib/capybara/spec/views/with_shadow.erb +31 -0
- data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
- data/lib/capybara/spec/views/with_sortable_js.erb +2 -2
- data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
- data/lib/capybara/spec/views/with_windows.erb +1 -1
- data/lib/capybara/spec/views/within_frames.erb +1 -1
- data/lib/capybara/version.rb +1 -1
- data/lib/capybara/window.rb +1 -1
- data/lib/capybara.rb +30 -30
- data/spec/basic_node_spec.rb +16 -3
- data/spec/capybara_spec.rb +12 -0
- data/spec/counter_spec.rb +35 -0
- data/spec/css_builder_spec.rb +1 -1
- data/spec/css_splitter_spec.rb +1 -1
- data/spec/dsl_spec.rb +5 -3
- data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -2
- data/spec/fixtures/selenium_driver_rspec_success.rb +2 -2
- data/spec/minitest_spec.rb +12 -1
- data/spec/minitest_spec_spec.rb +4 -0
- data/spec/per_session_config_spec.rb +1 -1
- data/spec/rack_test_spec.rb +30 -12
- data/spec/result_spec.rb +41 -35
- data/spec/rspec/features_spec.rb +3 -3
- data/spec/rspec/scenarios_spec.rb +2 -2
- data/spec/rspec/shared_spec_matchers.rb +27 -3
- data/spec/rspec_matchers_spec.rb +25 -0
- data/spec/rspec_spec.rb +3 -3
- data/spec/sauce_spec_chrome.rb +5 -5
- data/spec/selector_spec.rb +4 -4
- data/spec/selenium_spec_chrome.rb +20 -18
- data/spec/selenium_spec_chrome_remote.rb +15 -19
- data/spec/selenium_spec_edge.rb +19 -6
- data/spec/selenium_spec_firefox.rb +26 -8
- data/spec/selenium_spec_firefox_remote.rb +18 -4
- data/spec/selenium_spec_ie.rb +7 -8
- data/spec/selenium_spec_safari.rb +34 -20
- data/spec/server_spec.rb +19 -7
- data/spec/shared_selenium_node.rb +0 -4
- data/spec/shared_selenium_session.rb +22 -14
- data/spec/spec_helper.rb +36 -3
- data/spec/whitespace_normalizer_spec.rb +54 -0
- data/spec/xpath_builder_spec.rb +1 -1
- metadata +49 -30
- data/lib/capybara/selenium/logger_suppressor.rb +0 -34
- data/lib/capybara/selenium/patches/action_pauser.rb +0 -26
- data/lib/capybara/spec/views/with_title.erb +0 -5
data/lib/capybara/selector.rb
CHANGED
@@ -14,18 +14,37 @@ require 'capybara/selector/definition'
|
|
14
14
|
# * :left_of (Element) - Match elements left of the passed element on the page
|
15
15
|
# * :right_of (Element) - Match elements right of the passed element on the page
|
16
16
|
# * :near (Element) - Match elements near (within 50px) the passed element on the page
|
17
|
+
# * :focused (Boolean) - Match elements with focus (requires driver support)
|
17
18
|
#
|
18
19
|
# ### Built-in Selectors
|
19
20
|
#
|
20
21
|
# * **:xpath** - Select elements by XPath expression
|
21
22
|
# * Locator: An XPath expression
|
22
23
|
#
|
24
|
+
# ```ruby
|
25
|
+
# page.html # => '<input>'
|
26
|
+
#
|
27
|
+
# page.find :xpath, './/input'
|
28
|
+
# ```
|
29
|
+
#
|
23
30
|
# * **:css** - Select elements by CSS selector
|
24
31
|
# * Locator: A CSS selector
|
25
32
|
#
|
33
|
+
# ```ruby
|
34
|
+
# page.html # => '<input>'
|
35
|
+
#
|
36
|
+
# page.find :css, 'input'
|
37
|
+
# ```
|
38
|
+
#
|
26
39
|
# * **:id** - Select element by id
|
27
40
|
# * Locator: (String, Regexp, XPath::Expression) The id of the element to match
|
28
41
|
#
|
42
|
+
# ```ruby
|
43
|
+
# page.html # => '<input id="field">'
|
44
|
+
#
|
45
|
+
# page.find :id, 'content'
|
46
|
+
# ```
|
47
|
+
#
|
29
48
|
# * **:field** - Select field elements (input [not of type submit, image, or hidden], textarea, select)
|
30
49
|
# * Locator: Matches against the id, {Capybara.configure test_id} attribute, name, placeholder, or
|
31
50
|
# associated label text
|
@@ -42,12 +61,30 @@ require 'capybara/selector/definition'
|
|
42
61
|
# * :valid (Boolean) - Match fields that are valid/invalid according to HTML5 form validation
|
43
62
|
# * :validation_message (String, Regexp) - Matches the elements current validationMessage
|
44
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
|
+
#
|
45
74
|
# * **:fieldset** - Select fieldset elements
|
46
75
|
# * Locator: Matches id, {Capybara.configure test_id}, or contents of wrapped legend
|
47
76
|
# * Filters:
|
48
77
|
# * :legend (String) - Matches contents of wrapped legend
|
49
78
|
# * :disabled (Boolean) - Match disabled fieldset?
|
50
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
|
+
#
|
51
88
|
# * **:link** - Find links (`<a>` elements with an href attribute)
|
52
89
|
# * Locator: Matches the id, {Capybara.configure test_id}, or title attributes, or the string content of the link,
|
53
90
|
# or the alt attribute of a contained img element. By default this selector requires a link to have an href attribute.
|
@@ -56,6 +93,17 @@ require 'capybara/selector/definition'
|
|
56
93
|
# * :alt (String) - Matches the alt attribute of a contained img element
|
57
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
|
58
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
|
+
#
|
59
107
|
# * **:button** - Find buttons ( input [of type submit, reset, image, button] or button elements )
|
60
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
|
61
109
|
# * Filters:
|
@@ -65,11 +113,31 @@ require 'capybara/selector/definition'
|
|
65
113
|
# * :type (String) - Matches the type attribute
|
66
114
|
# * :disabled (Boolean, :all) - Match disabled buttons (Default: false)
|
67
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
|
+
#
|
68
126
|
# * **:link_or_button** - Find links or buttons
|
69
127
|
# * Locator: See :link and :button selectors
|
70
128
|
# * Filters:
|
71
129
|
# * :disabled (Boolean, :all) - Match disabled buttons? (Default: false)
|
72
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
|
+
#
|
73
141
|
# * **:fillable_field** - Find text fillable fields ( textarea, input [not of type submit, image, radio, checkbox, hidden, file] )
|
74
142
|
# * Locator: Matches against the id, {Capybara.configure test_id} attribute, name, placeholder, or associated label text
|
75
143
|
# * Filters:
|
@@ -82,6 +150,16 @@ require 'capybara/selector/definition'
|
|
82
150
|
# * :valid (Boolean) - Match fields that are valid/invalid according to HTML5 form validation
|
83
151
|
# * :validation_message (String, Regexp) - Matches the elements current validationMessage
|
84
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
|
+
#
|
85
163
|
# * **:radio_button** - Find radio buttons
|
86
164
|
# * Locator: Match id, {Capybara.configure test_id} attribute, name, or associated label text
|
87
165
|
# * Filters:
|
@@ -92,6 +170,18 @@ require 'capybara/selector/definition'
|
|
92
170
|
# * :option (String, Regexp) - Match the current value
|
93
171
|
# * :with - Alias of :option
|
94
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
|
+
#
|
95
185
|
# * **:checkbox** - Find checkboxes
|
96
186
|
# * Locator: Match id, {Capybara.configure test_id} attribute, name, or associated label text
|
97
187
|
# * Filters:
|
@@ -102,6 +192,15 @@ require 'capybara/selector/definition'
|
|
102
192
|
# * :with (String, Regexp) - Match the current value
|
103
193
|
# * :option - Alias of :with
|
104
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
|
+
#
|
105
204
|
# * **:select** - Find select elements
|
106
205
|
# * Locator: Match id, {Capybara.configure test_id} attribute, name, placeholder, or associated label text
|
107
206
|
# * Filters:
|
@@ -116,12 +215,40 @@ require 'capybara/selector/definition'
|
|
116
215
|
# * :selected (String, Array<String>) - Match the selection(s)
|
117
216
|
# * :with_selected (String, Array<String>) - Partial match the selection(s)
|
118
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
|
+
#
|
119
235
|
# * **:option** - Find option elements
|
120
236
|
# * Locator: Match text of option
|
121
237
|
# * Filters:
|
122
238
|
# * :disabled (Boolean) - Match disabled option
|
123
239
|
# * :selected (Boolean) - Match selected option
|
124
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
|
+
#
|
125
252
|
# * **:datalist_input** - Find input field with datalist completion
|
126
253
|
# * Locator: Matches against the id, {Capybara.configure test_id} attribute, name,
|
127
254
|
# placeholder, or associated label text
|
@@ -132,11 +259,42 @@ require 'capybara/selector/definition'
|
|
132
259
|
# * :options (Array<String>) - Exact match options
|
133
260
|
# * :with_options (Array<String>) - Partial match options
|
134
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
|
+
#
|
135
279
|
# * **:datalist_option** - Find datalist option
|
136
280
|
# * Locator: Match text or value of option
|
137
281
|
# * Filters:
|
138
282
|
# * :disabled (Boolean) - Match disabled option
|
139
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
|
+
#
|
140
298
|
# * **:file_field** - Find file input elements
|
141
299
|
# * Locator: Match id, {Capybara.configure test_id} attribute, name, or associated label text
|
142
300
|
# * Filters:
|
@@ -144,11 +302,31 @@ require 'capybara/selector/definition'
|
|
144
302
|
# * :disabled (Boolean, :all) - Match disabled field? (Default: false)
|
145
303
|
# * :multiple (Boolean) - Match field that accepts multiple values
|
146
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
|
+
#
|
147
316
|
# * **:label** - Find label elements
|
148
317
|
# * Locator: Match id, {Capybara.configure test_id}, or text contents
|
149
318
|
# * Filters:
|
150
319
|
# * :for (Element, String, Regexp) - The element or id of the element associated with the label
|
151
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
|
+
#
|
152
330
|
# * **:table** - Find table elements
|
153
331
|
# * Locator: id, {Capybara.configure test_id}, or caption text of table
|
154
332
|
# * Filters:
|
@@ -158,19 +336,93 @@ require 'capybara/selector/definition'
|
|
158
336
|
# * :with_cols (Array<Array<String>>, Array<Hash<String, String>>) - Partial match `<td>` data - visibility of `<td>` elements is not considered
|
159
337
|
# * :cols (Array<Array<String>>) - Match all `<td>`s - visibility of `<td>` elements is not considered
|
160
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
|
+
#
|
161
376
|
# * **:table_row** - Find table row
|
162
377
|
# * Locator: Array<String>, Hash<String, String> table row `<td>` contents - visibility of `<td>` elements is not considered
|
163
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
|
+
#
|
164
399
|
# * **:frame** - Find frame/iframe elements
|
165
400
|
# * Locator: Match id, {Capybara.configure test_id} attribute, or name
|
166
401
|
# * Filters:
|
167
402
|
# * :name (String) - Match name attribute
|
168
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
|
+
#
|
169
412
|
# * **:element**
|
170
413
|
# * Locator: Type of element ('div', 'a', etc) - if not specified defaults to '*'
|
171
414
|
# * Filters:
|
172
415
|
# * :\<any> (String, Regexp) - Match on any specified element attribute
|
173
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
|
+
#
|
174
426
|
class Capybara::Selector; end # rubocop:disable Lint/EmptyClass
|
175
427
|
|
176
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
|
@@ -148,12 +139,17 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
148
139
|
unwrap_script_result(result)
|
149
140
|
end
|
150
141
|
|
142
|
+
def active_element
|
143
|
+
build_node(native_active_element)
|
144
|
+
end
|
145
|
+
|
151
146
|
def send_keys(*args)
|
152
|
-
|
147
|
+
# Should this call the specialized nodes rather than native???
|
148
|
+
native_active_element.send_keys(*args)
|
153
149
|
end
|
154
150
|
|
155
|
-
def save_screenshot(path, **
|
156
|
-
browser.save_screenshot(path)
|
151
|
+
def save_screenshot(path, **options)
|
152
|
+
browser.save_screenshot(path, **options)
|
157
153
|
end
|
158
154
|
|
159
155
|
def reset!
|
@@ -249,7 +245,13 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
249
245
|
end
|
250
246
|
|
251
247
|
def open_new_window(kind = :tab)
|
252
|
-
browser.
|
248
|
+
if browser.switch_to.respond_to?(:new_window)
|
249
|
+
handle = current_window_handle
|
250
|
+
browser.switch_to.new_window(kind)
|
251
|
+
switch_to_window(handle)
|
252
|
+
else
|
253
|
+
browser.manage.new_window(kind)
|
254
|
+
end
|
253
255
|
rescue NoMethodError, Selenium::WebDriver::Error::WebDriverError
|
254
256
|
# If not supported by the driver or browser default to using JS
|
255
257
|
browser.execute_script('window.open();')
|
@@ -293,7 +295,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
293
295
|
end
|
294
296
|
|
295
297
|
def invalid_element_errors
|
296
|
-
@invalid_element_errors ||=
|
298
|
+
@invalid_element_errors ||=
|
297
299
|
[
|
298
300
|
::Selenium::WebDriver::Error::StaleElementReferenceError,
|
299
301
|
::Selenium::WebDriver::Error::ElementNotInteractableError,
|
@@ -302,18 +304,10 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
302
304
|
::Selenium::WebDriver::Error::NoSuchElementError, # IE
|
303
305
|
::Selenium::WebDriver::Error::InvalidArgumentError # IE
|
304
306
|
].tap do |errors|
|
305
|
-
|
306
|
-
::Selenium::WebDriver
|
307
|
-
errors.concat [
|
308
|
-
::Selenium::WebDriver::Error::UnhandledError,
|
309
|
-
::Selenium::WebDriver::Error::ElementNotVisibleError,
|
310
|
-
::Selenium::WebDriver::Error::InvalidElementStateError,
|
311
|
-
::Selenium::WebDriver::Error::ElementNotSelectableError
|
312
|
-
]
|
313
|
-
end
|
307
|
+
if defined?(::Selenium::WebDriver::Error::DetachedShadowRootError)
|
308
|
+
errors.push(::Selenium::WebDriver::Error::DetachedShadowRootError)
|
314
309
|
end
|
315
310
|
end
|
316
|
-
end
|
317
311
|
end
|
318
312
|
|
319
313
|
def no_such_window_error
|
@@ -322,14 +316,14 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
322
316
|
|
323
317
|
private
|
324
318
|
|
325
|
-
def selenium_4?
|
326
|
-
defined?(Selenium::WebDriver::VERSION) && (Selenium::WebDriver::VERSION.to_f >= 4)
|
327
|
-
end
|
328
|
-
|
329
319
|
def native_args(args)
|
330
320
|
args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg }
|
331
321
|
end
|
332
322
|
|
323
|
+
def native_active_element
|
324
|
+
browser.switch_to.active_element
|
325
|
+
end
|
326
|
+
|
333
327
|
def clear_browser_state
|
334
328
|
delete_all_cookies
|
335
329
|
clear_storage
|
@@ -344,10 +338,7 @@ private
|
|
344
338
|
end
|
345
339
|
|
346
340
|
def unhandled_alert_errors
|
347
|
-
@unhandled_alert_errors ||=
|
348
|
-
[Selenium::WebDriver::Error::UnexpectedAlertOpenError],
|
349
|
-
'UnhandledAlertError'
|
350
|
-
)
|
341
|
+
@unhandled_alert_errors ||= [Selenium::WebDriver::Error::UnexpectedAlertOpenError]
|
351
342
|
end
|
352
343
|
|
353
344
|
def delete_all_cookies
|
@@ -437,17 +428,7 @@ private
|
|
437
428
|
end
|
438
429
|
|
439
430
|
def find_modal_errors
|
440
|
-
@find_modal_errors ||=
|
441
|
-
end
|
442
|
-
|
443
|
-
def with_legacy_error(errors, legacy_error)
|
444
|
-
errors.tap do |errs|
|
445
|
-
unless selenium_4?
|
446
|
-
::Selenium::WebDriver.logger.suppress_deprecations do
|
447
|
-
errs << Selenium::WebDriver::Error.const_get(legacy_error)
|
448
|
-
end
|
449
|
-
end
|
450
|
-
end
|
431
|
+
@find_modal_errors ||= [Selenium::WebDriver::Error::TimeoutError]
|
451
432
|
end
|
452
433
|
|
453
434
|
def silenced_unknown_error_message?(msg)
|
@@ -464,7 +445,7 @@ private
|
|
464
445
|
arg.map { |arr| unwrap_script_result(arr) }
|
465
446
|
when Hash
|
466
447
|
arg.transform_values! { |value| unwrap_script_result(value) }
|
467
|
-
when Selenium::WebDriver::Element
|
448
|
+
when Selenium::WebDriver::Element, Selenium::WebDriver::ShadowRoot
|
468
449
|
build_node(arg)
|
469
450
|
else
|
470
451
|
arg
|
@@ -475,10 +456,6 @@ private
|
|
475
456
|
browser
|
476
457
|
end
|
477
458
|
|
478
|
-
def active_element
|
479
|
-
browser.switch_to.active_element
|
480
|
-
end
|
481
|
-
|
482
459
|
def build_node(native_node, initial_cache = {})
|
483
460
|
::Capybara::Selenium::Node.new(self, native_node, initial_cache)
|
484
461
|
end
|
@@ -38,7 +38,7 @@ module Capybara::Selenium::Driver::ChromeDriver
|
|
38
38
|
return unless @browser
|
39
39
|
|
40
40
|
switch_to_window(window_handles.first)
|
41
|
-
window_handles.slice(1
|
41
|
+
window_handles.slice(1..).each { |win| close_window(win) }
|
42
42
|
return super if chromedriver_version < 73
|
43
43
|
|
44
44
|
timer = Capybara::Helpers.timer(expire_in: 10)
|
@@ -39,7 +39,7 @@ module Capybara::Selenium::Driver::EdgeDriver
|
|
39
39
|
return unless @browser
|
40
40
|
|
41
41
|
switch_to_window(window_handles.first)
|
42
|
-
window_handles.slice(1
|
42
|
+
window_handles.slice(1..).each { |win| close_window(win) }
|
43
43
|
|
44
44
|
timer = Capybara::Helpers.timer(expire_in: 10)
|
45
45
|
begin
|
@@ -103,9 +103,13 @@ private
|
|
103
103
|
end
|
104
104
|
|
105
105
|
def execute_cdp(cmd, params = {})
|
106
|
-
|
107
|
-
|
108
|
-
|
106
|
+
if browser.respond_to? :execute_cdp
|
107
|
+
browser.execute_cdp(cmd, **params)
|
108
|
+
else
|
109
|
+
args = { cmd: cmd, params: params }
|
110
|
+
result = bridge.http.call(:post, "session/#{bridge.session_id}/ms/cdp/execute", args)
|
111
|
+
result['value']
|
112
|
+
end
|
109
113
|
end
|
110
114
|
|
111
115
|
def build_node(native_node, initial_cache = {})
|
@@ -115,7 +119,7 @@ private
|
|
115
119
|
def edgedriver_version
|
116
120
|
@edgedriver_version ||= begin
|
117
121
|
caps = browser.capabilities
|
118
|
-
caps['
|
122
|
+
caps['msedge']&.fetch('msedgedriverVersion', nil).to_f
|
119
123
|
end
|
120
124
|
end
|
121
125
|
end
|
@@ -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) && (Selenium::WebDriver::VERSION.to_f >= 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
|
@@ -52,7 +47,7 @@ module Capybara::Selenium::Driver::W3CFirefoxDriver
|
|
52
47
|
end
|
53
48
|
|
54
49
|
switch_to_window(window_handles.first)
|
55
|
-
window_handles.slice(1
|
50
|
+
window_handles.slice(1..).each { |win| close_window(win) }
|
56
51
|
super
|
57
52
|
end
|
58
53
|
|
@@ -39,10 +39,8 @@ class Capybara::Selenium::Node
|
|
39
39
|
input.set_file(args)
|
40
40
|
driver.execute_script DROP_FILE, self, input
|
41
41
|
else
|
42
|
-
items = args.
|
43
|
-
arg.
|
44
|
-
arr_ << { type: type, data: data }
|
45
|
-
end
|
42
|
+
items = args.flat_map do |arg|
|
43
|
+
arg.map { |(type, data)| { type: type, data: data } }
|
46
44
|
end
|
47
45
|
driver.execute_script DROP_STRING, items, self
|
48
46
|
end
|
@@ -168,6 +166,9 @@ class Capybara::Selenium::Node
|
|
168
166
|
opts[key + 'Key'] = true;
|
169
167
|
}
|
170
168
|
|
169
|
+
var dragEnterEvent = new DragEvent('dragenter', opts);
|
170
|
+
target.dispatchEvent(dragEnterEvent);
|
171
|
+
|
171
172
|
// fire 2 dragover events to simulate dragging with a direction
|
172
173
|
var entryPoint = pointOnRect(sourceCenter, targetRect)
|
173
174
|
var dragOverOpts = Object.assign({clientX: entryPoint.x, clientY: entryPoint.y}, opts);
|