capybara 3.3.1 → 3.4.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.
Files changed (159) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +16 -0
  3. data/README.md +5 -7
  4. data/lib/capybara.rb +7 -6
  5. data/lib/capybara/config.rb +1 -1
  6. data/lib/capybara/dsl.rb +2 -2
  7. data/lib/capybara/helpers.rb +3 -3
  8. data/lib/capybara/minitest/spec.rb +3 -3
  9. data/lib/capybara/node/actions.rb +18 -18
  10. data/lib/capybara/node/base.rb +1 -1
  11. data/lib/capybara/node/element.rb +2 -2
  12. data/lib/capybara/node/finders.rb +6 -6
  13. data/lib/capybara/node/matchers.rb +5 -5
  14. data/lib/capybara/node/simple.rb +2 -2
  15. data/lib/capybara/queries/ancestor_query.rb +1 -1
  16. data/lib/capybara/queries/base_query.rb +12 -11
  17. data/lib/capybara/queries/current_path_query.rb +1 -1
  18. data/lib/capybara/queries/selector_query.rb +39 -15
  19. data/lib/capybara/queries/sibling_query.rb +1 -1
  20. data/lib/capybara/queries/text_query.rb +1 -1
  21. data/lib/capybara/rack_test/browser.rb +7 -7
  22. data/lib/capybara/rack_test/driver.rb +1 -1
  23. data/lib/capybara/rack_test/form.rb +7 -7
  24. data/lib/capybara/rack_test/node.rb +16 -16
  25. data/lib/capybara/rails.rb +1 -1
  26. data/lib/capybara/result.rb +8 -4
  27. data/lib/capybara/rspec/features.rb +4 -4
  28. data/lib/capybara/rspec/matchers.rb +6 -6
  29. data/lib/capybara/selector.rb +106 -90
  30. data/lib/capybara/selector/css.rb +4 -4
  31. data/lib/capybara/selector/filter_set.rb +52 -8
  32. data/lib/capybara/selector/selector.rb +39 -15
  33. data/lib/capybara/selenium/driver.rb +10 -10
  34. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +8 -0
  35. data/lib/capybara/selenium/node.rb +9 -10
  36. data/lib/capybara/selenium/nodes/chrome_node.rb +18 -0
  37. data/lib/capybara/selenium/nodes/marionette_node.rb +32 -7
  38. data/lib/capybara/server.rb +3 -3
  39. data/lib/capybara/server/animation_disabler.rb +1 -1
  40. data/lib/capybara/server/middleware.rb +1 -1
  41. data/lib/capybara/session.rb +23 -19
  42. data/lib/capybara/session/config.rb +18 -3
  43. data/lib/capybara/spec/public/test.js +1 -1
  44. data/lib/capybara/spec/session/accept_alert_spec.rb +10 -10
  45. data/lib/capybara/spec/session/accept_confirm_spec.rb +3 -3
  46. data/lib/capybara/spec/session/accept_prompt_spec.rb +9 -10
  47. data/lib/capybara/spec/session/all_spec.rb +33 -32
  48. data/lib/capybara/spec/session/ancestor_spec.rb +19 -19
  49. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +38 -38
  50. data/lib/capybara/spec/session/assert_current_path_spec.rb +16 -16
  51. data/lib/capybara/spec/session/assert_selector_spec.rb +53 -53
  52. data/lib/capybara/spec/session/assert_style_spec.rb +3 -3
  53. data/lib/capybara/spec/session/assert_text_spec.rb +31 -30
  54. data/lib/capybara/spec/session/assert_title_spec.rb +12 -12
  55. data/lib/capybara/spec/session/attach_file_spec.rb +51 -52
  56. data/lib/capybara/spec/session/body_spec.rb +6 -6
  57. data/lib/capybara/spec/session/check_spec.rb +52 -47
  58. data/lib/capybara/spec/session/choose_spec.rb +32 -32
  59. data/lib/capybara/spec/session/click_button_spec.rb +103 -103
  60. data/lib/capybara/spec/session/click_link_or_button_spec.rb +24 -23
  61. data/lib/capybara/spec/session/click_link_spec.rb +49 -48
  62. data/lib/capybara/spec/session/current_scope_spec.rb +7 -7
  63. data/lib/capybara/spec/session/current_url_spec.rb +26 -27
  64. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +3 -3
  65. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +2 -2
  66. data/lib/capybara/spec/session/element/assert_match_selector_spec.rb +8 -8
  67. data/lib/capybara/spec/session/element/match_css_spec.rb +10 -10
  68. data/lib/capybara/spec/session/element/match_xpath_spec.rb +6 -6
  69. data/lib/capybara/spec/session/element/matches_selector_spec.rb +51 -51
  70. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +7 -7
  71. data/lib/capybara/spec/session/evaluate_script_spec.rb +15 -8
  72. data/lib/capybara/spec/session/execute_script_spec.rb +7 -7
  73. data/lib/capybara/spec/session/fill_in_spec.rb +43 -42
  74. data/lib/capybara/spec/session/find_button_spec.rb +23 -23
  75. data/lib/capybara/spec/session/find_by_id_spec.rb +7 -7
  76. data/lib/capybara/spec/session/find_field_spec.rb +32 -30
  77. data/lib/capybara/spec/session/find_link_spec.rb +21 -21
  78. data/lib/capybara/spec/session/find_spec.rb +153 -135
  79. data/lib/capybara/spec/session/first_spec.rb +41 -41
  80. data/lib/capybara/spec/session/frame/frame_title_spec.rb +5 -5
  81. data/lib/capybara/spec/session/frame/frame_url_spec.rb +5 -5
  82. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +17 -17
  83. data/lib/capybara/spec/session/frame/within_frame_spec.rb +31 -17
  84. data/lib/capybara/spec/session/go_back_spec.rb +1 -1
  85. data/lib/capybara/spec/session/go_forward_spec.rb +1 -1
  86. data/lib/capybara/spec/session/has_all_selectors_spec.rb +17 -17
  87. data/lib/capybara/spec/session/has_button_spec.rb +13 -13
  88. data/lib/capybara/spec/session/has_css_spec.rb +133 -131
  89. data/lib/capybara/spec/session/has_current_path_spec.rb +29 -29
  90. data/lib/capybara/spec/session/has_field_spec.rb +58 -58
  91. data/lib/capybara/spec/session/has_link_spec.rb +4 -4
  92. data/lib/capybara/spec/session/has_none_selectors_spec.rb +24 -24
  93. data/lib/capybara/spec/session/has_select_spec.rb +43 -43
  94. data/lib/capybara/spec/session/has_selector_spec.rb +71 -71
  95. data/lib/capybara/spec/session/has_style_spec.rb +3 -3
  96. data/lib/capybara/spec/session/has_table_spec.rb +4 -4
  97. data/lib/capybara/spec/session/has_text_spec.rb +53 -52
  98. data/lib/capybara/spec/session/has_title_spec.rb +14 -14
  99. data/lib/capybara/spec/session/has_xpath_spec.rb +39 -38
  100. data/lib/capybara/spec/session/headers_spec.rb +1 -1
  101. data/lib/capybara/spec/session/html_spec.rb +6 -6
  102. data/lib/capybara/spec/session/node_spec.rb +129 -123
  103. data/lib/capybara/spec/session/node_wrapper_spec.rb +10 -7
  104. data/lib/capybara/spec/session/refresh_spec.rb +4 -7
  105. data/lib/capybara/spec/session/reset_session_spec.rb +28 -28
  106. data/lib/capybara/spec/session/response_code_spec.rb +1 -1
  107. data/lib/capybara/spec/session/save_and_open_page_spec.rb +2 -2
  108. data/lib/capybara/spec/session/save_page_spec.rb +37 -37
  109. data/lib/capybara/spec/session/save_screenshot_spec.rb +6 -6
  110. data/lib/capybara/spec/session/screenshot_spec.rb +2 -2
  111. data/lib/capybara/spec/session/select_spec.rb +81 -81
  112. data/lib/capybara/spec/session/selectors_spec.rb +17 -17
  113. data/lib/capybara/spec/session/sibling_spec.rb +9 -9
  114. data/lib/capybara/spec/session/text_spec.rb +23 -23
  115. data/lib/capybara/spec/session/title_spec.rb +5 -5
  116. data/lib/capybara/spec/session/uncheck_spec.rb +24 -20
  117. data/lib/capybara/spec/session/unselect_spec.rb +37 -37
  118. data/lib/capybara/spec/session/visit_spec.rb +48 -49
  119. data/lib/capybara/spec/session/window/current_window_spec.rb +1 -1
  120. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +16 -16
  121. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +2 -2
  122. data/lib/capybara/spec/session/window/window_spec.rb +4 -4
  123. data/lib/capybara/spec/session/window/within_window_spec.rb +14 -14
  124. data/lib/capybara/spec/session/within_spec.rb +41 -41
  125. data/lib/capybara/spec/spec_helper.rb +11 -9
  126. data/lib/capybara/spec/test_app.rb +18 -17
  127. data/lib/capybara/spec/views/form.erb +29 -31
  128. data/lib/capybara/spec/views/with_html.erb +2 -2
  129. data/lib/capybara/version.rb +1 -1
  130. data/spec/basic_node_spec.rb +23 -23
  131. data/spec/capybara_spec.rb +20 -20
  132. data/spec/css_splitter_spec.rb +7 -7
  133. data/spec/dsl_spec.rb +37 -32
  134. data/spec/filter_set_spec.rb +4 -4
  135. data/spec/fixtures/selenium_driver_rspec_failure.rb +1 -1
  136. data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
  137. data/spec/minitest_spec.rb +4 -4
  138. data/spec/minitest_spec_spec.rb +23 -23
  139. data/spec/per_session_config_spec.rb +5 -5
  140. data/spec/rack_test_spec.rb +44 -44
  141. data/spec/result_spec.rb +14 -14
  142. data/spec/rspec/features_spec.rb +13 -13
  143. data/spec/rspec/scenarios_spec.rb +4 -4
  144. data/spec/rspec/shared_spec_matchers.rb +282 -281
  145. data/spec/rspec/views_spec.rb +3 -3
  146. data/spec/rspec_matchers_spec.rb +10 -10
  147. data/spec/rspec_spec.rb +29 -29
  148. data/spec/selector_spec.rb +64 -64
  149. data/spec/selenium_spec_chrome.rb +14 -22
  150. data/spec/selenium_spec_chrome_remote.rb +28 -8
  151. data/spec/selenium_spec_edge.rb +9 -4
  152. data/spec/selenium_spec_firefox_remote.rb +87 -0
  153. data/spec/selenium_spec_ie.rb +9 -4
  154. data/spec/selenium_spec_marionette.rb +42 -18
  155. data/spec/server_spec.rb +29 -27
  156. data/spec/session_spec.rb +17 -17
  157. data/spec/shared_selenium_session.rb +70 -52
  158. data/spec/spec_helper.rb +1 -1
  159. metadata +4 -2
