capybara 3.29.0 → 3.36.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (200) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +203 -15
  3. data/README.md +13 -4
  4. data/lib/capybara/config.rb +24 -10
  5. data/lib/capybara/cucumber.rb +1 -1
  6. data/lib/capybara/driver/base.rb +8 -0
  7. data/lib/capybara/driver/node.rb +1 -1
  8. data/lib/capybara/dsl.rb +10 -2
  9. data/lib/capybara/helpers.rb +19 -2
  10. data/lib/capybara/minitest/spec.rb +156 -97
  11. data/lib/capybara/minitest.rb +232 -144
  12. data/lib/capybara/node/actions.rb +41 -37
  13. data/lib/capybara/node/base.rb +6 -6
  14. data/lib/capybara/node/document.rb +2 -2
  15. data/lib/capybara/node/document_matchers.rb +3 -3
  16. data/lib/capybara/node/element.rb +24 -21
  17. data/lib/capybara/node/finders.rb +25 -18
  18. data/lib/capybara/node/matchers.rb +72 -57
  19. data/lib/capybara/node/simple.rb +13 -3
  20. data/lib/capybara/queries/active_element_query.rb +18 -0
  21. data/lib/capybara/queries/ancestor_query.rb +4 -3
  22. data/lib/capybara/queries/base_query.rb +2 -1
  23. data/lib/capybara/queries/current_path_query.rb +14 -4
  24. data/lib/capybara/queries/selector_query.rb +71 -23
  25. data/lib/capybara/queries/sibling_query.rb +4 -3
  26. data/lib/capybara/queries/style_query.rb +1 -1
  27. data/lib/capybara/queries/text_query.rb +7 -1
  28. data/lib/capybara/rack_test/browser.rb +13 -4
  29. data/lib/capybara/rack_test/driver.rb +2 -1
  30. data/lib/capybara/rack_test/form.rb +2 -2
  31. data/lib/capybara/rack_test/node.rb +43 -15
  32. data/lib/capybara/registration_container.rb +44 -0
  33. data/lib/capybara/registrations/drivers.rb +18 -12
  34. data/lib/capybara/registrations/patches/puma_ssl.rb +3 -1
  35. data/lib/capybara/registrations/servers.rb +3 -2
  36. data/lib/capybara/result.rb +35 -15
  37. data/lib/capybara/rspec/matcher_proxies.rb +8 -8
  38. data/lib/capybara/rspec/matchers/base.rb +12 -6
  39. data/lib/capybara/rspec/matchers/count_sugar.rb +2 -1
  40. data/lib/capybara/rspec/matchers/have_ancestor.rb +4 -3
  41. data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
  42. data/lib/capybara/rspec/matchers/have_selector.rb +16 -8
  43. data/lib/capybara/rspec/matchers/have_sibling.rb +3 -3
  44. data/lib/capybara/rspec/matchers/have_text.rb +3 -3
  45. data/lib/capybara/rspec/matchers/have_title.rb +2 -2
  46. data/lib/capybara/rspec/matchers/match_selector.rb +3 -3
  47. data/lib/capybara/rspec/matchers/match_style.rb +7 -2
  48. data/lib/capybara/rspec/matchers/spatial_sugar.rb +2 -1
  49. data/lib/capybara/rspec/matchers.rb +33 -32
  50. data/lib/capybara/rspec.rb +2 -0
  51. data/lib/capybara/selector/builders/css_builder.rb +2 -2
  52. data/lib/capybara/selector/builders/xpath_builder.rb +4 -2
  53. data/lib/capybara/selector/css.rb +2 -2
  54. data/lib/capybara/selector/definition/button.rb +35 -13
  55. data/lib/capybara/selector/definition/checkbox.rb +3 -3
  56. data/lib/capybara/selector/definition/css.rb +3 -1
  57. data/lib/capybara/selector/definition/datalist_input.rb +2 -2
  58. data/lib/capybara/selector/definition/datalist_option.rb +1 -1
  59. data/lib/capybara/selector/definition/element.rb +3 -2
  60. data/lib/capybara/selector/definition/field.rb +1 -1
  61. data/lib/capybara/selector/definition/file_field.rb +2 -2
  62. data/lib/capybara/selector/definition/fillable_field.rb +3 -3
  63. data/lib/capybara/selector/definition/label.rb +5 -3
  64. data/lib/capybara/selector/definition/link.rb +8 -0
  65. data/lib/capybara/selector/definition/radio_button.rb +3 -3
  66. data/lib/capybara/selector/definition/select.rb +33 -14
  67. data/lib/capybara/selector/definition/table.rb +6 -3
  68. data/lib/capybara/selector/definition/table_row.rb +2 -2
  69. data/lib/capybara/selector/definition.rb +14 -11
  70. data/lib/capybara/selector/filter_set.rb +17 -17
  71. data/lib/capybara/selector/filters/base.rb +6 -1
  72. data/lib/capybara/selector/filters/locator_filter.rb +1 -1
  73. data/lib/capybara/selector/selector.rb +13 -3
  74. data/lib/capybara/selector.rb +37 -19
  75. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -1
  76. data/lib/capybara/selenium/atoms/src/getAttribute.js +1 -1
  77. data/lib/capybara/selenium/atoms/src/isDisplayed.js +1 -1
  78. data/lib/capybara/selenium/driver.rb +79 -16
  79. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +11 -13
  80. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +10 -12
  81. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +4 -4
  82. data/lib/capybara/selenium/extensions/find.rb +4 -4
  83. data/lib/capybara/selenium/extensions/html5_drag.rb +30 -13
  84. data/lib/capybara/selenium/extensions/scroll.rb +8 -10
  85. data/lib/capybara/selenium/logger_suppressor.rb +8 -2
  86. data/lib/capybara/selenium/node.rb +110 -26
  87. data/lib/capybara/selenium/nodes/chrome_node.rb +34 -19
  88. data/lib/capybara/selenium/nodes/edge_node.rb +5 -3
  89. data/lib/capybara/selenium/nodes/firefox_node.rb +11 -6
  90. data/lib/capybara/selenium/nodes/safari_node.rb +3 -3
  91. data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
  92. data/lib/capybara/selenium/patches/atoms.rb +4 -4
  93. data/lib/capybara/selenium/patches/logs.rb +7 -9
  94. data/lib/capybara/server/animation_disabler.rb +17 -6
  95. data/lib/capybara/server/checker.rb +1 -1
  96. data/lib/capybara/server/middleware.rb +22 -10
  97. data/lib/capybara/server.rb +15 -3
  98. data/lib/capybara/session/config.rb +9 -3
  99. data/lib/capybara/session/matchers.rb +11 -11
  100. data/lib/capybara/session.rb +73 -37
  101. data/lib/capybara/spec/public/test.js +75 -7
  102. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -1
  103. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  104. data/lib/capybara/spec/session/all_spec.rb +62 -9
  105. data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
  106. data/lib/capybara/spec/session/assert_text_spec.rb +9 -5
  107. data/lib/capybara/spec/session/check_spec.rb +15 -0
  108. data/lib/capybara/spec/session/choose_spec.rb +6 -0
  109. data/lib/capybara/spec/session/click_button_spec.rb +16 -0
  110. data/lib/capybara/spec/session/click_link_or_button_spec.rb +9 -0
  111. data/lib/capybara/spec/session/current_url_spec.rb +11 -1
  112. data/lib/capybara/spec/session/fill_in_spec.rb +29 -0
  113. data/lib/capybara/spec/session/find_spec.rb +31 -8
  114. data/lib/capybara/spec/session/has_any_selectors_spec.rb +4 -0
  115. data/lib/capybara/spec/session/has_button_spec.rb +75 -0
  116. data/lib/capybara/spec/session/has_css_spec.rb +14 -10
  117. data/lib/capybara/spec/session/has_current_path_spec.rb +15 -2
  118. data/lib/capybara/spec/session/has_field_spec.rb +40 -0
  119. data/lib/capybara/spec/session/has_link_spec.rb +24 -0
  120. data/lib/capybara/spec/session/has_select_spec.rb +32 -4
  121. data/lib/capybara/spec/session/has_selector_spec.rb +4 -4
  122. data/lib/capybara/spec/session/has_table_spec.rb +51 -5
  123. data/lib/capybara/spec/session/has_text_spec.rb +25 -1
  124. data/lib/capybara/spec/session/html_spec.rb +1 -1
  125. data/lib/capybara/spec/session/matches_style_spec.rb +2 -2
  126. data/lib/capybara/spec/session/node_spec.rb +184 -33
  127. data/lib/capybara/spec/session/refresh_spec.rb +2 -1
  128. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  129. data/lib/capybara/spec/session/save_page_spec.rb +4 -4
  130. data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -4
  131. data/lib/capybara/spec/session/scroll_spec.rb +4 -4
  132. data/lib/capybara/spec/session/selectors_spec.rb +15 -2
  133. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +1 -1
  134. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +1 -1
  135. data/lib/capybara/spec/session/window/window_spec.rb +8 -8
  136. data/lib/capybara/spec/session/window/windows_spec.rb +1 -1
  137. data/lib/capybara/spec/spec_helper.rb +17 -17
  138. data/lib/capybara/spec/test_app.rb +40 -29
  139. data/lib/capybara/spec/views/animated.erb +1 -1
  140. data/lib/capybara/spec/views/form.erb +52 -6
  141. data/lib/capybara/spec/views/frame_child.erb +1 -1
  142. data/lib/capybara/spec/views/frame_one.erb +1 -1
  143. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  144. data/lib/capybara/spec/views/frame_two.erb +1 -1
  145. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  146. data/lib/capybara/spec/views/layout.erb +10 -0
  147. data/lib/capybara/spec/views/obscured.erb +1 -1
  148. data/lib/capybara/spec/views/offset.erb +2 -1
  149. data/lib/capybara/spec/views/path.erb +2 -2
  150. data/lib/capybara/spec/views/popup_one.erb +1 -1
  151. data/lib/capybara/spec/views/popup_two.erb +1 -1
  152. data/lib/capybara/spec/views/react.erb +2 -2
  153. data/lib/capybara/spec/views/scroll.erb +2 -1
  154. data/lib/capybara/spec/views/spatial.erb +1 -1
  155. data/lib/capybara/spec/views/with_animation.erb +10 -3
  156. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  157. data/lib/capybara/spec/views/with_dragula.erb +5 -3
  158. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  159. data/lib/capybara/spec/views/with_hover.erb +2 -2
  160. data/lib/capybara/spec/views/with_html.erb +3 -3
  161. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  162. data/lib/capybara/spec/views/with_js.erb +5 -3
  163. data/lib/capybara/spec/views/with_jstree.erb +1 -1
  164. data/lib/capybara/spec/views/with_namespace.erb +1 -0
  165. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  166. data/lib/capybara/spec/views/with_sortable_js.erb +3 -3
  167. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  168. data/lib/capybara/spec/views/with_windows.erb +1 -1
  169. data/lib/capybara/spec/views/within_frames.erb +1 -1
  170. data/lib/capybara/version.rb +1 -1
  171. data/lib/capybara/window.rb +3 -7
  172. data/lib/capybara.rb +35 -29
  173. data/spec/basic_node_spec.rb +25 -11
  174. data/spec/capybara_spec.rb +1 -1
  175. data/spec/dsl_spec.rb +16 -3
  176. data/spec/fixtures/selenium_driver_rspec_success.rb +2 -2
  177. data/spec/minitest_spec.rb +3 -2
  178. data/spec/minitest_spec_spec.rb +46 -46
  179. data/spec/rack_test_spec.rb +37 -11
  180. data/spec/regexp_dissassembler_spec.rb +40 -36
  181. data/spec/result_spec.rb +42 -32
  182. data/spec/rspec/features_spec.rb +5 -2
  183. data/spec/rspec/scenarios_spec.rb +4 -0
  184. data/spec/rspec/shared_spec_matchers.rb +68 -56
  185. data/spec/rspec_spec.rb +8 -4
  186. data/spec/selector_spec.rb +17 -2
  187. data/spec/selenium_spec_chrome.rb +48 -25
  188. data/spec/selenium_spec_chrome_remote.rb +13 -6
  189. data/spec/selenium_spec_firefox.rb +25 -17
  190. data/spec/selenium_spec_firefox_remote.rb +2 -2
  191. data/spec/selenium_spec_ie.rb +3 -6
  192. data/spec/selenium_spec_safari.rb +27 -19
  193. data/spec/server_spec.rb +84 -31
  194. data/spec/session_spec.rb +1 -1
  195. data/spec/shared_selenium_node.rb +21 -7
  196. data/spec/shared_selenium_session.rb +114 -19
  197. data/spec/spec_helper.rb +1 -1
  198. metadata +78 -21
  199. data/lib/capybara/spec/session/source_spec.rb +0 -0
  200. data/lib/capybara/spec/views/with_title.erb +0 -5
