capybara 2.18.0 → 3.0.0.rc1

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 (168) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +26 -1
  3. data/README.md +12 -12
  4. data/lib/capybara.rb +13 -25
  5. data/lib/capybara/config.rb +11 -57
  6. data/lib/capybara/cucumber.rb +2 -3
  7. data/lib/capybara/driver/base.rb +5 -16
  8. data/lib/capybara/driver/node.rb +5 -4
  9. data/lib/capybara/dsl.rb +1 -0
  10. data/lib/capybara/helpers.rb +16 -28
  11. data/lib/capybara/minitest.rb +139 -138
  12. data/lib/capybara/minitest/spec.rb +15 -14
  13. data/lib/capybara/node/actions.rb +59 -81
  14. data/lib/capybara/node/base.rb +11 -18
  15. data/lib/capybara/node/document.rb +2 -2
  16. data/lib/capybara/node/document_matchers.rb +8 -8
  17. data/lib/capybara/node/element.rb +30 -40
  18. data/lib/capybara/node/finders.rb +62 -70
  19. data/lib/capybara/node/matchers.rb +48 -71
  20. data/lib/capybara/node/simple.rb +11 -17
  21. data/lib/capybara/queries/ancestor_query.rb +4 -6
  22. data/lib/capybara/queries/base_query.rb +18 -17
  23. data/lib/capybara/queries/current_path_query.rb +8 -24
  24. data/lib/capybara/queries/match_query.rb +3 -7
  25. data/lib/capybara/queries/selector_query.rb +92 -95
  26. data/lib/capybara/queries/sibling_query.rb +4 -4
  27. data/lib/capybara/queries/text_query.rb +37 -34
  28. data/lib/capybara/queries/title_query.rb +8 -11
  29. data/lib/capybara/rack_test/browser.rb +15 -18
  30. data/lib/capybara/rack_test/css_handlers.rb +6 -4
  31. data/lib/capybara/rack_test/driver.rb +6 -10
  32. data/lib/capybara/rack_test/form.rb +50 -40
  33. data/lib/capybara/rack_test/node.rb +70 -56
  34. data/lib/capybara/rails.rb +2 -6
  35. data/lib/capybara/result.rb +22 -22
  36. data/lib/capybara/rspec.rb +5 -10
  37. data/lib/capybara/rspec/compound.rb +5 -10
  38. data/lib/capybara/rspec/features.rb +17 -48
  39. data/lib/capybara/rspec/matcher_proxies.rb +31 -15
  40. data/lib/capybara/rspec/matchers.rb +70 -60
  41. data/lib/capybara/selector.rb +129 -117
  42. data/lib/capybara/selector/css.rb +6 -11
  43. data/lib/capybara/selector/filter.rb +1 -17
  44. data/lib/capybara/selector/filter_set.rb +17 -14
  45. data/lib/capybara/selector/filters/base.rb +7 -6
  46. data/lib/capybara/selector/filters/expression_filter.rb +6 -23
  47. data/lib/capybara/selector/filters/node_filter.rb +2 -12
  48. data/lib/capybara/selector/selector.rb +27 -33
  49. data/lib/capybara/selenium/driver.rb +113 -127
  50. data/lib/capybara/selenium/node.rb +148 -113
  51. data/lib/capybara/server.rb +3 -2
  52. data/lib/capybara/session.rb +137 -223
  53. data/lib/capybara/session/config.rb +47 -67
  54. data/lib/capybara/session/matchers.rb +8 -7
  55. data/lib/capybara/spec/public/test.js +26 -4
  56. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -0
  57. data/lib/capybara/spec/session/accept_confirm_spec.rb +3 -2
  58. data/lib/capybara/spec/session/accept_prompt_spec.rb +1 -0
  59. data/lib/capybara/spec/session/all_spec.rb +31 -18
  60. data/lib/capybara/spec/session/ancestor_spec.rb +2 -4
  61. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +6 -5
  62. data/lib/capybara/spec/session/assert_current_path.rb +12 -11
  63. data/lib/capybara/spec/session/assert_selector.rb +1 -0
  64. data/lib/capybara/spec/session/assert_text.rb +18 -17
  65. data/lib/capybara/spec/session/assert_title.rb +1 -0
  66. data/lib/capybara/spec/session/attach_file_spec.rb +14 -13
  67. data/lib/capybara/spec/session/body_spec.rb +1 -0
  68. data/lib/capybara/spec/session/check_spec.rb +7 -6
  69. data/lib/capybara/spec/session/choose_spec.rb +5 -4
  70. data/lib/capybara/spec/session/click_button_spec.rb +20 -28
  71. data/lib/capybara/spec/session/click_link_or_button_spec.rb +8 -7
  72. data/lib/capybara/spec/session/click_link_spec.rb +8 -7
  73. data/lib/capybara/spec/session/current_scope_spec.rb +4 -3
  74. data/lib/capybara/spec/session/current_url_spec.rb +7 -6
  75. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +1 -1
  76. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +1 -0
  77. data/lib/capybara/spec/session/element/assert_match_selector.rb +1 -1
  78. data/lib/capybara/spec/session/element/match_xpath_spec.rb +1 -1
  79. data/lib/capybara/spec/session/element/matches_selector_spec.rb +5 -5
  80. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +3 -2
  81. data/lib/capybara/spec/session/evaluate_script_spec.rb +4 -3
  82. data/lib/capybara/spec/session/execute_script_spec.rb +4 -3
  83. data/lib/capybara/spec/session/fill_in_spec.rb +6 -5
  84. data/lib/capybara/spec/session/find_button_spec.rb +4 -3
  85. data/lib/capybara/spec/session/find_by_id_spec.rb +2 -1
  86. data/lib/capybara/spec/session/find_field_spec.rb +8 -14
  87. data/lib/capybara/spec/session/find_link_spec.rb +6 -5
  88. data/lib/capybara/spec/session/find_spec.rb +37 -31
  89. data/lib/capybara/spec/session/first_spec.rb +60 -33
  90. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +2 -1
  91. data/lib/capybara/spec/session/frame/within_frame_spec.rb +9 -16
  92. data/lib/capybara/spec/session/go_back_spec.rb +1 -0
  93. data/lib/capybara/spec/session/go_forward_spec.rb +1 -0
  94. data/lib/capybara/spec/session/has_all_selectors_spec.rb +15 -15
  95. data/lib/capybara/spec/session/has_button_spec.rb +2 -1
  96. data/lib/capybara/spec/session/has_css_spec.rb +3 -2
  97. data/lib/capybara/spec/session/has_current_path_spec.rb +12 -28
  98. data/lib/capybara/spec/session/has_field_spec.rb +4 -3
  99. data/lib/capybara/spec/session/has_link_spec.rb +1 -0
  100. data/lib/capybara/spec/session/has_none_selectors_spec.rb +17 -17
  101. data/lib/capybara/spec/session/has_select_spec.rb +30 -29
  102. data/lib/capybara/spec/session/has_selector_spec.rb +5 -4
  103. data/lib/capybara/spec/session/has_table_spec.rb +2 -1
  104. data/lib/capybara/spec/session/has_text_spec.rb +6 -5
  105. data/lib/capybara/spec/session/has_title_spec.rb +1 -0
  106. data/lib/capybara/spec/session/has_xpath_spec.rb +1 -0
  107. data/lib/capybara/spec/session/headers.rb +2 -1
  108. data/lib/capybara/spec/session/html_spec.rb +1 -0
  109. data/lib/capybara/spec/session/node_spec.rb +91 -56
  110. data/lib/capybara/spec/session/node_wrapper_spec.rb +36 -0
  111. data/lib/capybara/spec/session/refresh_spec.rb +4 -2
  112. data/lib/capybara/spec/session/reset_session_spec.rb +1 -0
  113. data/lib/capybara/spec/session/response_code.rb +1 -0
  114. data/lib/capybara/spec/session/save_and_open_page_spec.rb +1 -0
  115. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +6 -11
  116. data/lib/capybara/spec/session/save_page_spec.rb +1 -17
  117. data/lib/capybara/spec/session/save_screenshot_spec.rb +1 -1
  118. data/lib/capybara/spec/session/select_spec.rb +20 -20
  119. data/lib/capybara/spec/session/selectors_spec.rb +2 -2
  120. data/lib/capybara/spec/session/sibling_spec.rb +1 -1
  121. data/lib/capybara/spec/session/text_spec.rb +1 -0
  122. data/lib/capybara/spec/session/title_spec.rb +1 -1
  123. data/lib/capybara/spec/session/uncheck_spec.rb +4 -3
  124. data/lib/capybara/spec/session/unselect_spec.rb +6 -5
  125. data/lib/capybara/spec/session/visit_spec.rb +9 -3
  126. data/lib/capybara/spec/session/window/become_closed_spec.rb +2 -1
  127. data/lib/capybara/spec/session/window/current_window_spec.rb +1 -0
  128. data/lib/capybara/spec/session/window/open_new_window_spec.rb +1 -0
  129. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +2 -1
  130. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +2 -1
  131. data/lib/capybara/spec/session/window/window_spec.rb +12 -12
  132. data/lib/capybara/spec/session/window/windows_spec.rb +2 -3
  133. data/lib/capybara/spec/session/window/within_window_spec.rb +13 -68
  134. data/lib/capybara/spec/session/within_spec.rb +1 -0
  135. data/lib/capybara/spec/spec_helper.rb +26 -18
  136. data/lib/capybara/spec/test_app.rb +8 -9
  137. data/lib/capybara/spec/views/form.erb +1 -0
  138. data/lib/capybara/spec/views/with_html.erb +3 -1
  139. data/lib/capybara/spec/views/within_frames.erb +4 -1
  140. data/lib/capybara/version.rb +2 -1
  141. data/lib/capybara/window.rb +6 -10
  142. data/spec/basic_node_spec.rb +1 -0
  143. data/spec/capybara_spec.rb +9 -32
  144. data/spec/dsl_spec.rb +5 -13
  145. data/spec/filter_set_spec.rb +5 -4
  146. data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -1
  147. data/spec/fixtures/selenium_driver_rspec_success.rb +3 -2
  148. data/spec/minitest_spec.rb +4 -3
  149. data/spec/minitest_spec_spec.rb +3 -2
  150. data/spec/per_session_config_spec.rb +9 -8
  151. data/spec/rack_test_spec.rb +21 -20
  152. data/spec/result_spec.rb +17 -16
  153. data/spec/rspec/features_spec.rb +17 -14
  154. data/spec/rspec/scenarios_spec.rb +5 -7
  155. data/spec/rspec/shared_spec_matchers.rb +96 -99
  156. data/spec/rspec/views_spec.rb +2 -1
  157. data/spec/rspec_matchers_spec.rb +19 -2
  158. data/spec/rspec_spec.rb +11 -15
  159. data/spec/selector_spec.rb +5 -6
  160. data/spec/selenium_spec_chrome.rb +7 -4
  161. data/spec/selenium_spec_marionette.rb +26 -12
  162. data/spec/server_spec.rb +33 -33
  163. data/spec/session_spec.rb +2 -1
  164. data/spec/shared_selenium_session.rb +27 -21
  165. data/spec/spec_helper.rb +2 -5
  166. metadata +66 -87
  167. data/lib/capybara/query.rb +0 -7
  168. data/spec/selenium_spec_firefox.rb +0 -68
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Capybara
3
4
  module Queries
