capybara 2.18.0 → 3.0.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 (172) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +55 -1
  3. data/README.md +18 -17
  4. data/lib/capybara/config.rb +11 -58
  5. data/lib/capybara/cucumber.rb +2 -3
  6. data/lib/capybara/driver/base.rb +15 -16
  7. data/lib/capybara/driver/node.rb +5 -4
  8. data/lib/capybara/dsl.rb +1 -0
  9. data/lib/capybara/helpers.rb +19 -29
  10. data/lib/capybara/minitest/spec.rb +15 -14
  11. data/lib/capybara/minitest.rb +139 -138
  12. data/lib/capybara/node/actions.rb +60 -81
  13. data/lib/capybara/node/base.rb +11 -18
  14. data/lib/capybara/node/document.rb +2 -2
  15. data/lib/capybara/node/document_matchers.rb +8 -8
  16. data/lib/capybara/node/element.rb +30 -40
  17. data/lib/capybara/node/finders.rb +62 -70
  18. data/lib/capybara/node/matchers.rb +50 -71
  19. data/lib/capybara/node/simple.rb +11 -17
  20. data/lib/capybara/queries/ancestor_query.rb +11 -7
  21. data/lib/capybara/queries/base_query.rb +22 -18
  22. data/lib/capybara/queries/current_path_query.rb +8 -24
  23. data/lib/capybara/queries/match_query.rb +3 -7
  24. data/lib/capybara/queries/selector_query.rb +92 -95
  25. data/lib/capybara/queries/sibling_query.rb +4 -4
  26. data/lib/capybara/queries/text_query.rb +35 -35
  27. data/lib/capybara/queries/title_query.rb +8 -11
  28. data/lib/capybara/rack_test/browser.rb +15 -18
  29. data/lib/capybara/rack_test/css_handlers.rb +6 -4
  30. data/lib/capybara/rack_test/driver.rb +6 -10
  31. data/lib/capybara/rack_test/form.rb +50 -40
  32. data/lib/capybara/rack_test/node.rb +93 -63
  33. data/lib/capybara/rails.rb +2 -6
  34. data/lib/capybara/result.rb +22 -22
  35. data/lib/capybara/rspec/compound.rb +5 -10
  36. data/lib/capybara/rspec/features.rb +17 -48
  37. data/lib/capybara/rspec/matcher_proxies.rb +31 -15
  38. data/lib/capybara/rspec/matchers.rb +70 -61
  39. data/lib/capybara/rspec.rb +5 -10
  40. data/lib/capybara/selector/css.rb +6 -11
  41. data/lib/capybara/selector/filter.rb +1 -17
  42. data/lib/capybara/selector/filter_set.rb +18 -15
  43. data/lib/capybara/selector/filters/base.rb +7 -6
  44. data/lib/capybara/selector/filters/expression_filter.rb +6 -23
  45. data/lib/capybara/selector/filters/node_filter.rb +2 -12
  46. data/lib/capybara/selector/selector.rb +28 -34
  47. data/lib/capybara/selector.rb +129 -117
  48. data/lib/capybara/selenium/driver.rb +131 -125
  49. data/lib/capybara/selenium/node.rb +197 -115
  50. data/lib/capybara/server.rb +3 -2
  51. data/lib/capybara/session/config.rb +47 -67
  52. data/lib/capybara/session/matchers.rb +8 -7
  53. data/lib/capybara/session.rb +138 -224
  54. data/lib/capybara/spec/public/test.js +25 -4
  55. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -0
  56. data/lib/capybara/spec/session/accept_confirm_spec.rb +3 -2
  57. data/lib/capybara/spec/session/accept_prompt_spec.rb +1 -0
  58. data/lib/capybara/spec/session/all_spec.rb +31 -18
  59. data/lib/capybara/spec/session/ancestor_spec.rb +6 -8
  60. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +6 -5
  61. data/lib/capybara/spec/session/assert_current_path.rb +12 -11
  62. data/lib/capybara/spec/session/assert_selector.rb +1 -0
  63. data/lib/capybara/spec/session/assert_text.rb +23 -23
  64. data/lib/capybara/spec/session/assert_title.rb +13 -3
  65. data/lib/capybara/spec/session/attach_file_spec.rb +51 -30
  66. data/lib/capybara/spec/session/body_spec.rb +1 -0
  67. data/lib/capybara/spec/session/check_spec.rb +7 -6
  68. data/lib/capybara/spec/session/choose_spec.rb +5 -4
  69. data/lib/capybara/spec/session/click_button_spec.rb +24 -32
  70. data/lib/capybara/spec/session/click_link_or_button_spec.rb +8 -7
  71. data/lib/capybara/spec/session/click_link_spec.rb +8 -7
  72. data/lib/capybara/spec/session/current_scope_spec.rb +4 -3
  73. data/lib/capybara/spec/session/current_url_spec.rb +17 -6
  74. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +1 -1
  75. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +1 -0
  76. data/lib/capybara/spec/session/element/assert_match_selector.rb +1 -1
  77. data/lib/capybara/spec/session/element/match_xpath_spec.rb +1 -1
  78. data/lib/capybara/spec/session/element/matches_selector_spec.rb +5 -5
  79. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +3 -2
  80. data/lib/capybara/spec/session/evaluate_script_spec.rb +4 -3
  81. data/lib/capybara/spec/session/execute_script_spec.rb +4 -3
  82. data/lib/capybara/spec/session/fill_in_spec.rb +30 -5
  83. data/lib/capybara/spec/session/find_button_spec.rb +4 -3
  84. data/lib/capybara/spec/session/find_by_id_spec.rb +2 -1
  85. data/lib/capybara/spec/session/find_field_spec.rb +9 -15
  86. data/lib/capybara/spec/session/find_link_spec.rb +6 -5
  87. data/lib/capybara/spec/session/find_spec.rb +37 -31
  88. data/lib/capybara/spec/session/first_spec.rb +60 -33
  89. data/lib/capybara/spec/session/frame/frame_title_spec.rb +23 -0
  90. data/lib/capybara/spec/session/frame/frame_url_spec.rb +23 -0
  91. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +2 -1
  92. data/lib/capybara/spec/session/frame/within_frame_spec.rb +9 -16
  93. data/lib/capybara/spec/session/go_back_spec.rb +1 -0
  94. data/lib/capybara/spec/session/go_forward_spec.rb +1 -0
  95. data/lib/capybara/spec/session/has_all_selectors_spec.rb +15 -15
  96. data/lib/capybara/spec/session/has_button_spec.rb +2 -1
  97. data/lib/capybara/spec/session/has_css_spec.rb +3 -2
  98. data/lib/capybara/spec/session/has_current_path_spec.rb +12 -28
  99. data/lib/capybara/spec/session/has_field_spec.rb +4 -3
  100. data/lib/capybara/spec/session/has_link_spec.rb +1 -0
  101. data/lib/capybara/spec/session/has_none_selectors_spec.rb +17 -17
  102. data/lib/capybara/spec/session/has_select_spec.rb +30 -29
  103. data/lib/capybara/spec/session/has_selector_spec.rb +5 -4
  104. data/lib/capybara/spec/session/has_table_spec.rb +2 -1
  105. data/lib/capybara/spec/session/has_text_spec.rb +9 -13
  106. data/lib/capybara/spec/session/has_title_spec.rb +1 -0
  107. data/lib/capybara/spec/session/has_xpath_spec.rb +1 -0
  108. data/lib/capybara/spec/session/headers.rb +2 -1
  109. data/lib/capybara/spec/session/html_spec.rb +1 -0
  110. data/lib/capybara/spec/session/node_spec.rb +91 -56
  111. data/lib/capybara/spec/session/node_wrapper_spec.rb +36 -0
  112. data/lib/capybara/spec/session/refresh_spec.rb +6 -2
  113. data/lib/capybara/spec/session/reset_session_spec.rb +19 -0
  114. data/lib/capybara/spec/session/response_code.rb +1 -0
  115. data/lib/capybara/spec/session/save_and_open_page_spec.rb +1 -0
  116. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +6 -11
  117. data/lib/capybara/spec/session/save_page_spec.rb +1 -17
  118. data/lib/capybara/spec/session/save_screenshot_spec.rb +3 -3
  119. data/lib/capybara/spec/session/select_spec.rb +20 -20
  120. data/lib/capybara/spec/session/selectors_spec.rb +2 -2
  121. data/lib/capybara/spec/session/sibling_spec.rb +1 -1
  122. data/lib/capybara/spec/session/text_spec.rb +17 -3
  123. data/lib/capybara/spec/session/title_spec.rb +11 -1
  124. data/lib/capybara/spec/session/uncheck_spec.rb +4 -3
  125. data/lib/capybara/spec/session/unselect_spec.rb +6 -5
  126. data/lib/capybara/spec/session/visit_spec.rb +9 -3
  127. data/lib/capybara/spec/session/window/become_closed_spec.rb +2 -1
  128. data/lib/capybara/spec/session/window/current_window_spec.rb +1 -0
  129. data/lib/capybara/spec/session/window/open_new_window_spec.rb +1 -0
  130. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +2 -1
  131. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +2 -1
  132. data/lib/capybara/spec/session/window/window_spec.rb +12 -12
  133. data/lib/capybara/spec/session/window/windows_spec.rb +2 -3
  134. data/lib/capybara/spec/session/window/within_window_spec.rb +15 -71
  135. data/lib/capybara/spec/session/within_spec.rb +1 -0
  136. data/lib/capybara/spec/spec_helper.rb +34 -18
  137. data/lib/capybara/spec/test_app.rb +17 -9
  138. data/lib/capybara/spec/views/form.erb +7 -0
  139. data/lib/capybara/spec/views/with_html.erb +23 -1
  140. data/lib/capybara/spec/views/within_frames.erb +4 -1
  141. data/lib/capybara/version.rb +2 -1
  142. data/lib/capybara/window.rb +6 -10
  143. data/lib/capybara.rb +28 -25
  144. data/spec/basic_node_spec.rb +1 -0
  145. data/spec/capybara_spec.rb +11 -50
  146. data/spec/dsl_spec.rb +5 -13
  147. data/spec/filter_set_spec.rb +5 -4
  148. data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -1
  149. data/spec/fixtures/selenium_driver_rspec_success.rb +3 -2
  150. data/spec/minitest_spec.rb +4 -3
  151. data/spec/minitest_spec_spec.rb +3 -2
  152. data/spec/per_session_config_spec.rb +9 -8
  153. data/spec/rack_test_spec.rb +21 -20
  154. data/spec/result_spec.rb +17 -16
  155. data/spec/rspec/features_spec.rb +17 -14
  156. data/spec/rspec/scenarios_spec.rb +5 -7
  157. data/spec/rspec/shared_spec_matchers.rb +96 -99
  158. data/spec/rspec/views_spec.rb +2 -1
  159. data/spec/rspec_matchers_spec.rb +18 -2
  160. data/spec/rspec_spec.rb +11 -15
  161. data/spec/selector_spec.rb +5 -6
  162. data/spec/selenium_spec_chrome.rb +9 -4
  163. data/spec/selenium_spec_edge.rb +27 -0
  164. data/spec/selenium_spec_ie.rb +31 -0
  165. data/spec/selenium_spec_marionette.rb +28 -12
  166. data/spec/server_spec.rb +33 -33
  167. data/spec/session_spec.rb +2 -1
  168. data/spec/shared_selenium_session.rb +36 -22
  169. data/spec/spec_helper.rb +3 -6
  170. metadata +68 -85
  171. data/lib/capybara/query.rb +0 -7
  172. data/spec/selenium_spec_firefox.rb +0 -68
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Capybara::RackTest::Form < Capybara::RackTest::Node
3
4
  # This only needs to inherit from Rack::Test::UploadedFile because Rack::Test checks for