@@ -7,13 +7,14 @@ require 'capybara/selector/definition'
7
7
  #
8
8
  # All Selectors below support the listed selector specific filters in addition to the following system-wide filters
9
9
  # * :id (String, Regexp, XPath::Expression) - Matches the id attribute
10
- # * :class (String, Array<String>, Regexp, XPath::Expression) - Matches the class(es) provided
10
+ # * :class (String, Array<String | Regexp>, Regexp, XPath::Expression) - Matches the class(es) provided
11
11
  # * :style (String, Regexp, Hash<String, String>) - Match on elements style
12
12
  # * :above (Element) - Match elements above the passed element on the page
13
13
  # * :below (Element) - Match elements below the passed element on the page
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
  #
@@ -30,8 +31,8 @@ require 'capybara/selector/definition'
30
31
  # * Locator: Matches against the id, {Capybara.configure test_id} attribute, name, placeholder, or
31
32
  # associated label text
32
33
  # * Filters:
33
- # * :name (String) - Matches the name attribute
34
- # * :placeholder (String) - Matches the placeholder attribute
34
+ # * :name (String, Regexp) - Matches the name attribute
35
+ # * :placeholder (String, Regexp) - Matches the placeholder attribute
35
36
  # * :type (String) - Matches the type attribute of the field or element type for 'textarea' and 'select'