4
5
  class SiblingQuery < MatchQuery
@@ -7,7 +8,7 @@ module Capybara
7
8
  @sibling_node = node
8
9
  node.synchronize do
9
10
  match_results = super(node.session.current_scope, exact)
10
- node.all(:xpath, XPath.preceding_sibling.union(XPath.following_sibling)) do |el|
11
+ node.all(:xpath, XPath.preceding_sibling + XPath.following_sibling) do |el|
11
12
  match_results.include?(el)
12
13
  end
13
14
  end
@@ -15,9 +16,8 @@ module Capybara
15
16
 
16
17
  def description
17
18
  desc = super
18
- if @sibling_node && (sibling_query = @sibling_node.instance_variable_get(:@query))
19
- desc += " that is a sibling of #{sibling_query.description}"
20
- end
19
+ sibling_query = @sibling_node && @sibling_node.instance_variable_get(:@query)
20
+ desc += " that is a sibling of #{sibling_query.description}" if sibling_query
21
21
  desc
22
22
  end
23
23
  end
@@ -1,22 +1,26 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Capybara
3
4
  # @api private
4
5
  module Queries
5
6
  class TextQuery < BaseQuery
6
- def initialize(*args)
7
- @type = (args.first.is_a?(Symbol) || args.first.nil?) ? args.shift : nil
8
- # @type = (Capybara.ignore_hidden_elements or Capybara.visible_text_only) ? :visible : :all if @type.nil?
9
- @options = if args.last.is_a?(Hash) then args.pop.dup else {} end
7
+ def initialize(type = nil, expected_text, session_options:, **options) # rubocop:disable Style/OptionalArguments
8
+ @type = if type.nil?
9
+ Capybara.ignore_hidden_elements || Capybara.visible_text_only ? :visible : :all
10
+ else
11
+ type
12
+ end
13
+ @expected_text = if expected_text.is_a?(Regexp)
14
+ expected_text
15
+ else
16
+ Capybara::Helpers.normalize_whitespace(expected_text)
17
+ end
18
+ @options = options
10
19
  super(@options)