4
5
  # the class specifically when determining whether to construct the request as multipart.
@@ -20,53 +21,21 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
20
21
  def params(button)
21
22
  params = make_params
22
23
 
23
- form_element_types=[:input, :select, :textarea]
24
- form_elements_xpath=XPath.generate do |x|
25
- xpath=x.descendant(*form_element_types).where(~x.attr(:form))
26
- xpath=xpath.union(x.anywhere(*form_element_types).where(x.attr(:form) == native[:id])) if native[:id]
27
- xpath.where(~x.attr(:disabled))
24
+ form_element_types = %i[input select textarea]
25
+ form_elements_xpath = XPath.generate do |x|
26
+ xpath = x.descendant(*form_element_types).where(!x.attr(:form))
27
+ xpath += x.anywhere(*form_element_types).where(x.attr(:form) == native[:id]) if native[:id]
28
+ xpath.where(!x.attr(:disabled))
28
29
  end.to_s
29
30
 
30
31
  native.xpath(form_elements_xpath).map do |field|
31
32
  case field.name
32
33
  when 'input'
33
- if %w(radio checkbox).include? field['type']
34
- if field['checked']
35
- node=Capybara::RackTest::Node.new(self.driver, field)
36
- merge_param!(params, field['name'].to_s, node.value.to_s)
37
- end
38
- elsif %w(submit image).include? field['type']
39
- # TO DO identify the click button here (in document order, rather
40
- # than leaving until the end of the params)
41
- elsif field['type'] =='file'
42
- if multipart?
43
- file = \
44
- if (value = field['value']).to_s.empty?
45
- NilUploadedFile.new
46
- else
47
- mime_info = MiniMime.lookup_by_filename(value)
48
- Rack::Test::UploadedFile.new(value, (mime_info && mime_info.content_type).to_s)
49
- end
50
- merge_param!(params, field['name'].to_s, file)
51
- else
52
- merge_param!(params, field['name'].to_s, File.basename(field['value'].to_s))
53
- end
54
- else
55
- merge_param!(params, field['name'].to_s, field['value'].to_s)
56
- end
34
+ add_input_param(field, params)
57
35
  when 'select'