36
37
  # * :readonly (Boolean) - Match on the element being readonly
37
38
  # * :with (String, Regexp) - Matches the current value of the field
@@ -40,6 +41,7 @@ require 'capybara/selector/definition'
40
41
  # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
41
42
  # * :multiple (Boolean) - Match fields that accept multiple values
42
43
  # * :valid (Boolean) - Match fields that are valid/invalid according to HTML5 form validation
44
+ # * :validation_message (String, Regexp) - Matches the elements current validationMessage
43
45
  #
44
46
  # * **:fieldset** - Select fieldset elements
45
47
  # * Locator: Matches id, {Capybara.configure test_id}, or contents of wrapped legend
@@ -58,7 +60,7 @@ require 'capybara/selector/definition'
58
60
  # * **:button** - Find buttons ( input [of type submit, reset, image, button] or button elements )
59
61
  # * 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
60
62
  # * Filters:
61
- # * :name (String) - Matches the name attribute
63
+ # * :name (String, Regexp) - Matches the name attribute
62
64
  # * :title (String) - Matches the title attribute
63
65
  # * :value (String) - Matches the value of an input button
64
66
  # * :type (String) - Matches the type attribute
@@ -72,18 +74,19 @@ require 'capybara/selector/definition'
72
74
  # * **:fillable_field** - Find text fillable fields ( textarea, input [not of type submit, image, radio, checkbox, hidden, file] )
73
75
  # * Locator: Matches against the id, {Capybara.configure test_id} attribute, name, placeholder, or associated label text
74
76
  # * Filters:
75
- # * :name (String) - Matches the name attribute
76
- # * :placeholder (String) - Matches the placeholder attribute
77
+ # * :name (String, Regexp) - Matches the name attribute
78
+ # * :placeholder (String, Regexp) - Matches the placeholder attribute
77
79
  # * :with (String, Regexp) - Matches the current value of the field
78
80
  # * :type (String) - Matches the type attribute of the field or element type for 'textarea'
79
81
  # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
80
82
  # * :multiple (Boolean) - Match fields that accept multiple values
81
83
  # * :valid (Boolean) - Match fields that are valid/invalid according to HTML5 form validation
84
+ # * :validation_message (String, Regexp) - Matches the elements current validationMessage
82
85
  #
83
86
  # * **:radio_button** - Find radio buttons
84
87
  # * Locator: Match id, {Capybara.configure test_id} attribute, name, or associated label text
85
88
  # * Filters:
86
- # * :name (String) - Matches the name attribute
89
+ # * :name (String, Regexp) - Matches the name attribute
87
90
  # * :checked (Boolean) - Match checked fields?
