capybara 2.18.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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