58
- if field['multiple'] == 'multiple'
59
- options = field.xpath(".//option[@selected]")
60
- options.each do |option|
61
- merge_param!(params, field['name'].to_s, (option['value'] || option.text).to_s)
62
- end
63
- else
64
- option = field.xpath(".//option[@selected]").first
65
- option ||= field.xpath('.//option').first
66
- merge_param!(params, field['name'].to_s, (option['value'] || option.text).to_s) if option
67
- end
36
+ add_select_param(field, params)
68
37
  when 'textarea'
69
- merge_param!(params, field['name'].to_s, field['_capybara_raw_value'].to_s.gsub(/\n/, "\r\n"))
38
+ add_textarea_param(field, params)
70
39
  end
71
40
  end
72
41
  merge_param!(params, button[:name], button[:value] || "") if button[:name]
@@ -111,4 +80,45 @@ private
111
80
  ParamsHash.new
112
81
  end
113
82
  end
83
+
84
+ def add_input_param(field, params)
85
+ if %w[radio checkbox].include? field['type']
86
+ if field['checked']
87
+ node = Capybara::RackTest::Node.new(driver, field)
88
+ merge_param!(params, field['name'].to_s, node.value.to_s)
89
+ end
90
+ elsif %w[submit image].include? field['type']
91
+ # TODO: identify the click button here (in document order, rather
92
+ # than leaving until the end of the params)
93
+ elsif field['type'] == 'file'
94
+ if multipart?
95
+ file = if (value = field['value']).to_s.empty?
96
+ NilUploadedFile.new
97
+ else
98
+ mime_info = MiniMime.lookup_by_filename(value)
99
+ Rack::Test::UploadedFile.new(value, (mime_info && mime_info.content_type).to_s)
100
+ end
101
+ merge_param!(params, field['name'].to_s, file)
102
+ else
103
+ merge_param!(params, field['name'].to_s, File.basename(field['value'].to_s))
104
+ end
105
+ else
106
+ merge_param!(params, field['name'].to_s, field['value'].to_s)
107
+ end
108
+ end
109
+
110
+ def add_select_param(field, params)
111
+ if field['multiple'] == 'multiple'
112
+ field.xpath(".//option[@selected]").each do |option|
113
+ merge_param!(params, field['name'].to_s, (option['value'] || option.text).to_s)
114
+ end
115
+ else
116
+ option = field.xpath('.//option[@selected]').first || field.xpath('.//option').first
117
+ merge_param!(params, field['name'].to_s, (option['value'] || option.text).to_s) if option
118
+ end
119
+ end
120
+
121
+ def add_textarea_param(field, params)
122
+ merge_param!(params, field['name'].to_s, field['_capybara_raw_value'].to_s.gsub(/\n/, "\r\n"))
123
+ end
114
124
  end