20
+ self.session_options = session_options
11
21
 
12
- @type = (session_options.ignore_hidden_elements or session_options.visible_text_only) ? :visible : :all if @type.nil?
22
+ @search_regexp = Capybara::Helpers.to_regexp(@expected_text, exact: exact?)
13
23
 
14
- @expected_text = args.shift
15
- unless @expected_text.is_a?(Regexp)
16
- @expected_text = Capybara::Helpers.normalize_whitespace(@expected_text)
17
- end
18
- @search_regexp = Capybara::Helpers.to_regexp(@expected_text, nil, exact?)
19
- warn "Unused parameters passed to #{self.class.name} : #{args}" unless args.empty?
20
24
  assert_valid_keys
21
25
  end
22
26
 
@@ -42,48 +46,47 @@ module Capybara
42
46
  end
43
47
  end
44
48
 
45
- private
49
+ private
46
50
 
47
51
  def exact?
48
52
  options.fetch(:exact, session_options.exact_text)
49
53
  end
50
54
 
51
55
  def build_message(report_on_invisible)
52
- message = String.new()
56
+ message = "".dup
53
57
  unless (COUNT_KEYS & @options.keys).empty?
54
58
  message << " but found #{@count} #{Capybara::Helpers.declension('time', 'times', @count)}"