88
91
  # * :unchecked (Boolean) - Match unchecked fields?
89
92
  # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
@@ -93,21 +96,23 @@ require 'capybara/selector/definition'
93
96
  # * **:checkbox** - Find checkboxes
94
97
  # * Locator: Match id, {Capybara.configure test_id} attribute, name, or associated label text
95
98
  # * Filters:
96
- # * :name (String) - Matches the name attribute
99
+ # * :name (String, Regexp) - Matches the name attribute
97
100
  # * :checked (Boolean) - Match checked fields?
98
101
  # * :unchecked (Boolean) - Match unchecked fields?
99
102
  # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
100
- # * :option (String, Regexp) - Match the current value
101
- # * :with - Alias of :option
103
+ # * :with (String, Regexp) - Match the current value
104
+ # * :option - Alias of :with
102
105
  #
103
106
  # * **:select** - Find select elements
104
107
  # * Locator: Match id, {Capybara.configure test_id} attribute, name, placeholder, or associated label text
105
108
  # * Filters:
106
- # * :name (String) - Matches the name attribute
107
- # * :placeholder (String) - Matches the placeholder attribute
109
+ # * :name (String, Regexp) - Matches the name attribute
110
+ # * :placeholder (String, Placeholder) - Matches the placeholder attribute
108
111
  # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
109
112
  # * :multiple (Boolean) - Match fields that accept multiple values
110
113
  # * :options (Array<String>) - Exact match options
114
+ # * :enabled_options (Array<String>) - Exact match enabled options
115
+ # * :disabled_options (Array<String>) - Exact match disabled options
111
116
  # * :with_options (Array<String>) - Partial match options
112
117
  # * :selected (String, Array<String>) - Match the selection(s)
113
118
  # * :with_selected (String, Array<String>) - Partial match the selection(s)
@@ -122,8 +127,8 @@ require 'capybara/selector/definition'
122
127
  # * Locator: Matches against the id, {Capybara.configure test_id} attribute, name,
123
128
  # placeholder, or associated label text
124
129
  # * Filters:
125
- # * :name (String) - Matches the name attribute
126
- # * :placeholder (String) - Matches the placeholder attribute
130
+ # * :name (String, Regexp) - Matches the name attribute
131
+ # * :placeholder (String, Regexp) - Matches the placeholder attribute
127
132
  # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
128
133
  # * :options (Array<String>) - Exact match options
129
134
  # * :with_options (Array<String>) - Partial match options
@@ -136,7 +141,7 @@ require 'capybara/selector/definition'
136
141
  # * **:file_field** - Find file input elements
137
142
  # * Locator: Match id, {Capybara.configure test_id} attribute, name, or associated label text
138
143
  # * Filters:
139
- # * :name (String) - Matches the name attribute
144
+ # * :name (String, Regexp) - Matches the name attribute
140
145
  # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
141
146
  # * :multiple (Boolean) - Match field that accepts multiple values
142
147
  #
@@ -167,16 +172,28 @@ require 'capybara/selector/definition'
167
172
  # * Filters:
168
173
  # * :\<any> (String, Regexp) - Match on any specified element attribute
169
174
  #
170
- class Capybara::Selector; end
175
+ class Capybara::Selector; end # rubocop:disable Lint/EmptyClass
171
176
 
172
177
  Capybara::Selector::FilterSet.add(:_field) do
173
178
  node_filter(:checked, :boolean) { |node, value| !(value ^ node.checked?) }
174
179
  node_filter(:unchecked, :boolean) { |node, value| (value ^ node.checked?) }
175
180
  node_filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| !(value ^ node.disabled?) }
176
181
  node_filter(:valid, :boolean) { |node, value| node.evaluate_script('this.validity.valid') == value }
182
+ node_filter(:name) { |node, value| !value.is_a?(Regexp) || value.match?(node[:name]) }
183
+ node_filter(:placeholder) { |node, value| !value.is_a?(Regexp) || value.match?(node[:placeholder]) }
184
+ node_filter(:validation_message) do |node, msg|
185
+ vm = node[:validationMessage]
186
+ (msg.is_a?(Regexp) ? msg.match?(vm) : vm == msg.to_s).tap do |res|
187
+ add_error("Expected validation message to be #{msg.inspect} but was #{vm}") unless res
188
+ end
189
+ end
177
190
 
178
- expression_filter(:name) { |xpath, val| xpath[XPath.attr(:name) == val] }
179
- expression_filter(:placeholder) { |xpath, val| xpath[XPath.attr(:placeholder) == val] }
191
+ expression_filter(:name) do |xpath, val|
192
+ builder(xpath).add_attribute_conditions(name: val)
193
+ end
194
+ expression_filter(:placeholder) do |xpath, val|
195
+ builder(xpath).add_attribute_conditions(placeholder: val)
196
+ end
180
197
  expression_filter(:disabled) { |xpath, val| val ? xpath : xpath[~XPath.attr(:disabled)] }
181
198
  expression_filter(:multiple) { |xpath, val| xpath[val ? XPath.attr(:multiple) : ~XPath.attr(:multiple)] }
182
199
 
@@ -190,7 +207,7 @@ Capybara::Selector::FilterSet.add(:_field) do
190
207
  desc
191
208
  end
192
209
 
193
- describe(:node_filters) do |checked: nil, unchecked: nil, disabled: nil, valid: nil, **|
210
+ describe(:node_filters) do |checked: nil, unchecked: nil, disabled: nil, valid: nil, validation_message: nil, **|
194
211
  desc, states = +'', []
195
212
  states << 'checked' if checked || (unchecked == false)
196
213
  states << 'not checked' if unchecked || (checked == false)