@@ -1,11 +1,23 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Capybara::RackTest::Node < Capybara::Driver::Node
4
+ BLOCK_ELEMENTS = %w[p h1 h2 h3 h4 h5 h6 ol ul pre address blockquote dl div fieldset form hr noscript table].freeze
5
+
3
6
  def all_text
4
- Capybara::Helpers.normalize_whitespace(native.text)
7
+ native.text
8
+ .gsub(/[\u200b\u200e\u200f]/, '')
9
+ .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
10
+ .gsub(/\A[[:space:]&&[^\u00a0]]+/, "")
11
+ .gsub(/[[:space:]&&[^\u00a0]]+\z/, "")
12
+ .tr("\u00a0", ' ')
5
13
  end
6
14
 
7
15
  def visible_text
8
- Capybara::Helpers.normalize_whitespace(unnormalized_text)
16
+ displayed_text.gsub(/\ +/, ' ')
17
+ .gsub(/[\ \n]*\n[\ \n]*/, "\n")
18
+ .gsub(/\A[[:space:]&&[^\u00a0]]+/, "")
19
+ .gsub(/[[:space:]&&[^\u00a0]]+\z/, "")
20
+ .tr("\u00a0", ' ')
9
21
  end
10
22
 
11
23
  def [](name)
@@ -16,64 +28,45 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
16
28
  string_node.value
17
29
  end
18
30
 
19
- def set(value)
20
- return if disabled?
21
- if readonly?
22
- warn "Attempt to set readonly element with value: #{value} \n * This will raise an exception in a future version of Capybara"
23
- return
24
- end
31
+ def set(value, **options)
32
+ return if disabled? || readonly?
25
33
 