55
59
  end
56
60
  message << " in #{@actual_text.inspect}"
57
61
 
58
62
  details_message = []
63
+ details_message << case_insensitive_message if @node and !@expected_text.is_a? Regexp
64
+ details_message << invisible_message if @node and check_visible_text? and report_on_invisible
65
+ details_message.compact!
59
66
 
60
- if @node and !@expected_text.is_a? Regexp
61
- insensitive_regexp = Capybara::Helpers.to_regexp(@expected_text, Regexp::IGNORECASE)
62
- insensitive_count = @actual_text.scan(insensitive_regexp).size
63
- if insensitive_count != @count
64
- details_message << "it was found #{insensitive_count} #{Capybara::Helpers.declension("time", "times", insensitive_count)} using a case insensitive search"
65
- end
66
- end
67
+ message << ". (However, #{details_message.join(' and ')}.)" unless details_message.empty?
68
+ message
69
+ end
67
70
 
68
- if @node and check_visible_text? and report_on_invisible
69
- begin
70
- invisible_text = text(@node, :all)
71
- invisible_count = invisible_text.scan(@search_regexp).size
72
- if invisible_count != @count
73
- details_message << "it was found #{invisible_count} #{Capybara::Helpers.declension("time", "times", invisible_count)} including non-visible text"
74
- end
75
- rescue
76
- # An error getting the non-visible text (if element goes out of scope) should not affect the response
77
- end
71
+ def case_insensitive_message
72
+ insensitive_regexp = Capybara::Helpers.to_regexp(@expected_text, options: Regexp::IGNORECASE)
73
+ insensitive_count = @actual_text.scan(insensitive_regexp).size
74
+ if insensitive_count != @count
75
+ "it was found #{insensitive_count} #{Capybara::Helpers.declension("time", "times", insensitive_count)} using a case insensitive search"
78
76
  end