@@ -10,15 +10,15 @@ Capybara::Selector::FilterSet.add(:_field) do
10
10
  expression_filter(:name) { |xpath, val| xpath[XPath.attr(:name) == val] }
11
11
  expression_filter(:placeholder) { |xpath, val| xpath[XPath.attr(:placeholder) == val] }
12
12
 
13
- describe do |checked: nil, unchecked: nil, disabled: nil, multiple: nil, **_options|
14
- desc, states = +"", []
13
+ describe(:node_filters) do |checked: nil, unchecked: nil, disabled: nil, multiple: nil, **|
14
+ desc, states = +'', []
15
15
  states << 'checked' if checked || (unchecked == false)
16
16
  states << 'not checked' if unchecked || (checked == false)
17
17
  states << 'disabled' if disabled == true
18
18
  states << 'not disabled' if disabled == false
19
19
  desc << " that is #{states.join(' and ')}" unless states.empty?
20
- desc << " with the multiple attribute" if multiple == true
21
- desc << " without the multiple attribute" if multiple == false
20
+ desc << ' with the multiple attribute' if multiple == true
21
+ desc << ' without the multiple attribute' if multiple == false
22
22
  desc
23
23
  end
24
24
  end
@@ -58,26 +58,34 @@ Capybara.add_selector(:field) do
58
58
  node_filter(:with) do |node, with|