26
- if (Array === value) && !multiple?
27
- raise TypeError.new "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
34
+ warn "Options passed to Node#set but the RackTest driver doesn't support any - ignoring" unless options.empty?
35
+
36
+ if value.is_a?(Array) && !multiple?
37
+ raise TypeError, "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
28
38
  end
29
39
 
30
- if radio?
31
- set_radio(value)
32
- elsif checkbox?
33
- set_checkbox(value)
34
- elsif input_field?
35
- set_input(value)
36
- elsif textarea?
37
- native['_capybara_raw_value'] = value.to_s
40
+ if radio? then set_radio(value)
41
+ elsif checkbox? then set_checkbox(value)
42
+ elsif input_field? then set_input(value)
43
+ elsif textarea? then native['_capybara_raw_value'] = value.to_s
38
44
  end
39
45
  end
40
46
 
41
47
  def select_option
42
48
  return if disabled?
43
- if select_node['multiple'] != 'multiple'
44
- select_node.find_xpath(".//option[@selected]").each { |node| node.native.remove_attribute("selected") }
45
- end
49
+ deselect_options unless select_node.multiple?
46
50
  native["selected"] = 'selected'
47
51
  end
48
52
 
49
53
  def unselect_option
50
- if select_node['multiple'] != 'multiple'
51
- raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
52
- end
54
+ raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box." unless select_node.multiple?
53
55
  native.remove_attribute('selected')
54
56
  end
55
57
 
56
- def click
57
- if tag_name == 'a' && !self[:href].nil?
58
- method = self["data-method"] if driver.options[:respect_data_method]
59
- method ||= :get
60
- driver.follow(method, self[:href].to_s)
61
- elsif (tag_name == 'input' and %w(submit image).include?(type)) or
62
- ((tag_name == 'button') and type.nil? or type == "submit")
58
+ def click(keys = [], offset = {})
59
+ raise ArgumentError, "The RackTest driver does not support click options" unless keys.empty? && offset.empty?
60
+
61
+ if link?
62
+ follow_link
63
+ elsif submits?
63
64
  associated_form = form
64
65
  Capybara::RackTest::Form.new(driver, associated_form).submit(self) if associated_form
65
- elsif (tag_name == 'input' and %w(checkbox radio).include?(type))
66
+ elsif checkable?
66
67
  set(!checked?)
67
- elsif (tag_name == 'label')
68
- labelled_control = if native[:for]
69
- find_xpath("//input[@id='#{native[:for]}']").first
70
- else
71
- find_xpath(".//input").first
72
- end
73
-
74
- if labelled_control && (labelled_control.checkbox? || labelled_control.radio?)
75
- labelled_control.set(!labelled_control.checked?)
76
- end
68
+ elsif tag_name == 'label'
69
+ click_label
77
70
  end
78
71
  end
79
72
 
@@ -94,10 +87,12 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
94
87
  end
95
88
 
96
89
  def disabled?
97
- if %w(option optgroup).include? tag_name
98
- string_node.disabled? || find_xpath("parent::*[self::optgroup or self::select]")[0].disabled?
90
+ return true if string_node.disabled?
91
+
92
+ if %w[option optgroup].include? tag_name
93
+ find_xpath("parent::*[self::optgroup or self::select]")[0].disabled?
99
94
  else
100
- string_node.disabled? || !find_xpath("parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]").empty?
95
+ !find_xpath("parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]").empty?
101
96
  end
102
97
  end
103
98
 
@@ -119,15 +114,20 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
119
114
 
120
115
  protected
121
116
 
122
- def unnormalized_text(check_ancestor_visibility = true)
123
- if !string_node.visible?(check_ancestor_visibility)
117
+ # @api private
118
+ def displayed_text(check_ancestor: true)
119
+ if !string_node.visible?(check_ancestor)
124
120
  ''
125
121
  elsif native.text?
126
122
  native.text
123
+ .gsub(/[\u200b\u200e\u200f]/, '')
124
+ .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
127
125
  elsif native.element?
128
- native.children.map do |child|
129
- Capybara::RackTest::Node.new(driver, child).unnormalized_text(false)
130
- end.join
126
+ text = native.children.map do |child|
127
+ Capybara::RackTest::Node.new(driver, child).displayed_text(check_ancestor: false)
128
+ end.join || ''
129
+ text = "\n#{text}\n" if BLOCK_ELEMENTS.include?(tag_name)
130
+ text
131
131
  else
132
132
  ''
133
133
  end
@@ -135,6 +135,10 @@ protected
135
135
 
136
136
  private