@@ -198,6 +215,7 @@ Capybara::Selector::FilterSet.add(:_field) do
198
215
  desc << " that is #{states.join(' and ')}" unless states.empty?
199
216
  desc << ' that is valid' if valid == true
200
217
  desc << ' that is invalid' if valid == false
218
+ desc << " with validation message #{validation_message.to_s.inspect}" if validation_message
201
219
  desc
202
220
  end
203
221
  end
@@ -1 +1 @@
1
- (function(){function u(e){var t=e.tagName.toUpperCase();if("OPTION"==t)return!0;if("INPUT"!=t)return!1;var r=e.type.toLowerCase();return"checkbox"==r||"radio"==r}function s(e){var t="selected",r=e.type&&e.type.toLowerCase();return"checkbox"!=r&&"radio"!=r||(t="checked"),!!e[t]}function c(e,t){var r=e.getAttributeNode(t);return r&&r.specified?r.value:null}var i=["allowfullscreen","allowpaymentrequest","allowusermedia","async","autofocus","autoplay","checked","compact","complete","controls","declare","default","defaultchecked","defaultselected","defer","disabled","ended","formnovalidate","hidden","indeterminate","iscontenteditable","ismap","itemscope","loop","multiple","muted","nohref","nomodule","noresize","noshade","novalidate","nowrap","open","paused","playsinline","pubdate","readonly","required","reversed","scoped","seamless","seeking","selected","truespeed","typemustmatch","willvalidate"],d={"class":"className",readonly:"readOnly"};return function f(e,t){var r=null,a=t.toLowerCase();if("style"==a)return(r=e.style)&&"string"!=typeof r&&(r=r.cssText),r;if(("selected"==a||"checked"==a)&&u(e))return s(e)?"true":null;if(tagName=e.tagName.toUpperCase(),"IMG"==tagName&&"src"==a||"A"==tagName&&"href"==a)return(r=c(e,a))&&(r=e[a]),r;if("spellcheck"==a){if(null===!(r=c(e,a))){if("false"==r.toLowerCase())return"false";if("true"==r.toLowerCase())return"true"}return e[a]+""}var l,n=d[t]||t;if(i.some(function(e){e==a}))return(r=!(null===(r=c(e,a)))||e[n])?"true":null;try{l=e[n]}catch(o){}return null!=(r=null==l||"object"==typeof l||"function"==typeof l?c(e,t):l)?r.toString():null}})()
1
+ (function(){function u(e){var t=e.tagName.toUpperCase();if("OPTION"==t)return!0;if("INPUT"!=t)return!1;var r=e.type.toLowerCase();return"checkbox"==r||"radio"==r}function s(e){var t="selected",r=e.type&&e.type.toLowerCase();return"checkbox"!=r&&"radio"!=r||(t="checked"),!!e[t]}function c(e,t){var r=e.getAttributeNode(t);return r&&r.specified?r.value:null}var i=["allowfullscreen","allowpaymentrequest","allowusermedia","async","autofocus","autoplay","checked","compact","complete","controls","declare","default","defaultchecked","defaultselected","defer","disabled","ended","formnovalidate","hidden","indeterminate","iscontenteditable","ismap","itemscope","loop","multiple","muted","nohref","nomodule","noresize","noshade","novalidate","nowrap","open","paused","playsinline","pubdate","readonly","required","reversed","scoped","seamless","seeking","selected","truespeed","typemustmatch","willvalidate"],d={"class":"className",readonly:"readOnly"};return function f(e,t){var r=null,a=t.toLowerCase();if("style"==a)return(r=e.style)&&"string"!=typeof r&&(r=r.cssText),r;if(("selected"==a||"checked"==a)&&u(e))return s(e)?"true":null;if(tagName=e.tagName.toUpperCase(),"IMG"==tagName&&"src"==a||"A"==tagName&&"href"==a)return(r=c(e,a))&&(r=e[a]),r;if("spellcheck"==a){if(null!==(r=c(e,a))){if("false"==r.toLowerCase())return"false";if("true"==r.toLowerCase())return"true"}return e[a]+""}var l,n=d[t]||t;if(i.some(function(e){e==a}))return(r=!(null===(r=c(e,a)))||e[n])?"true":null;try{l=e[n]}catch(o){}return null!=(r=null==l||"object"==typeof l||"function"==typeof l?c(e,t):l)?r.toString():null}})()
@@ -117,7 +117,7 @@
117
117
 