59
59
  with.is_a?(Regexp) ? node.value =~ with : node.value == with.to_s
60
60
  end
61
- describe do |type: nil, **options|
62
- desc = +""
61
+
62
+ describe_expression_filters do |type: nil, **options|
63
+ desc = +''
63
64
  (expression_filters.keys - [:type]).each { |ef| desc << " with #{ef} #{options[ef]}" if options.key?(ef) }
64
65
  desc << " of type #{type.inspect}" if type
65
- desc << " with value #{options[:with].to_s.inspect}" if options.key?(:with)
66
66
  desc
67
67
  end
68
+
69
+ describe_node_filters do |**options|
70
+ " with value #{options[:with].to_s.inspect}" if options.key?(:with)
71
+ end
68
72
  end
69
73
 
70
74
  Capybara.add_selector(:fieldset) do
71
- xpath(:legend) do |locator, legend: nil, **_options|
75
+ xpath(:legend) do |locator, legend: nil, **|
76
+ locator_matchers = (XPath.attr(:id) == locator.to_s) | XPath.child(:legend)[XPath.string.n.is(locator.to_s)]
77
+ locator_matchers |= XPath.attr(test_id) == locator if test_id
72
78
  xpath = XPath.descendant(:fieldset)
73
- xpath = xpath[(XPath.attr(:id) == locator.to_s) | XPath.child(:legend)[XPath.string.n.is(locator.to_s)]] unless locator.nil?
79
+ xpath = xpath[locator_matchers] unless locator.nil?
74
80
  xpath = xpath[XPath.child(:legend)[XPath.string.n.is(legend)]] if legend