77
+ end
79
78
 
80
- message << ". (However, #{details_message.join(' and ')}.)" unless details_message.empty?
81
-
82
- message
79
+ def invisible_message
80
+ invisible_text = text(@node, :all)
81
+ invisible_count = invisible_text.scan(@search_regexp).size
82
+ if invisible_count != @count
83
+ "it was found #{invisible_count} #{Capybara::Helpers.declension("time", "times", invisible_count)} including non-visible text"
84
+ end
85
+ rescue # An error getting the non-visible text (if element goes out of scope) should not affect the response
83
86
  end
84
87
 
85
88
  def valid_keys
86
- COUNT_KEYS + [:wait, :exact]
89
+ COUNT_KEYS + %i[wait exact]
87
90
  end
88
91
 
89
92
  def check_visible_text?
@@ -1,22 +1,19 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Capybara
3
4
  # @api private
4
5
  module Queries
5
6
  class TitleQuery < BaseQuery
6
- def initialize(expected_title, options = {})
7
- @expected_title = expected_title
7
+ def initialize(expected_title, **options)
8
+ @expected_title = expected_title.is_a?(Regexp) ? expected_title : Capybara::Helpers.normalize_whitespace(expected_title)
8
9
  @options = options
9
10
  super(@options)
10
- unless @expected_title.is_a?(Regexp)
11
- @expected_title = Capybara::Helpers.normalize_whitespace(@expected_title)
12
- end
13
- @search_regexp = Capybara::Helpers.to_regexp(@expected_title, nil, options.fetch(:exact, false))
11
+ @search_regexp = Capybara::Helpers.to_regexp(@expected_title, exact: options.fetch(:exact, false))
14
12
  assert_valid_keys
15
13
  end
16
14
 
17
15
  def resolves_for?(node)
18
- @actual_title = node.title
19
- @actual_title.match(@search_regexp)
16
+ (@actual_title = node.title).match(@search_regexp)
20
17
  end
21
18
 
22
19
  def failure_message
@@ -27,15 +24,15 @@ module Capybara
27
24
  failure_message_helper(' not')
28
25
  end
29
26
 
30
- private
27
+ private
31
28
 
32
29
  def failure_message_helper(negated = '')
33
- verb = (@expected_title.is_a?(Regexp))? 'match' : 'include'
30
+ verb = @expected_title.is_a?(Regexp) ? 'match' : 'include'
34
31
  "expected #{@actual_title.inspect}#{negated} to #{verb} #{@expected_title.inspect}"
35
32
  end
36
33
 
37
34
  def valid_keys
38
- [:wait, :exact]
35
+ %i[wait exact]
39
36
  end
40
37
  end
41
38
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Capybara::RackTest::Browser
3
4
  include ::Rack::Test::Methods
4
5
 
@@ -17,7 +18,7 @@ class Capybara::RackTest::Browser
17
18
  driver.options