118
118
  if ("spellcheck" == name) {
119
119
  value = getAttributeValue(element, name);
120
- if (!value === null) {
120
+ if (!(value === null)) {
121
121
  if (value.toLowerCase() == "false") {
122
122
  return "false";
123
123
  } else if (value.toLowerCase() == "true") {
@@ -158,7 +158,7 @@
158
158
  // the overflow style of the body, and the body is really overflow:visible.
159
159
  var overflowElem = e;
160
160
  if (htmlOverflowStyle == "visible") {
161
- // Note: bodyElem will be null/undefined in SVG documents.
161
+ // NOTE: bodyElem will be null/undefined in SVG documents.
162
162
  if (e == htmlElem && bodyElem) {
163
163
  overflowElem = bodyElem;
164
164
  } else if (e == bodyElem) {
@@ -12,17 +12,44 @@ 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('~> 4.0.0.alpha6')
16
+
15
17
  attr_reader :app, :options
16
18
 
17
19
  class << self
20
+ attr_reader :selenium_webdriver_version
21
+
18
22
  def load_selenium
19
23
  require 'selenium-webdriver'
20
24
  require 'capybara/selenium/logger_suppressor'
21
25
  require 'capybara/selenium/patches/atoms'
22
26
  require 'capybara/selenium/patches/is_displayed'
23
- warn "Warning: You're using an unsupported version of selenium-webdriver, please upgrade." if Gem.loaded_specs['selenium-webdriver'].version < Gem::Version.new('3.5.0')
27
+ require 'capybara/selenium/patches/action_pauser'
28
+
29
+ # Look up the version of `selenium-webdriver` to
30
+ # see if it's a version we support.
31
+ #
32
+ # By default, we use Gem.loaded_specs to determine
33
+ # the version number. However, in some cases, such
34
+ # as when loading `selenium-webdriver` outside of
35
+ # Rubygems, we fall back to referencing
36
+ # Selenium::WebDriver::VERSION. Ideally we'd
37
+ # use the constant in all cases, but earlier versions
38
+ # of `selenium-webdriver` didn't provide the constant.
39
+ @selenium_webdriver_version =
40
+ if Gem.loaded_specs['selenium-webdriver']
41
+ Gem.loaded_specs['selenium-webdriver'].version
42
+ else
43
+ Gem::Version.new(Selenium::WebDriver::VERSION)
44
+ end
45
+
46
+ unless Gem::Requirement.new('>= 3.142.7').satisfied_by? @selenium_webdriver_version
47
+ warn "Warning: You're using an unsupported version of selenium-webdriver, please upgrade."
48
+ end
49
+
50
+ @selenium_webdriver_version
24
51
  rescue LoadError => e
25
- raise e unless e.message.match?(/selenium-webdriver/)
52
+ raise e unless e.message.include?('selenium-webdriver')
26
53
 
27
54
  raise LoadError, "Capybara's selenium driver is unable to load `selenium-webdriver`, please install the gem and add `gem 'selenium-webdriver'` to your Gemfile if you are using bundler."
28
55
  end
@@ -46,7 +73,15 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
46
73
  end
47
74
  end
48
75
  processed_options = options.reject { |key, _val| SPECIAL_OPTIONS.include?(key) }
49
- @browser = Selenium::WebDriver.for(options[:browser], processed_options)
76
+
77
+ @browser = if options[:browser] == :firefox &&
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
50
85
 
51
86
  specialize_driver
52
87
  setup_exit_handler
@@ -55,6 +90,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
55
90
  end
56
91
 
57
92
  def initialize(app, **options)
93
+ super()
58
94
  self.class.load_selenium
59
95
  @app = app
60
96
  @browser = nil
@@ -82,6 +118,8 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
82
118
 
83
119
  def html
84
120
  browser.page_source
121
+ rescue Selenium::WebDriver::Error::JavascriptError => e
122
+ raise unless e.message.include?('documentElement is null')
85
123
  end
86
124
 
87
125
  def title
@@ -110,6 +148,15 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
110
148
  unwrap_script_result(result)
111
149
  end
112
150
 
151
+ def active_element
152
+ build_node(native_active_element)
153
+ end
154
+
155
+ def send_keys(*args)
156
+ # Should this call the specialized nodes rather than native???
157
+ native_active_element.send_keys(*args)
158
+ end
159
+
113
160
  def save_screenshot(path, **_options)
114
161
  browser.save_screenshot(path)
115
162
  end
@@ -143,7 +190,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
143
190
 
144
191
  switch_to_frame(:parent)
145
192
  begin
146
- return frame.base.obscured?(x: x, y: y)
193
+ frame.base.obscured?(x: x, y: y)
147
194
  ensure
148
195
  switch_to_frame(frame)
149
196
  end
@@ -207,7 +254,13 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
207
254
  end
208
255
 
209
256
  def open_new_window(kind = :tab)
210
- browser.manage.new_window(kind)
257
+ if browser.switch_to.respond_to?(:new_window)
258
+ handle = current_window_handle
259
+ browser.switch_to.new_window(kind)
260
+ switch_to_window(handle)
261
+ else
262
+ browser.manage.new_window(kind)
263
+ end
211
264
  rescue NoMethodError, Selenium::WebDriver::Error::WebDriverError
212
265
  # If not supported by the driver or browser default to using JS
213
266
  browser.execute_script('window.open();')
@@ -219,7 +272,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
219
272
 
220
273
  def accept_modal(_type, **options)
221
274
  yield if block_given?
222
- modal = find_modal(options)
275
+ modal = find_modal(**options)
223
276
 
224
277
  modal.send_keys options[:with] if options[:with]
225
278
 
@@ -230,7 +283,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
230
283
 
231
284
  def dismiss_modal(_type, **options)
232
285
  yield if block_given?
233
- modal = find_modal(options)
286
+ modal = find_modal(**options)
234
287
  message = modal.text
235
288
  modal.dismiss
236
289
  message
@@ -238,7 +291,8 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
238
291
 
239
292
  def quit
240
293
  @browser&.quit
241
- rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED # rubocop:disable Lint/HandleExceptions
294
+ rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED,
295
+ Selenium::WebDriver::Error::InvalidSessionIdError
242
296
  # Browser must have already gone
243
297
  rescue Selenium::WebDriver::Error::UnknownError => e
244
298
  unless silenced_unknown_error_message?(e.message) # Most likely already gone
@@ -250,7 +304,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
250
304
  end
251
305
 
252
306
  def invalid_element_errors
253
- @invalid_element_errors ||= begin
307
+ @invalid_element_errors ||=
254
308
  [
255
309
  ::Selenium::WebDriver::Error::StaleElementReferenceError,
256
310
  ::Selenium::WebDriver::Error::ElementNotInteractableError,
@@ -270,7 +324,6 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
270
324
  end
271
325
  end
272
326
  end
273
- end
274
327
  end
275
328
 
276
329
  def no_such_window_error
@@ -287,10 +340,14 @@ private
287
340
  args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg }
288
341
  end
289
342
 
343
+ def native_active_element
344
+ browser.switch_to.active_element
345
+ end
346
+
290
347
  def clear_browser_state
291
348
  delete_all_cookies
292
349
  clear_storage
293
- rescue *clear_browser_state_errors # rubocop:disable Lint/HandleExceptions
350
+ rescue *clear_browser_state_errors
294
351
  # delete_all_cookies fails when we've previously gone
295
352
  # to about:blank, so we rescue this error and do nothing
296
353
  # instead.
@@ -314,7 +371,7 @@ private
314
371
  def clear_storage
315
372
  clear_session_storage unless options[:clear_session_storage] == false
316
373
  clear_local_storage unless options[:clear_local_storage] == false
317
- rescue Selenium::WebDriver::Error::JavascriptError # rubocop:disable Lint/HandleExceptions
374
+ rescue Selenium::WebDriver::Error::JavascriptError
318
375
  # session/local storage may not be available if on non-http pages (e.g. about:blank)
319
376
  end
320
377
 
@@ -325,7 +382,9 @@ private
325
382
  begin
326
383
  @browser&.execute_script('window.sessionStorage.clear()')
327
384
  rescue # rubocop:disable Style/RescueStandardError
328
- warn 'sessionStorage clear requested but is not supported by this driver' unless options[:clear_session_storage].nil?
385
+ unless options[:clear_session_storage].nil?
386
+ warn 'sessionStorage clear requested but is not supported by this driver'
387
+ end
329
388
  end
330
389
  end
331
390
  end
@@ -337,7 +396,9 @@ private
337
396
  begin
338
397
  @browser&.execute_script('window.localStorage.clear()')
339
398
  rescue # rubocop:disable Style/RescueStandardError
340
- warn 'localStorage clear requested but is not supported by this driver' unless options[:clear_local_storage].nil?
399
+ unless options[:clear_local_storage].nil?
400
+ warn 'localStorage clear requested but is not supported by this driver'
401
+ end
341
402
  end
342
403
  end
343
404
  end
@@ -346,7 +407,7 @@ private
346
407
  @browser.navigate.to(url)
347
408
  sleep 0.1 # slight wait for alert
348
409
  @browser.switch_to.alert.accept
349
- rescue modal_error # rubocop:disable Lint/HandleExceptions
410
+ rescue modal_error
350
411
  # alert now gone, should mean navigation happened
351
412
  end
352
413
 
@@ -378,7 +439,9 @@ private
378
439
  alert = @browser.switch_to.alert
379
440
  regexp = text.is_a?(Regexp) ? text : Regexp.new(Regexp.escape(text.to_s))
380
441
  matched = alert.text.match?(regexp)
381
- raise Capybara::ModalNotFound, "Unable to find modal dialog with #{text} - found '#{alert.text}' instead." unless matched
442
+ unless matched
443
+ raise Capybara::ModalNotFound, "Unable to find modal dialog with #{text} - found '#{alert.text}' instead."
444
+ end
382
445
 
383
446
  alert
384
447
  end
@@ -7,27 +7,25 @@ module Capybara::Selenium::Driver::ChromeDriver
7
7
  def self.extended(base)
8
8
  bridge = base.send(:bridge)
9
9
  bridge.extend Capybara::Selenium::ChromeLogs unless bridge.respond_to?(:log)
10
- bridge.extend Capybara::Selenium::IsDisplayed unless bridge.commands(:is_element_displayed)
10
+ bridge.extend Capybara::Selenium::IsDisplayed unless bridge.send(:commands, :is_element_displayed)
11
11
  base.options[:native_displayed] = false if base.options[:native_displayed].nil?
12
12
  end
13
13
 
14
14
  def fullscreen_window(handle)
15
15
  within_given_window(handle) do
16
- begin
17
- super
18
- rescue NoMethodError => e
19
- raise unless e.message.match?(/full_screen_window/)
20
-
21
- result = bridge.http.call(:post, "session/#{bridge.session_id}/window/fullscreen", {})
22
- result['value']
23
- end
16
+ super
17
+ rescue NoMethodError => e
18
+ raise unless e.message.include?('full_screen_window')
19
+
20
+ result = bridge.http.call(:post, "session/#{bridge.session_id}/window/fullscreen", {})
21
+ result['value']
24
22
  end
25
23
  end
26
24
 
27
25
  def resize_window_to(handle, width, height)
28
26
  super
29
27
  rescue Selenium::WebDriver::Error::UnknownError => e
30
- raise unless e.message.match?(/failed to change window state/)
28
+ raise unless e.message.include?('failed to change window state')
31
29
 
32
30
  # Chromedriver doesn't wait long enough for state to change when coming out of fullscreen
33
31
  # and raises unnecessary error. Wait a bit and try again.
@@ -40,7 +38,7 @@ module Capybara::Selenium::Driver::ChromeDriver
40
38
  return unless @browser
41
39
 
42
40
  switch_to_window(window_handles.first)
43
- window_handles.slice(1..-1).each { |win| close_window(win) }
41
+ window_handles.slice(1..).each { |win| close_window(win) }
44
42
  return super if chromedriver_version < 73
45
43
 
46
44
  timer = Capybara::Helpers.timer(expire_in: 10)
@@ -65,7 +63,7 @@ private
65
63
  end
66
64
 
67
65
  def clear_all_storage?
68
- storage_clears.none? { |s| s == false }
66
+ storage_clears.none? false
69
67
  end
70
68
 
71
69
  def uniform_storage_clear?
@@ -96,7 +94,7 @@ private
96
94
 
97
95
  def execute_cdp(cmd, params = {})
98
96
  if browser.respond_to? :execute_cdp
99
- browser.execute_cdp(cmd, params)
97
+ browser.execute_cdp(cmd, **params)
100
98
  else
101
99
  args = { cmd: cmd, params: params }
102
100
  result = bridge.http.call(:post, "session/#{bridge.session_id}/goog/cdp/execute", args)
@@ -5,7 +5,7 @@ require 'capybara/selenium/nodes/edge_node'
5
5
  module Capybara::Selenium::Driver::EdgeDriver
6
6
  def self.extended(base)
7
7
  bridge = base.send(:bridge)
8
- bridge.extend Capybara::Selenium::IsDisplayed unless bridge.commands(:is_element_displayed)
8
+ bridge.extend Capybara::Selenium::IsDisplayed unless bridge.send(:commands, :is_element_displayed)
9
9
  base.options[:native_displayed] = false if base.options[:native_displayed].nil?
10
10
  end
11
11
 
@@ -13,21 +13,19 @@ module Capybara::Selenium::Driver::EdgeDriver
13
13
  return super if edgedriver_version < 75
14
14
 
15
15
  within_given_window(handle) do
16
- begin
17
- super
18
- rescue NoMethodError => e
19
- raise unless e.message.match?(/full_screen_window/)
20
-
21
- result = bridge.http.call(:post, "session/#{bridge.session_id}/window/fullscreen", {})
22
- result['value']
23
- end
16
+ super
17
+ rescue NoMethodError => e
18
+ raise unless e.message.include?('full_screen_window')
19
+
20
+ result = bridge.http.call(:post, "session/#{bridge.session_id}/window/fullscreen", {})
21
+ result['value']
24
22
  end
25
23
  end
26
24
 
27
25
  def resize_window_to(handle, width, height)
28
26
  super
29
27
  rescue Selenium::WebDriver::Error::UnknownError => e
30
- raise unless e.message.match?(/failed to change window state/)
28
+ raise unless e.message.include?('failed to change window state')
31
29
 
32
30
  # Chromedriver doesn't wait long enough for state to change when coming out of fullscreen
33
31
  # and raises unnecessary error. Wait a bit and try again.
@@ -41,7 +39,7 @@ module Capybara::Selenium::Driver::EdgeDriver
41
39
  return unless @browser
42
40
 
43
41
  switch_to_window(window_handles.first)
44
- window_handles.slice(1..-1).each { |win| close_window(win) }
42
+ window_handles.slice(1..).each { |win| close_window(win) }
45
43
 
46
44
  timer = Capybara::Helpers.timer(expire_in: 10)
47
45
  begin
@@ -74,7 +72,7 @@ private
74
72
  end
75
73
 
76
74
  def clear_all_storage?
77
- storage_clears.none? { |s| s == false }
75
+ storage_clears.none? false
78
76
  end
79
77
 
80
78
  def uniform_storage_clear?
@@ -6,7 +6,7 @@ module Capybara::Selenium::Driver::FirefoxDriver
6
6
  def self.extended(driver)
7
7
  driver.extend Capybara::Selenium::Driver::W3CFirefoxDriver if w3c?(driver)
8
8
  bridge = driver.send(:bridge)
9
- bridge.extend Capybara::Selenium::IsDisplayed unless bridge.commands(:is_element_displayed)
9
+ bridge.extend Capybara::Selenium::IsDisplayed unless bridge.send(:commands, :is_element_displayed)
10
10
  end
11
11
 
12
12
  def self.w3c?(driver)
@@ -46,13 +46,13 @@ module Capybara::Selenium::Driver::W3CFirefoxDriver
46
46
  begin
47
47
  # Firefox 68 hangs if we try to switch windows while a modal is visible
48
48
  browser.switch_to.alert&.dismiss
49
- rescue Selenium::WebDriver::Error::NoSuchAlertError # rubocop:disable Lint/HandleExceptions
49
+ rescue Selenium::WebDriver::Error::NoSuchAlertError
50
50
  # Swallow
51
51
  end
52
52
  end
53
53
 
54
54
  switch_to_window(window_handles.first)
55
- window_handles.slice(1..-1).each { |win| close_window(win) }
55
+ window_handles.slice(1..).each { |win| close_window(win) }
56
56
  super
57
57
  end
58
58
 
@@ -61,7 +61,7 @@ module Capybara::Selenium::Driver::W3CFirefoxDriver
61
61
  accept_modal :confirm, wait: 0.1 do
62
62
  super
63
63
  end
64
- rescue Capybara::ModalNotFound # rubocop:disable Lint/HandleExceptions
64
+ rescue Capybara::ModalNotFound
65
65
  # No modal was opened - page has refreshed - ignore
66
66
  end
67
67
 
@@ -28,7 +28,7 @@ module Capybara
28
28
  hints_js, functions = build_hints_js(uses_visibility, styles, position)
29
29
  return [] unless functions.any?
30
30
 
31
- es_context.execute_script(hints_js, elements).map! do |results|
31
+ (es_context.execute_script(hints_js, elements) || []).map! do |results|
32
32
  hint = {}
33
33
  hint[:style] = results.pop if functions.include?(:style_func)
34
34
  hint[:position] = results.pop if functions.include?(:position_func)
@@ -100,9 +100,9 @@ module Capybara
100
100
  def is_displayed_atom # rubocop:disable Naming/PredicateName
101
101
  @@is_displayed_atom ||= begin # rubocop:disable Style/ClassVars
102
102
  browser.send(:bridge).send(:read_atom, 'isDisplayed')
103
- rescue StandardError
104
- # If the atom doesn't exist or other error
105
- ''
103
+ rescue StandardError
104
+ # If the atom doesn't exist or other error
105
+ ''
106
106
  end
107
107
  end
108
108
  end