75
81
  xpath
76
82
  end
83
+
84
+ node_filter(:disabled, :boolean) { |node, value| !(value ^ node.disabled?) }
77
85
  end
78
86
 
79
87
  Capybara.add_selector(:link) do
80
- xpath(:title, :alt) do |locator, href: true, enable_aria_label: false, alt: nil, title: nil, **_options|
88
+ xpath(:title, :alt) do |locator, href: true, alt: nil, title: nil, **|
81
89
  xpath = XPath.descendant(:a)
82
90
  xpath = xpath[
83
91
  case href
@@ -99,6 +107,7 @@ Capybara.add_selector(:link) do
99
107
  XPath.attr(:title).is(locator),
100
108
  XPath.descendant(:img)[XPath.attr(:alt).is(locator)]]
101
109
  matchers << XPath.attr(:'aria-label').is(locator) if enable_aria_label
110
+ matchers << XPath.attr(test_id) == locator if test_id
102
111
  xpath = xpath[matchers.reduce(:|)]
103
112
  end
104
113
 
@@ -121,27 +130,33 @@ Capybara.add_selector(:link) do
121
130
  expr[mod]
122
131
  end
123
132
 
124
- describe do |**options|
125
- desc = +""
126
- desc << " with href #{options[:href].inspect}" if options[:href]
127
- desc << " with no href attribute" if options.fetch(:href, true).nil?
133
+ describe_expression_filters do |**options|
134
+ desc = +''
135
+ desc << " with href #{options[:href].inspect}" if options[:href] && !options[:href].is_a?(Regexp)
136
+ desc << ' with no href attribute' if options.fetch(:href, true).nil?
137
+ desc
138
+ end
139
+
140
+ describe_node_filters do |href: nil, **|
141
+ " with href matching #{href.inspect}" if href.is_a? Regexp
128
142
  end
129
143
  end
130
144
 
131
145
  Capybara.add_selector(:button) do
132
- xpath(:value, :title, :type) do |locator, enable_aria_label: false, **options|
146
+ xpath(:value, :title, :type) do |locator, **options|
133
147
  input_btn_xpath = XPath.descendant(:input)[XPath.attr(:type).one_of('submit', 'reset', 'image', 'button')]
134
148
  btn_xpath = XPath.descendant(:button)
135
149
  image_btn_xpath = XPath.descendant(:input)[XPath.attr(:type) == 'image']
136
150
 
137
151
  unless locator.nil?
138
152
  locator = locator.to_s
139
- locator_matches = XPath.attr(:id).equals(locator) | XPath.attr(:value).is(locator) | XPath.attr(:title).is(locator)
140
- locator_matches |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
153
+ locator_matchers = XPath.attr(:id).equals(locator) | XPath.attr(:value).is(locator) | XPath.attr(:title).is(locator)
154
+ locator_matchers |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
155
+ locator_matchers |= XPath.attr(test_id) == locator if test_id
141
156
 
142
- input_btn_xpath = input_btn_xpath[locator_matches]
157
+ input_btn_xpath = input_btn_xpath[locator_matchers]
143
158
 
144
- btn_xpath = btn_xpath[locator_matches | XPath.string.n.is(locator) | XPath.descendant(:img)[XPath.attr(:alt).is(locator)]]
159
+ btn_xpath = btn_xpath[locator_matchers | XPath.string.n.is(locator) | XPath.descendant(:img)[XPath.attr(:alt).is(locator)]]
145
160
 
146
161
  alt_matches = XPath.attr(:alt).is(locator)
147
162
  alt_matches |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
@@ -157,27 +172,27 @@ Capybara.add_selector(:button) do
157
172
 
158
173
  node_filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| !(value ^ node.disabled?) }
159
174
 