137
137
 
138
+ def deselect_options
139
+ select_node.find_xpath(".//option[@selected]").each { |node| node.native.remove_attribute("selected") }
140
+ end
141
+
138
142
  def string_node
139
143
  @string_node ||= Capybara::Node::Simple.new(native)
140
144
  end
@@ -150,19 +154,19 @@ private
150
154
 
151
155
  def form
152
156
  if native[:form]
153
- native.xpath("//form[@id='#{native[:form]}']").first
157
+ native.xpath("//form[@id='#{native[:form]}']")
154
158
  else
155
- native.ancestors('form').first
156
- end
159
+ native.ancestors('form')
160
+ end.first
157
161
  end
158
162
 
159
- def set_radio(_value)
160
- other_radios_xpath = XPath.generate { |x| x.anywhere(:input)[x.attr(:name).equals(self[:name])] }.to_s
163
+ def set_radio(_value) # rubocop:disable Naming/AccessorMethodName
164
+ other_radios_xpath = XPath.generate { |x| x.anywhere(:input)[x.attr(:name) == self[:name]] }.to_s
161
165
  driver.dom.xpath(other_radios_xpath).each { |node| node.remove_attribute("checked") }
162
166
  native['checked'] = 'checked'
163
167
  end
164
168
 
165
- def set_checkbox(value)
169
+ def set_checkbox(value) # rubocop:disable Naming/AccessorMethodName
166
170
  if value && !native['checked']
167
171
  native['checked'] = 'checked'
168
172
  elsif !value && native['checked']
@@ -170,13 +174,13 @@ private
170
174
  end
171
175
  end
172
176
 
173
- def set_input(value)
177
+ def set_input(value) # rubocop:disable Naming/AccessorMethodName
174
178
  if text_or_password? && attribute_is_not_blank?(:maxlength)
175
179
  # Browser behavior for maxlength="0" is inconsistent, so we stick with
176
180
  # Firefox, allowing no input
177
181
  value = value.to_s[0...self[:maxlength].to_i]
178
182
  end
179
- if Array === value #Assert multiple attribute is present
183
+ if value.is_a?(Array) # Assert multiple attribute is present
180
184
  value.each do |v|
181
185
  new_native = native.clone
182
186
  new_native.remove_attribute('value')
@@ -185,11 +189,7 @@ private
185
189
  end
186
190
  native.remove
187
191
  else
188
- if readonly?
189
- warn "Attempt to set readonly element with value: #{value} \n *This will raise an exception in a future version of Capybara"
190
- else
191
- native['value'] = value.to_s
192
- end
192
+ native['value'] = value.to_s
193
193
  end
194
194
  end
195
195
 
@@ -197,6 +197,36 @@ private
197
197
  self[attribute] && !self[attribute].empty?
198
198
  end
199
199
 
200
+ def follow_link
201
+ method = self["data-method"] if driver.options[:respect_data_method]
202
+ method ||= :get
203
+ driver.follow(method, self[:href].to_s)
204
+ end
205
+
206
+ def click_label
207
+ labelled_control = if native[:for]
208
+ find_xpath("//input[@id='#{native[:for]}']")
209
+ else
210
+ find_xpath(".//input")
211
+ end.first
212
+
213
+ if labelled_control && (labelled_control.checkbox? || labelled_control.radio?)
214
+ labelled_control.set(!labelled_control.checked?)
215
+ end
216
+ end
217
+
218
+ def link?
219
+ tag_name == 'a' && !self[:href].nil?
220
+ end
221
+
222
+ def submits?
223
+ (tag_name == 'input' and %w[submit image].include?(type)) || (tag_name == 'button' and [nil, "submit"].include?(type))
224
+ end
225
+
226
+ def checkable?
227
+ tag_name == 'input' and %w[checkbox radio].include?(type)
228
+ end
229
+
200
230
  protected
201
231
 
202
232
  def checkbox?
@@ -1,14 +1,10 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'capybara/dsl'
3
4
 
4
5
  Capybara.app = Rack::Builder.new do
5
6
  map "/" do
6
- if Gem::Version.new(Rails.version) >= Gem::Version.new("3.0")
7
- run Rails.application
8
- else # Rails 2
9
- use Rails::Rack::Static
10
- run ActionController::Dispatcher.new
11
- end
7
+ run Rails.application
12
8
  end
13
9
  end.to_app
14
10
 
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'forwardable'
3
4
 
4
5
  module Capybara
5
-
6
6
  ##
7
7
  # A {Capybara::Result} represents a collection of {Capybara::Node::Element} on the page. It is possible to interact with this
8
8
  # collection similar to an Array because it implements Enumerable and offers the following Array methods through delegation:
@@ -41,7 +41,7 @@ module Capybara
41
41
  loop do
42
42
  next_result = @results_enum.next
43
43
  @result_cache << next_result
44
- block.call(next_result)
44
+ yield next_result
45
45
  end
46
46
  self
47
47
  end
@@ -62,7 +62,7 @@ module Capybara
62
62
  !any?
63
63
  end
64
64
 
65
- def matches_count?
65
+ def compare_count
66
66
  # Only check filters for as many elements as necessary to determine result
67
67
  if @query.options[:count]
68
68
  count_opt = Integer(@query.options[:count])
@@ -70,7 +70,7 @@ module Capybara
70
70
  break if @result_cache.size > count_opt
71
71
  @result_cache << @results_enum.next
72
72
  end
73
- return @result_cache.size == count_opt
73
+ return @result_cache.size <=> count_opt
74
74
  end
75
75
 
76
76
  if @query.options[:minimum]
@@ -78,7 +78,7 @@ module Capybara
78
78
  begin
79
79
  @result_cache << @results_enum.next while @result_cache.size < min_opt
80
80
  rescue StopIteration
81
- return false
81
+ return -1
82
82
  end
83
83
  end
84
84
 
@@ -86,32 +86,38 @@ module Capybara
86
86
  max_opt = Integer(@query.options[:maximum])
87
87
  begin
88
88
  @result_cache << @results_enum.next while @result_cache.size <= max_opt
89
- return false
89
+ return 1
90
90
  rescue StopIteration
91
91
  end
92
92
  end
93
93
 
94
94
  if @query.options[:between]
95
- max = Integer(@query.options[:between].max)
95
+ min, max = @query.options[:between].minmax
96
96
  loop do
97
97
  break if @result_cache.size > max
98
98
  @result_cache << @results_enum.next
99
99
  end
100
- return false unless (@query.options[:between] === @result_cache.size)
100
+ return 0 if @query.options[:between].include?(@result_cache.size)
101
+ return -1 if @result_cache.size < min
102
+ return 1
101
103
  end
102
104
 
103
- return true
105
+ return 0
106
+ end
107
+
108
+ def matches_count?
109
+ compare_count.zero?
104
110
  end
105
111
 
106
112
  def failure_message
107
113
  message = @query.failure_message
108
- if count > 0
109
- message << ", found #{count} #{Capybara::Helpers.declension("match", "matches", count)}: " << full_results.map(&:text).map(&:inspect).join(", ")
110
- else
114
+ if count.zero?
111
115
  message << " but there were no matches"
116
+ else
117
+ message << ", found #{count} #{Capybara::Helpers.declension("match", "matches", count)}: " << full_results.map(&:text).map(&:inspect).join(", ")
112
118
  end
113
119
  unless rest.empty?
114
- elements = rest.map(&:text).map(&:inspect).join(", ")
120
+ elements = rest.map { |el| el.text rescue "<<ERROR>>" }.map(&:inspect).join(", ")
115
121
  message << ". Also found " << elements << ", which matched the selector but not all filters."
116
122
  end
117
123
  message
@@ -121,7 +127,7 @@ module Capybara
121
127
  failure_message.sub(/(to find)/, 'not \1')
122
128
  end
123
129
 
124
- private
130
+ private
125
131
 
126
132
  def full_results
127
133
  loop { @result_cache << @results_enum.next }
@@ -137,15 +143,9 @@ module Capybara
137
143
  # causes a concurrency issue with network requests here
138
144
  # https://github.com/jruby/jruby/issues/4212
139
145
  if RUBY_PLATFORM == 'java'
140
- @elements.select(&block).to_enum # non-lazy evaluation
141
- elsif @elements.respond_to? :lazy #Ruby 2.0+
142
- @elements.lazy.select(&block)
146
+ @elements.select(&block).to_enum # non-lazy evaluation
143
147
  else
144
- Enumerator.new do |yielder|
145
- @elements.each do |val|
146
- yielder.yield(val) if block.call(val)
147
- end
148
- end
148
+ @elements.lazy.select(&block)
149
149
  end
150
150
  end
151
151
  end
@@ -4,7 +4,7 @@ module Capybara
4
4
  include ::RSpec::Matchers::Composable
5
5
 
6
6
  def and(matcher)
7
- Capybara::RSpecMatchers::Compound::And.new(self,matcher)
7
+ Capybara::RSpecMatchers::Compound::And.new(self, matcher)
8
8
  end
9
9
 
10
10
  def and_then(matcher)
@@ -15,12 +15,9 @@ module Capybara
15
15
  Capybara::RSpecMatchers::Compound::Or.new(self, matcher)