18
19
  end
19
20
 
20
- def visit(path, attributes = {})
21
+ def visit(path, **attributes)
21
22
  reset_host!
22
23
  process_and_follow_redirects(:get, path, attributes)
23
24
  end
@@ -28,23 +29,24 @@ class Capybara::RackTest::Browser
28
29
  end
29
30
 
30
31
  def submit(method, path, attributes)
31
- path = request_path if not path or path.empty?
32
- process_and_follow_redirects(method, path, attributes, {'HTTP_REFERER' => current_url})
32
+ path = request_path if path.nil? || path.empty?
33
+ process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => current_url)
33
34
  end
34
35
 
35
- def follow(method, path, attributes = {})
36
+ def follow(method, path, **attributes)
36
37
  return if path.gsub(/^#{Regexp.escape(request_path)}/, '').start_with?('#') || path.downcase.start_with?('javascript:')
37
- process_and_follow_redirects(method, path, attributes, {'HTTP_REFERER' => current_url})
38
+ process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => current_url)
38
39
  end
39
40
 
40
41
  def process_and_follow_redirects(method, path, attributes = {}, env = {})
41
42
  process(method, path, attributes, env)
42
- if driver.follow_redirects?
43
- driver.redirect_limit.times do
44
- process(:get, last_response["Location"], {}, env) if last_response.redirect?
45
- end
46
- raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects." if last_response.redirect?
43
+
44
+ return unless driver.follow_redirects?
45
+
46
+ driver.redirect_limit.times do
47
+ process(:get, last_response["Location"], {}, env) if last_response.redirect?
47
48
  end
49
+ raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects." if last_response.redirect?
48
50
  end
49
51
 
50
52
  def process(method, path, attributes = {}, env = {})
@@ -55,7 +57,7 @@ class Capybara::RackTest::Browser
55
57
  else
56
58
  new_uri.path = request_path if path.start_with?("?")
57
59
  new_uri.path = "/" if new_uri.path.empty?
58
- new_uri.path = request_path.sub(%r(/[^/]*$), '/') + new_uri.path unless new_uri.path.start_with?('/')
60
+ new_uri.path = request_path.sub(%r{/[^/]*$}, '/') + new_uri.path unless new_uri.path.start_with?('/')
59
61
  end
60
62
  new_uri.scheme ||= @current_scheme
61
63
  new_uri.host ||= @current_host
@@ -91,7 +93,7 @@ class Capybara::RackTest::Browser
91
93
  end
92
94
 
93
95
  def find(format, selector)
94
- if format==:css
96
+ if format == :css
95
97
  dom.css(selector, Capybara::RackTest::CSSHandlers.new)
96
98
  else
97
99
  dom.xpath(selector)
@@ -105,12 +107,7 @@ class Capybara::RackTest::Browser
105
107
  end
106
108
 
107
109
  def title
108
- if dom.respond_to? :title
109
- dom.title
110
- else
111
- #old versions of nokogiri don't have #title - remove in 3.0
112
- dom.xpath('/html/head/title | /html/title').first.text
113
- end
110
+ dom.title
114
111
  end
115
112
 
116
113
  protected
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Capybara::RackTest::CSSHandlers < BasicObject
3
4
  include ::Kernel
4
-
5
- def disabled list
5
+
6
+ def disabled(list)
6
7
  list.find_all { |node| node.has_attribute? 'disabled' }
7
- end
8
- def enabled list
8
+ end
9
+
10
+ def enabled(list)
9
11
  list.find_all { |node| !node.has_attribute? 'disabled' }
10
12
  end
11
13
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'rack/test'
3
4
  require 'rack/utils'
4
5
  require 'mini_mime'
@@ -10,10 +11,10 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
10
11
  respect_data_method: false,
11
12
  follow_redirects: true,
12
13
  redirect_limit: 5
13
- }
14
+ }.freeze
14
15
  attr_reader :app, :options