160
- describe do |disabled: nil, **options|
161
- desc = +""
162
- desc << " that is disabled" if disabled == true
163
- desc << describe_all_expression_filters(options)
164
- desc
175
+ describe_expression_filters
176
+ describe_node_filters do |disabled: nil, **|
177
+ ' that is disabled' if disabled == true
165
178
  end
166
179
  end
167
180
 
168
181
  Capybara.add_selector(:link_or_button) do
169
- label "link or button"
182
+ label 'link or button'
170
183
  xpath do |locator, **options|
171
184
  self.class.all.values_at(:link, :button).map { |selector| selector.xpath.call(locator, options) }.reduce(:union)
172
185
  end
173
186
 
174
- node_filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| node.tag_name == "a" || !(value ^ node.disabled?) }
187
+ node_filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| node.tag_name == 'a' || !(value ^ node.disabled?) }
175
188
 
176
- describe { |disabled: nil, **_options| " that is disabled" if disabled == true }
189
+ describe_node_filters do |disabled: nil, **|
190
+ ' that is disabled' if disabled == true
191
+ end
177
192
  end
178
193
 
179
194
  Capybara.add_selector(:fillable_field) do
180
- label "field"
195
+ label 'field'
181
196
 
182
197
  xpath do |locator, **options|