16
16
  end
17
17
 
18
-
19
18
  class CapybaraEvaluator
20
- def initialize(actual, matcher_1, matcher_2)
21
- @actual = actual
22
- @matcher_1 = matcher_1
23
- @matcher_2 = matcher_2
19
+ def initialize(actual)
20
+ @actual = actual
24
21
  @match_results = Hash.new { |h, matcher| h[matcher] = matcher.matches?(@actual) }
25
22
  end
26
23
 
@@ -34,11 +31,10 @@ module Capybara
34
31
  end
35
32
 
36
33
  class And < ::RSpec::Matchers::BuiltIn::Compound::And
37
-
38
34
  private
39
35
 
40
36
  def match(_expected, actual)
41
- @evaluator = CapybaraEvaluator.new(actual, matcher_1, matcher_2)
37
+ @evaluator = CapybaraEvaluator.new(actual)
42
38
  syncer = sync_element(actual)
43
39
  begin
44
40
  syncer.synchronize do
@@ -63,11 +59,10 @@ module Capybara
63
59
  end
64
60
 
65
61
  class Or < ::RSpec::Matchers::BuiltIn::Compound::Or
66
-
67
62
  private
68
63
 
69
64
  def match(_expected, actual)
70
- @evaluator = CapybaraEvaluator.new(actual, matcher_1, matcher_2)
65
+ @evaluator = CapybaraEvaluator.new(actual)
71
66
  syncer = sync_element(actual)
72
67
  begin
73
68
  syncer.synchronize do
@@ -1,56 +1,25 @@
1
1
  # frozen_string_literal: true
2
- if RSpec::Core::Version::STRING.to_f >= 3.0
3
- RSpec.shared_context "Capybara Features", capybara_feature: true do
4
- instance_eval do
5
- alias background before
6
- alias given let
7
- alias given! let!
8
- end
9
- end
10
2
 
11
- # ensure shared_context is included if default shared_context_metadata_behavior is changed
12
- if RSpec::Core::Version::STRING.to_f >= 3.5
13
- RSpec.configure do |config|
14
- config.include_context "Capybara Features", capybara_feature: true
15
- end
3
+ RSpec.shared_context "Capybara Features", capybara_feature: true do
4
+ instance_eval do
5
+ alias background before
6
+ alias given let
7
+ alias given! let!
16
8
  end
9
+ end
17
10
 
11
+ # ensure shared_context is included if default shared_context_metadata_behavior is changed
12
+ if RSpec::Core::Version::STRING.to_f >= 3.5
18
13
  RSpec.configure do |config|
19
- config.alias_example_group_to :feature, capybara_feature: true, type: :feature
20
- config.alias_example_group_to :xfeature, capybara_feature: true, type: :feature, skip: "Temporarily disabled with xfeature"
21
- config.alias_example_group_to :ffeature, capybara_feature: true, type: :feature, focus: true
22
- config.alias_example_to :scenario
23
- config.alias_example_to :xscenario, skip: "Temporarily disabled with xscenario"
24
- config.alias_example_to :fscenario, focus: true
25
- end
26
- else
27
- module Capybara
28
- module Features
29
- def self.included(base)
30
- base.instance_eval do
31
- alias :background :before
32
- alias :scenario :it
33
- alias :xscenario :xit
34
- alias :given :let
35
- alias :given! :let!
36
- alias :feature :describe
37
- end
38
- end
39
- end
40
- end
41
-
42
- def self.feature(*args, &block)
43
- options = if args.last.is_a?(Hash) then args.pop else {} end
44
- options[:capybara_feature] = true
45
- options[:type] = :feature
46
- options[:caller] ||= caller
47
- args.push(options)
48
-
49
- #call describe on RSpec in case user has expose_dsl_globally set to false
50
- RSpec.describe(*args, &block)
14
+ config.include_context "Capybara Features", capybara_feature: true
51
15
  end
16
+ end
52
17
 
53
- RSpec.configure do |config|
54
- config.include(Capybara::Features, capybara_feature: true)
55
- end
18
+ RSpec.configure do |config|
19
+ config.alias_example_group_to :feature, capybara_feature: true, type: :feature
20
+ config.alias_example_group_to :xfeature, capybara_feature: true, type: :feature, skip: "Temporarily disabled with xfeature"
21
+ config.alias_example_group_to :ffeature, capybara_feature: true, type: :feature, focus: true
22
+ config.alias_example_to :scenario
23
+ config.alias_example_to :xscenario, skip: "Temporarily disabled with xscenario"
24
+ config.alias_example_to :fscenario, focus: true
56
25
  end