15
16
 
16
- def initialize(app, options={})
17
+ def initialize(app, **options)
17
18
  raise ArgumentError, "rack-test requires a rack application, but none was given" unless app
18
19
  @session = nil
19
20
  @app = app
@@ -40,7 +41,7 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
40
41
  browser.last_request
41
42
  end
42
43
 
43
- def visit(path, attributes = {})
44
+ def visit(path, **attributes)
44
45
  browser.visit(path, attributes)
45
46
  end
46
47
 
@@ -52,7 +53,7 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
52
53
  browser.submit(method, path, attributes)
53
54
  end
54
55
 
55
- def follow(method, path, attributes = {})
56
+ def follow(method, path, **attributes)
56
57
  browser.follow(method, path, attributes)
57
58
  end
58
59
 
@@ -73,7 +74,7 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
73
74
  end
74
75
 
75
76
  def find_css(selector)
76
- browser.find(:css,selector)
77
+ browser.find(:css, selector)
77
78
  end
78
79
 
79
80
  def html
@@ -92,11 +93,6 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
92
93
  @browser = nil
93
94
  end
94
95
 
95
- # @deprecated This method is being removed
96
- def browser_initialized?
97
- super && !@browser.nil?
98
- end
99
-
100
96
  def get(*args, &block); browser.get(*args, &block); end
101
97
  def post(*args, &block); browser.post(*args, &block); end
102
98
  def put(*args, &block); browser.put(*args, &block); end
@@ -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,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Capybara::RackTest::Node < Capybara::Driver::Node
3
4
  def all_text
4
5
  Capybara::Helpers.normalize_whitespace(native.text)
@@ -16,64 +17,45 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
16
17
  string_node.value
17
18
  end
18
19
 
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
20
+ def set(value, **options)
21
+ return if disabled? || readonly?
22
+
23
+ warn "Options passed to Node#set but the RackTest driver doesn't support any - ignoring" unless options.empty?
25
24
 
26
- if (Array === value) && !multiple?
27
- raise TypeError.new "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
25
+ if value.is_a?(Array) && !multiple?
26
+ raise TypeError, "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
28
27
  end
29
28
 
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
29
+ if radio? then set_radio(value)
30
+ elsif checkbox? then set_checkbox(value)
31
+ elsif input_field? then set_input(value)
32
+ elsif textarea? then native['_capybara_raw_value'] = value.to_s
38
33
  end
39
34
  end
40
35
 
41
36
  def select_option
42
37
  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
38
+ deselect_options unless select_node.multiple?
46
39
  native["selected"] = 'selected'
47
40
  end
48
41
 
49
42
  def unselect_option
50
- if select_node['multiple'] != 'multiple'
51
- raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
52
- end
43
+ raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box." unless select_node.multiple?
53
44
  native.remove_attribute('selected')
54
45
  end
55
46
 
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")
47
+ def click(keys = [], offset = {})
48
+ raise ArgumentError, "The RackTest driver does not support click options" unless keys.empty? && offset.empty?
49
+
50
+ if link?
51
+ follow_link
52
+ elsif submits?
63
53
  associated_form = form
64
54
  Capybara::RackTest::Form.new(driver, associated_form).submit(self) if associated_form
65
- elsif (tag_name == 'input' and %w(checkbox radio).include?(type))
55
+ elsif checkable?
66
56
  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
57
+ elsif tag_name == 'label'
58
+ click_label
77
59
  end
78
60
  end
79
61
 
@@ -94,10 +76,12 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
94
76
  end
95
77
 
96
78
  def disabled?
97
- if %w(option optgroup).include? tag_name
98
- string_node.disabled? || find_xpath("parent::*[self::optgroup or self::select]")[0].disabled?
79
+ return true if string_node.disabled?
80
+
81
+ if %w[option optgroup].include? tag_name
82
+ find_xpath("parent::*[self::optgroup or self::select]")[0].disabled?
99
83
  else