183
198
  xpath = XPath.descendant(:input, :textarea)[!XPath.attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file')]
@@ -186,7 +201,7 @@ Capybara.add_selector(:fillable_field) do
186
201
 
187
202
  expression_filter(:type) do |expr, type|
188
203
  type = type.to_s
189
- if ['textarea'].include?(type)
204
+ if type == 'textarea'
190
205
  expr.self(type.to_sym)
191
206
  else
192
207
  expr[XPath.attr(:type) == type]
@@ -199,16 +214,14 @@ Capybara.add_selector(:fillable_field) do
199
214
  with.is_a?(Regexp) ? node.value =~ with : node.value == with.to_s
200
215
  end
201
216
 
202
- describe do |options|
203
- desc = +""
204
- desc << describe_all_expression_filters(options)
205
- desc << " with value #{options[:with].to_s.inspect}" if options.key?(:with)
206
- desc
217
+ describe_expression_filters
218
+ describe_node_filters do |**options|
219
+ " with value #{options[:with].to_s.inspect}" if options.key?(:with)
207
220
  end
208
221
  end
209
222
 
210
223
  Capybara.add_selector(:radio_button) do
211
- label "radio button"
224
+ label 'radio button'
212
225
 
213
226
  xpath do |locator, **options|
214
227
  xpath = XPath.descendant(:input)[XPath.attr(:type) == 'radio']
@@ -219,11 +232,9 @@ Capybara.add_selector(:radio_button) do
219
232
 
220
233
  node_filter(:option) { |node, value| node.value == value.to_s }
221
234
 
222
- describe do |option: nil, **options|
223
- desc = +""
224
- desc << " with value #{option.inspect}" if option
225
- desc << describe_all_expression_filters(options)
226
- desc
235
+ describe_expression_filters
236
+ describe_node_filters do |option: nil, **|
237
+ " with value #{option.inspect}" if option
227
238
  end
228
239
  end
229
240
 
@@ -237,16 +248,14 @@ Capybara.add_selector(:checkbox) do
237
248
 
238
249
  node_filter(:option) { |node, value| node.value == value.to_s }
239
250
 
240
- describe do |option: nil, **options|
241
- desc = +""
242
- desc << " with value #{option.inspect}" if option
243
- desc << describe_all_expression_filters(options)
244
- desc
251
+ describe_expression_filters
252
+ describe_node_filters do |option: nil, **|
253
+ " with value #{option.inspect}" if option
245
254
  end
246
255
  end
247
256
 
248
257
  Capybara.add_selector(:select) do
249
- label "select box"
258
+ label 'select box'
250
259
 
251
260
  xpath do |locator, **options|
252
261
  xpath = XPath.descendant(:select)
@@ -280,19 +289,24 @@ Capybara.add_selector(:select) do
280
289
  (Array(selected) - actual).empty?
281
290
  end
282
291
 
283
- describe do |options: nil, with_options: nil, selected: nil, with_selected: nil, **opts|
284
- desc = +""
285
- desc << " with options #{options.inspect}" if options
292
+ describe_expression_filters do |with_options: nil, **opts|
293
+ desc = +''
286
294
  desc << " with at least options #{with_options.inspect}" if with_options
295
+ desc << describe_all_expression_filters(opts)
296
+ desc
297
+ end
298
+
299
+ describe_node_filters do |options: nil, selected: nil, with_selected: nil, **|
300
+ desc = +''
301
+ desc << " with options #{options.inspect}" if options
287
302
  desc << " with #{selected.inspect} selected" if selected
288
303
  desc << " with at least #{with_selected.inspect} selected" if with_selected
289
- desc << describe_all_expression_filters(opts)
290
304
  desc
291
305
  end
292
306
  end
293
307
 
294
308
  Capybara.add_selector(:datalist_input) do
295
- label "input box with datalist completion"
309
+ label 'input box with datalist completion'
296
310
 
297
311
  xpath do |locator, **options|
298
312
  xpath = XPath.descendant(:input)[XPath.attr(:list)]
@@ -312,13 +326,16 @@ Capybara.add_selector(:datalist_input) do
312
326
  end
313
327
  end
314
328
 
315
- describe do |options: nil, with_options: nil, **opts|
316
- desc = +""
317
- desc << " with options #{options.inspect}" if options
329
+ describe_expression_filters do |with_options: nil, **opts|
330
+ desc = +''
318
331
  desc << " with at least options #{with_options.inspect}" if with_options
319
332
  desc << describe_all_expression_filters(opts)
320
333
  desc
321
334
  end
335
+
336
+ describe_node_filters do |options: nil, **|
337
+ " with options #{options.inspect}" if options
338
+ end
322
339
  end
323
340
 
324
341
  Capybara.add_selector(:option) do
@@ -331,8 +348,8 @@ Capybara.add_selector(:option) do
331
348
  node_filter(:disabled, :boolean) { |node, value| !(value ^ node.disabled?) }
332
349
  node_filter(:selected, :boolean) { |node, value| !(value ^ node.selected?) }
333
350
 
334
- describe do |**options|
335
- desc = +""
351
+ describe_node_filters do |**options|
352
+ desc = +''
336
353
  desc << " that is#{' not' unless options[:disabled]} disabled" if options.key?(:disabled)
337
354
  desc << " that is#{' not' unless options[:selected]} selected" if options.key?(:selected)
338
355
  desc
@@ -340,7 +357,7 @@ Capybara.add_selector(:option) do
340
357
  end
341
358
 
342
359
  Capybara.add_selector(:datalist_option) do
343
- label "datalist option"
360
+ label 'datalist option'
344
361
  visible(:all)
345
362
 
346
363
  xpath do |locator|
@@ -351,15 +368,13 @@ Capybara.add_selector(:datalist_option) do
351
368
 
352
369
  node_filter(:disabled, :boolean) { |node, value| !(value ^ node.disabled?) }
353
370
 
354
- describe do |**options|
355
- desc = +""
356
- desc << " that is#{' not' unless options[:disabled]} disabled" if options.key?(:disabled)
357
- desc
371
+ describe_node_filters do |**options|
372
+ " that is#{' not' unless options[:disabled]} disabled" if options.key?(:disabled)
358
373
  end
359
374
  end
360
375
 
361
376
  Capybara.add_selector(:file_field) do
362
- label "file field"
377
+ label 'file field'
363
378
  xpath do |locator, options|
364
379
  xpath = XPath.descendant(:input)[XPath.attr(:type) == 'file']
365
380
  locate_field(xpath, locator, options)
@@ -367,18 +382,18 @@ Capybara.add_selector(:file_field) do
367
382
 
368
383
  filter_set(:_field, %i[disabled multiple name])
369
384
 
370
- describe do |**options|
371
- desc = +""
372
- desc << describe_all_expression_filters(options)
373
- desc
374
- end
385
+ describe_expression_filters
375
386
  end
376
387
 
377
388
  Capybara.add_selector(:label) do
378
- label "label"
389
+ label 'label'
379
390
  xpath(:for) do |locator, options|
380
391
  xpath = XPath.descendant(:label)
381
- xpath = xpath[XPath.string.n.is(locator.to_s) | (XPath.attr(:id) == locator.to_s)] unless locator.nil?
392
+ unless locator.nil?
393
+ locator_matchers = XPath.string.n.is(locator.to_s) | (XPath.attr(:id) == locator.to_s)
394
+ locator_matchers |= XPath.attr(test_id) == locator if test_id
395
+ xpath = xpath[locator_matchers]
396
+ end
382
397
  if options.key?(:for) && !options[:for].is_a?(Capybara::Node::Element)
383
398
  with_attr = XPath.attr(:for) == options[:for].to_s
384
399
  labelable_elements = %i[button input keygen meter output progress select textarea]
@@ -401,45 +416,50 @@ Capybara.add_selector(:label) do
401
416
  end
402
417
  end
403
418
 
404
- describe do |**options|
405
- desc = +""
406
- desc << " for #{options[:for]}" if options[:for]
407
- desc
419
+ describe_expression_filters do |**options|
420
+ " for element with id of \"#{options[:for]}\"" if options.key?(:for) && !options[:for].is_a?(Capybara::Node::Element)
421
+ end
422
+ describe_node_filters do |**options|
423
+ " for element #{options[:for]}" if options[:for]&.is_a?(Capybara::Node::Element)
408
424
  end
409
425
  end
410
426
 
411
427
  Capybara.add_selector(:table) do
412
- xpath(:caption) do |locator, caption: nil, **_options|
428
+ xpath(:caption) do |locator, caption: nil, **|
413
429
  xpath = XPath.descendant(:table)
414
- xpath = xpath[(XPath.attr(:id) == locator.to_s) | XPath.descendant(:caption).is(locator.to_s)] unless locator.nil?
430
+ unless locator.nil?
431
+ locator_matchers = (XPath.attr(:id) == locator.to_s) | XPath.descendant(:caption).is(locator.to_s)
432
+ locator_matchers |= XPath.attr(test_id) == locator if test_id
433
+ xpath = xpath[locator_matchers]
434
+ end
415
435
  xpath = xpath[XPath.descendant(:caption) == caption] if caption
416
436
  xpath
417
437
  end
418
438
 
419
- describe do |caption: nil, **_options|
420
- desc = +""
421
- desc << " with caption #{caption}" if caption
422
- desc
439
+ describe_expression_filters do |caption: nil, **|
440
+ " with caption \"#{caption}\"" if caption
423
441
  end
424
442
  end
425
443
 
426
444
  Capybara.add_selector(:frame) do
427
445
  xpath(:name) do |locator, **options|
428
446
  xpath = XPath.descendant(:iframe).union(XPath.descendant(:frame))
429
- xpath = xpath[(XPath.attr(:id) == locator.to_s) | (XPath.attr(:name) == locator.to_s)] unless locator.nil?
447
+ unless locator.nil?
448
+ locator_matchers = (XPath.attr(:id) == locator.to_s) | (XPath.attr(:name) == locator.to_s)
449
+ locator_matchers |= XPath.attr(test_id) == locator if test_id
450
+ xpath = xpath[locator_matchers]
451
+ end
430
452
  xpath = expression_filters.keys.inject(xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
431
453
  xpath
432
454
  end
433
455
 
434
- describe do |name: nil, **_options|
435
- desc = +""
436
- desc << " with name #{name}" if name
437
- desc
456
+ describe_expression_filters do |name: nil, **|
457
+ " with name #{name}" if name
438
458
  end
439
459
  end
440
460
 
441
461
  Capybara.add_selector(:element) do
442
- xpath do |locator, **_options|
462
+ xpath do |locator, **|
443
463
  XPath.descendant((locator || '@').to_sym)
444
464
  end
445
465
 
@@ -458,10 +478,6 @@ Capybara.add_selector(:element) do
458
478
  val.is_a?(Regexp) ? node[name] =~ val : true
459
479
  end
460
480
 
461
- describe do |**options|
462
- desc = +""
463
- desc << describe_all_expression_filters(options)
464
- desc
465
- end
481
+ describe_expression_filters
466
482
  end
467
483
  # rubocop:enable Metrics/BlockLength
@@ -5,7 +5,7 @@ module Capybara
5
5
  class CSS
6
6
  def self.escape(str)
7
7
  value = str.dup
8
- out = +""
8
+ out = +''
9
9
  out << value.slice!(0...1) if value =~ /^[-_]/
10
10
  out << (value[0] =~ NMSTART ? value.slice!(0...1) : escape_char(value.slice!(0...1)))
11
11
  out << value.gsub(/[^a-zA-Z0-9_-]/) { |c| escape_char c }
@@ -13,7 +13,7 @@ module Capybara
13
13
  end
14
14
 
15
15
  def self.escape_char(c)
16
- c =~ %r{[ -/:-~]} ? "\\#{c}" : format("\\%06x", c.ord)
16
+ c =~ %r{[ -/:-~]} ? "\\#{c}" : format('\\%06x', c.ord)
17
17
  end
18
18
 
19
19
  def self.split(css)
@@ -31,7 +31,7 @@ module Capybara
31
31
  def split(css)
32
32
  selectors = []
33
33
  StringIO.open(css) do |str|
34
- selector = ""
34
+ selector = ''
35
35
  while (c = str.getc)
36
36
  case c
37
37
  when '['
@@ -44,7 +44,7 @@ module Capybara
44
44
  selector += c + str.getc
45
45
  when ','
46
46
  selectors << selector.strip
47
- selector = ""
47
+ selector = ''
48
48
  else
49
49
  selector += c
50
50
  end
@@ -5,13 +5,13 @@ require 'capybara/selector/filter'
5
5
  module Capybara
6
6
  class Selector
7
7
  class FilterSet
8
- attr_reader :descriptions, :node_filters, :expression_filters
8
+ attr_reader :node_filters, :expression_filters
9
9
 
10
10
  def initialize(name, &block)
11
11
  @name = name
12
- @descriptions = []
13
- @expression_filters = {}
14
12
  @node_filters = {}
13
+ @expression_filters = {}
14
+ @descriptions = Hash.new { |h, k| h[k] = [] }
15
15
  instance_eval(&block)
16
16
  end
17
17
 
@@ -24,13 +24,43 @@ module Capybara
24
24
  add_filter(name, Filters::ExpressionFilter, *types_and_options, &block)
25
25
  end
26
26
 
27
- def describe(&block)
28
- descriptions.push block
27
+ def describe(what = nil, &block)
28
+ case what
29
+ when nil
30
+ undeclared_descriptions.push block
31
+ when :node_filters
32
+ node_filter_descriptions.push block
33
+ when :expression_filters
34
+ expression_filter_descriptions.push block
35
+ else
36
+ raise ArgumentError, 'Unknown description type'
37
+ end
29
38
  end
30
39
 
31
- def description(**options)
40
+ def description(node_filters: true, expression_filters: true, **options)
32
41
  opts = options_with_defaults(options)
33
- @descriptions.map { |desc| desc.call(opts).to_s }.join
42
+ d = +''
43
+ d += undeclared_descriptions.map { |desc| desc.call(opts).to_s }.join
44
+ d += expression_filter_descriptions.map { |desc| desc.call(opts).to_s }.join if expression_filters
45
+ d += node_filter_descriptions.map { |desc| desc.call(opts).to_s }.join if node_filters
46
+ d
47
+ end
48
+
49
+ def descriptions
50
+ warn 'DEPRECATED: FilterSet#descriptions is deprecated without replacement'
51
+ [undeclared_descriptions, node_filter_descriptions, expression_filter_descriptions].flatten
52
+ end
53
+
54
+ def import(name, filters = nil)
55
+ f_set = self.class.all[name]
56
+ filter_selector = filters.nil? ? ->(*) { true } : ->(n, _) { filters.include? n }
57
+
58
+ expression_filters.merge!(f_set.expression_filters.select(&filter_selector))
59
+ node_filters.merge!(f_set.node_filters.select(&filter_selector))
60
+
61
+ f_set.undeclared_descriptions.each { |desc| describe(&desc) }
62
+ f_set.expression_filter_descriptions.each { |desc| describe(:expression_filters, &desc) }
63
+ f_set.node_filter_descriptions.each { |desc| describe(:node_filters, &desc) }
34
64
  end
35
65
 
36
66
  class << self
@@ -47,6 +77,20 @@ module Capybara
47
77
  end
48
78
  end
49
79
 
80
+ protected
81
+
82
+ def undeclared_descriptions
83
+ @descriptions[:undeclared]
84
+ end
85
+
86
+ def node_filter_descriptions
87
+ @descriptions[:node_filters]
88
+ end
89
+
90
+ def expression_filter_descriptions
91
+ @descriptions[:expression_filters]
92
+ end
93
+
50
94
  private
51
95
 
52
96
  def options_with_defaults(options)
@@ -61,7 +105,7 @@ module Capybara
61
105
 
62
106
  def add_filter(name, filter_class, *types, matcher: nil, **options, &block)
63
107
  types.each { |k| options[k] = true }
64
- raise "ArgumentError", ":default option is not supported for filters with a :matcher option" if matcher && options[:default]
108
+ raise 'ArgumentError', ':default option is not supported for filters with a :matcher option' if matcher && options[:default]
65
109
  if filter_class <= Filters::ExpressionFilter
66
110
  @expression_filters[name] = filter_class.new(name, matcher, block, options)
67
111
  else