100
- string_node.disabled? || !find_xpath("parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]").empty?
84
+ !find_xpath("parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]").empty?
101
85
  end
102
86
  end
103
87
 
@@ -135,6 +119,10 @@ protected
135
119
 
136
120
  private
137
121
 
122
+ def deselect_options
123
+ select_node.find_xpath(".//option[@selected]").each { |node| node.native.remove_attribute("selected") }
124
+ end
125
+
138
126
  def string_node
139
127
  @string_node ||= Capybara::Node::Simple.new(native)
140
128
  end
@@ -150,19 +138,19 @@ private
150
138
 
151
139
  def form
152
140
  if native[:form]
153
- native.xpath("//form[@id='#{native[:form]}']").first
141
+ native.xpath("//form[@id='#{native[:form]}']")
154
142
  else
155
- native.ancestors('form').first
156
- end
143
+ native.ancestors('form')
144
+ end.first
157
145
  end
158
146
 
159
- def set_radio(_value)
160
- other_radios_xpath = XPath.generate { |x| x.anywhere(:input)[x.attr(:name).equals(self[:name])] }.to_s
147
+ def set_radio(_value) # rubocop:disable Naming/AccessorMethodName
148
+ other_radios_xpath = XPath.generate { |x| x.anywhere(:input)[x.attr(:name) == self[:name]] }.to_s
161
149
  driver.dom.xpath(other_radios_xpath).each { |node| node.remove_attribute("checked") }
162
150
  native['checked'] = 'checked'
163
151
  end
164
152
 
165
- def set_checkbox(value)
153
+ def set_checkbox(value) # rubocop:disable Naming/AccessorMethodName
166
154
  if value && !native['checked']
167
155
  native['checked'] = 'checked'
168
156
  elsif !value && native['checked']
@@ -170,13 +158,13 @@ private
170
158
  end
171
159
  end
172
160
 
173
- def set_input(value)
161
+ def set_input(value) # rubocop:disable Naming/AccessorMethodName
174
162
  if text_or_password? && attribute_is_not_blank?(:maxlength)
175
163
  # Browser behavior for maxlength="0" is inconsistent, so we stick with
176
164
  # Firefox, allowing no input
177
165
  value = value.to_s[0...self[:maxlength].to_i]
178
166
  end
179
- if Array === value #Assert multiple attribute is present
167
+ if value.is_a?(Array) # Assert multiple attribute is present
180
168
  value.each do |v|
181
169
  new_native = native.clone
182
170
  new_native.remove_attribute('value')
@@ -185,11 +173,7 @@ private
185
173
  end
186
174
  native.remove
187
175
  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
176
+ native['value'] = value.to_s
193
177
  end
194
178
  end
195
179
 
@@ -197,6 +181,36 @@ private
197
181
  self[attribute] && !self[attribute].empty?
198
182
  end
199
183
 
184
+ def follow_link
185
+ method = self["data-method"] if driver.options[:respect_data_method]
186
+ method ||= :get
187
+ driver.follow(method, self[:href].to_s)
188
+ end
189
+
190
+ def click_label
191
+ labelled_control = if native[:for]
192
+ find_xpath("//input[@id='#{native[:for]}']")
193
+ else
194
+ find_xpath(".//input")
195
+ end.first
196
+
197
+ if labelled_control && (labelled_control.checkbox? || labelled_control.radio?)
198
+ labelled_control.set(!labelled_control.checked?)
199
+ end
200
+ end
201
+
202
+ def link?
203
+ tag_name == 'a' && !self[:href].nil?
204
+ end
205
+
206
+ def submits?
207
+ (tag_name == 'input' and %w[submit image].include?(type)) || (tag_name == 'button' and [nil, "submit"].include?(type))
208
+ end
209
+
210
+ def checkable?
211
+ tag_name == 'input' and %w[checkbox radio].include?(type)
212
+ end
213
+
200
214
  protected
201
215
 
202
216
  def checkbox?