capybara 2.5.0 → 2.18.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 (205) hide show
  1. checksums.yaml +5 -5
  2. data/.yard/templates_custom/default/class/html/selectors.erb +38 -0
  3. data/.yard/templates_custom/default/class/html/setup.rb +17 -0
  4. data/.yard/yard_extensions.rb +78 -0
  5. data/.yardopts +1 -0
  6. data/History.md +413 -10
  7. data/License.txt +1 -1
  8. data/README.md +237 -130
  9. data/lib/capybara/config.rb +132 -0
  10. data/lib/capybara/cucumber.rb +3 -1
  11. data/lib/capybara/driver/base.rb +27 -6
  12. data/lib/capybara/driver/node.rb +14 -5
  13. data/lib/capybara/dsl.rb +2 -3
  14. data/lib/capybara/helpers.rb +13 -65
  15. data/lib/capybara/minitest/spec.rb +177 -0
  16. data/lib/capybara/minitest.rb +278 -0
  17. data/lib/capybara/node/actions.rb +180 -24
  18. data/lib/capybara/node/base.rb +17 -5
  19. data/lib/capybara/node/document.rb +5 -0
  20. data/lib/capybara/node/document_matchers.rb +15 -14
  21. data/lib/capybara/node/element.rb +55 -7
  22. data/lib/capybara/node/finders.rb +179 -67
  23. data/lib/capybara/node/matchers.rb +301 -105
  24. data/lib/capybara/node/simple.rb +15 -4
  25. data/lib/capybara/queries/ancestor_query.rb +25 -0
  26. data/lib/capybara/queries/base_query.rb +69 -3
  27. data/lib/capybara/queries/current_path_query.rb +17 -8
  28. data/lib/capybara/queries/match_query.rb +19 -0
  29. data/lib/capybara/queries/selector_query.rb +251 -0
  30. data/lib/capybara/queries/sibling_query.rb +25 -0
  31. data/lib/capybara/queries/text_query.rb +67 -16
  32. data/lib/capybara/queries/title_query.rb +4 -2
  33. data/lib/capybara/query.rb +3 -131
  34. data/lib/capybara/rack_test/browser.rb +14 -5
  35. data/lib/capybara/rack_test/css_handlers.rb +1 -0
  36. data/lib/capybara/rack_test/driver.rb +15 -8
  37. data/lib/capybara/rack_test/form.rb +34 -12
  38. data/lib/capybara/rack_test/node.rb +29 -12
  39. data/lib/capybara/rails.rb +3 -3
  40. data/lib/capybara/result.rb +104 -9
  41. data/lib/capybara/rspec/compound.rb +95 -0
  42. data/lib/capybara/rspec/features.rb +17 -6
  43. data/lib/capybara/rspec/matcher_proxies.rb +45 -0
  44. data/lib/capybara/rspec/matchers.rb +199 -80
  45. data/lib/capybara/rspec.rb +4 -2
  46. data/lib/capybara/selector/css.rb +30 -0
  47. data/lib/capybara/selector/filter.rb +20 -0
  48. data/lib/capybara/selector/filter_set.rb +74 -0
  49. data/lib/capybara/selector/filters/base.rb +33 -0
  50. data/lib/capybara/selector/filters/expression_filter.rb +40 -0
  51. data/lib/capybara/selector/filters/node_filter.rb +27 -0
  52. data/lib/capybara/selector/selector.rb +276 -0
  53. data/lib/capybara/selector.rb +452 -157
  54. data/lib/capybara/selenium/driver.rb +282 -81
  55. data/lib/capybara/selenium/node.rb +144 -46
  56. data/lib/capybara/server.rb +59 -16
  57. data/lib/capybara/session/config.rb +114 -0
  58. data/lib/capybara/session/matchers.rb +29 -19
  59. data/lib/capybara/session.rb +378 -143
  60. data/lib/capybara/spec/fixtures/no_extension +1 -0
  61. data/lib/capybara/spec/public/jquery-ui.js +13 -791
  62. data/lib/capybara/spec/public/jquery.js +4 -9045
  63. data/lib/capybara/spec/public/test.js +45 -11
  64. data/lib/capybara/spec/session/accept_alert_spec.rb +30 -7
  65. data/lib/capybara/spec/session/accept_confirm_spec.rb +14 -2
  66. data/lib/capybara/spec/session/accept_prompt_spec.rb +35 -6
  67. data/lib/capybara/spec/session/all_spec.rb +45 -32
  68. data/lib/capybara/spec/session/ancestor_spec.rb +85 -0
  69. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +110 -0
  70. data/lib/capybara/spec/session/assert_current_path.rb +15 -2
  71. data/lib/capybara/spec/session/assert_selector.rb +29 -28
  72. data/lib/capybara/spec/session/assert_text.rb +59 -20
  73. data/lib/capybara/spec/session/assert_title.rb +25 -11
  74. data/lib/capybara/spec/session/attach_file_spec.rb +42 -4
  75. data/lib/capybara/spec/session/body_spec.rb +1 -0
  76. data/lib/capybara/spec/session/check_spec.rb +90 -14
  77. data/lib/capybara/spec/session/choose_spec.rb +31 -5
  78. data/lib/capybara/spec/session/click_button_spec.rb +20 -9
  79. data/lib/capybara/spec/session/click_link_or_button_spec.rb +15 -9
  80. data/lib/capybara/spec/session/click_link_spec.rb +39 -15
  81. data/lib/capybara/spec/session/current_scope_spec.rb +2 -1
  82. data/lib/capybara/spec/session/current_url_spec.rb +12 -3
  83. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +6 -5
  84. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +4 -3
  85. data/lib/capybara/spec/session/element/assert_match_selector.rb +36 -0
  86. data/lib/capybara/spec/session/element/match_css_spec.rb +23 -0
  87. data/lib/capybara/spec/session/element/match_xpath_spec.rb +23 -0
  88. data/lib/capybara/spec/session/element/matches_selector_spec.rb +106 -0
  89. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +22 -0
  90. data/lib/capybara/spec/session/evaluate_script_spec.rb +23 -1
  91. data/lib/capybara/spec/session/execute_script_spec.rb +22 -3
  92. data/lib/capybara/spec/session/fill_in_spec.rb +50 -32
  93. data/lib/capybara/spec/session/find_button_spec.rb +43 -2
  94. data/lib/capybara/spec/session/find_by_id_spec.rb +3 -2
  95. data/lib/capybara/spec/session/find_field_spec.rb +42 -6
  96. data/lib/capybara/spec/session/find_link_spec.rb +22 -3
  97. data/lib/capybara/spec/session/find_spec.rb +103 -57
  98. data/lib/capybara/spec/session/first_spec.rb +34 -18
  99. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +103 -0
  100. data/lib/capybara/spec/session/{within_frame_spec.rb → frame/within_frame_spec.rb} +44 -2
  101. data/lib/capybara/spec/session/go_back_spec.rb +2 -1
  102. data/lib/capybara/spec/session/go_forward_spec.rb +2 -1
  103. data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
  104. data/lib/capybara/spec/session/has_button_spec.rb +17 -8
  105. data/lib/capybara/spec/session/has_css_spec.rb +85 -73
  106. data/lib/capybara/spec/session/has_current_path_spec.rb +91 -7
  107. data/lib/capybara/spec/session/has_field_spec.rb +93 -58
  108. data/lib/capybara/spec/session/has_link_spec.rb +9 -8
  109. data/lib/capybara/spec/session/has_none_selectors_spec.rb +76 -0
  110. data/lib/capybara/spec/session/has_select_spec.rb +159 -59
  111. data/lib/capybara/spec/session/has_selector_spec.rb +64 -28
  112. data/lib/capybara/spec/session/has_table_spec.rb +1 -0
  113. data/lib/capybara/spec/session/has_text_spec.rb +27 -12
  114. data/lib/capybara/spec/session/has_title_spec.rb +22 -4
  115. data/lib/capybara/spec/session/has_xpath_spec.rb +32 -29
  116. data/lib/capybara/spec/session/headers.rb +2 -1
  117. data/lib/capybara/spec/session/html_spec.rb +4 -3
  118. data/lib/capybara/spec/session/node_spec.rb +198 -38
  119. data/lib/capybara/spec/session/refresh_spec.rb +28 -0
  120. data/lib/capybara/spec/session/reset_session_spec.rb +46 -5
  121. data/lib/capybara/spec/session/response_code.rb +2 -1
  122. data/lib/capybara/spec/session/save_and_open_page_spec.rb +1 -0
  123. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +6 -5
  124. data/lib/capybara/spec/session/save_page_spec.rb +34 -2
  125. data/lib/capybara/spec/session/save_screenshot_spec.rb +31 -1
  126. data/lib/capybara/spec/session/screenshot_spec.rb +4 -2
  127. data/lib/capybara/spec/session/select_spec.rb +34 -32
  128. data/lib/capybara/spec/session/selectors_spec.rb +65 -0
  129. data/lib/capybara/spec/session/sibling_spec.rb +52 -0
  130. data/lib/capybara/spec/session/text_spec.rb +4 -4
  131. data/lib/capybara/spec/session/title_spec.rb +2 -1
  132. data/lib/capybara/spec/session/uncheck_spec.rb +42 -2
  133. data/lib/capybara/spec/session/unselect_spec.rb +17 -16
  134. data/lib/capybara/spec/session/visit_spec.rb +77 -2
  135. data/lib/capybara/spec/session/window/become_closed_spec.rb +12 -11
  136. data/lib/capybara/spec/session/window/current_window_spec.rb +1 -0
  137. data/lib/capybara/spec/session/window/open_new_window_spec.rb +1 -0
  138. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +16 -11
  139. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +7 -4
  140. data/lib/capybara/spec/session/window/window_spec.rb +36 -29
  141. data/lib/capybara/spec/session/window/windows_spec.rb +1 -0
  142. data/lib/capybara/spec/session/window/within_window_spec.rb +31 -7
  143. data/lib/capybara/spec/session/within_spec.rb +14 -6
  144. data/lib/capybara/spec/spec_helper.rb +37 -4
  145. data/lib/capybara/spec/test_app.rb +15 -3
  146. data/lib/capybara/spec/views/buttons.erb +1 -0
  147. data/lib/capybara/spec/views/fieldsets.erb +2 -1
  148. data/lib/capybara/spec/views/form.erb +169 -9
  149. data/lib/capybara/spec/views/frame_child.erb +10 -2
  150. data/lib/capybara/spec/views/frame_one.erb +2 -1
  151. data/lib/capybara/spec/views/frame_parent.erb +3 -2
  152. data/lib/capybara/spec/views/frame_two.erb +2 -1
  153. data/lib/capybara/spec/views/header_links.erb +1 -0
  154. data/lib/capybara/spec/views/host_links.erb +1 -0
  155. data/lib/capybara/spec/views/initial_alert.erb +10 -0
  156. data/lib/capybara/spec/views/path.erb +1 -0
  157. data/lib/capybara/spec/views/popup_one.erb +1 -0
  158. data/lib/capybara/spec/views/popup_two.erb +1 -0
  159. data/lib/capybara/spec/views/postback.erb +2 -1
  160. data/lib/capybara/spec/views/tables.erb +1 -0
  161. data/lib/capybara/spec/views/with_base_tag.erb +1 -0
  162. data/lib/capybara/spec/views/with_count.erb +2 -1
  163. data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
  164. data/lib/capybara/spec/views/with_hover.erb +7 -1
  165. data/lib/capybara/spec/views/with_html.erb +40 -2
  166. data/lib/capybara/spec/views/with_html_entities.erb +1 -0
  167. data/lib/capybara/spec/views/with_js.erb +32 -1
  168. data/lib/capybara/spec/views/with_scope.erb +1 -0
  169. data/lib/capybara/spec/views/with_simple_html.erb +2 -1
  170. data/lib/capybara/spec/views/with_slow_unload.erb +17 -0
  171. data/lib/capybara/spec/views/with_title.erb +2 -1
  172. data/lib/capybara/spec/views/with_unload_alert.erb +14 -0
  173. data/lib/capybara/spec/views/with_windows.erb +7 -0
  174. data/lib/capybara/spec/views/within_frames.erb +3 -2
  175. data/lib/capybara/version.rb +2 -1
  176. data/lib/capybara/window.rb +20 -3
  177. data/lib/capybara.rb +189 -93
  178. data/spec/basic_node_spec.rb +7 -6
  179. data/spec/capybara_spec.rb +90 -4
  180. data/spec/dsl_spec.rb +3 -1
  181. data/spec/filter_set_spec.rb +28 -0
  182. data/spec/fixtures/capybara.csv +1 -0
  183. data/spec/fixtures/selenium_driver_rspec_failure.rb +5 -1
  184. data/spec/fixtures/selenium_driver_rspec_success.rb +5 -1
  185. data/spec/minitest_spec.rb +130 -0
  186. data/spec/minitest_spec_spec.rb +135 -0
  187. data/spec/per_session_config_spec.rb +67 -0
  188. data/spec/rack_test_spec.rb +50 -7
  189. data/spec/result_spec.rb +76 -0
  190. data/spec/rspec/features_spec.rb +21 -8
  191. data/spec/rspec/scenarios_spec.rb +21 -0
  192. data/spec/rspec/{matchers_spec.rb → shared_spec_matchers.rb} +160 -54
  193. data/spec/rspec/views_spec.rb +5 -0
  194. data/spec/rspec_matchers_spec.rb +46 -0
  195. data/spec/rspec_spec.rb +79 -1
  196. data/spec/selector_spec.rb +199 -0
  197. data/spec/selenium_spec_chrome.rb +54 -9
  198. data/spec/selenium_spec_firefox.rb +68 -0
  199. data/spec/selenium_spec_marionette.rb +127 -0
  200. data/spec/server_spec.rb +102 -14
  201. data/spec/session_spec.rb +54 -0
  202. data/spec/shared_selenium_session.rb +215 -0
  203. data/spec/spec_helper.rb +7 -0
  204. metadata +140 -15
  205. data/spec/selenium_spec.rb +0 -128
@@ -1,135 +1,7 @@
1
+ # frozen_string_literal: true
2
+ require 'capybara/queries/selector_query'
1
3
  module Capybara
2
4
  # @deprecated This class and its methods are not supposed to be used by users of Capybara's public API.
3
5
  # It may be removed in future versions of Capybara.
4
- class Query < Queries::BaseQuery
5
- attr_accessor :selector, :locator, :options, :expression, :find, :negative
6
-
7
- VALID_KEYS = [:text, :visible, :between, :count, :maximum, :minimum, :exact, :match, :wait]
8
- VALID_MATCH = [:first, :smart, :prefer_exact, :one]
9
-
10
- def initialize(*args)
11
- @options = if args.last.is_a?(Hash) then args.pop.dup else {} end
12
-
13
- if args[0].is_a?(Symbol)
14
- @selector = Selector.all[args[0]]
15
- @locator = args[1]
16
- else
17
- @selector = Selector.all.values.find { |s| s.match?(args[0]) }
18
- @locator = args[0]
19
- end
20
- @selector ||= Selector.all[Capybara.default_selector]
21
-
22
- # for compatibility with Capybara 2.0
23
- if Capybara.exact_options and @selector == Selector.all[:option]
24
- @options[:exact] = true
25
- end
26
-
27
- @expression = @selector.call(@locator)
28
- assert_valid_keys
29
- end
30
-
31
- def name; selector.name; end
32
- def label; selector.label or selector.name; end
33
-
34
- def description
35
- @description = "#{label} #{locator.inspect}"
36
- @description << " with text #{options[:text].inspect}" if options[:text]
37
- @description << selector.description(options)
38
- @description
39
- end
40
-
41
- def matches_filters?(node)
42
- if options[:text]
43
- regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text].to_s)
44
- return false if not node.text(visible).match(regexp)
45
- end
46
- case visible
47
- when :visible then return false unless node.visible?
48
- when :hidden then return false if node.visible?
49
- end
50
- selector.custom_filters.each do |name, filter|
51
- if options.has_key?(name)
52
- return false unless filter.matches?(node, options[name])
53
- elsif filter.default?
54
- return false unless filter.matches?(node, filter.default)
55
- end
56
- end
57
- end
58
-
59
- def visible
60
- if options.has_key?(:visible)
61
- case @options[:visible]
62
- when true then :visible
63
- when false then :all
64
- else @options[:visible]
65
- end
66
- else
67
- if Capybara.ignore_hidden_elements
68
- :visible
69
- else
70
- :all
71
- end
72
- end
73
- end
74
-
75
- def exact?
76
- if options.has_key?(:exact)
77
- @options[:exact]
78
- else
79
- Capybara.exact
80
- end
81
- end
82
-
83
- def match
84
- if options.has_key?(:match)
85
- @options[:match]
86
- else
87
- Capybara.match
88
- end
89
- end
90
-
91
- def xpath(exact=nil)
92
- exact = self.exact? if exact == nil
93
- if @expression.respond_to?(:to_xpath) and exact
94
- @expression.to_xpath(:exact)
95
- else
96
- @expression.to_s
97
- end
98
- end
99
-
100
- def css
101
- @expression
102
- end
103
-
104
- # @api private
105
- def resolve_for(node, exact = nil)
106
- node.synchronize do
107
- children = if selector.format == :css
108
- node.find_css(self.css)
109
- else
110
- node.find_xpath(self.xpath(exact))
111
- end.map do |child|
112
- if node.is_a?(Capybara::Node::Base)
113
- Capybara::Node::Element.new(node.session, child, node, self)
114
- else
115
- Capybara::Node::Simple.new(child)
116
- end
117
- end
118
- Capybara::Result.new(children, self)
119
- end
120
- end
121
-
122
- private
123
-
124
- def valid_keys
125
- COUNT_KEYS + [:text, :visible, :exact, :match, :wait] + @selector.custom_filters.keys
126
- end
127
-
128
- def assert_valid_keys
129
- super
130
- unless VALID_MATCH.include?(match)
131
- raise ArgumentError, "invalid option #{match.inspect} for :match, should be one of #{VALID_MATCH.map(&:inspect).join(", ")}"
132
- end
133
- end
134
- end
6
+ Query = Queries::SelectorQuery
135
7
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  class Capybara::RackTest::Browser
2
3
  include ::Rack::Test::Methods
3
4
 
@@ -21,6 +22,11 @@ class Capybara::RackTest::Browser
21
22
  process_and_follow_redirects(:get, path, attributes)
22
23
  end
23
24
 
25
+ def refresh
26
+ reset_cache!
27
+ request(last_request.fullpath, last_request.env)
28
+ end
29
+
24
30
  def submit(method, path, attributes)
25
31
  path = request_path if not path or path.empty?
26
32
  process_and_follow_redirects(method, path, attributes, {'HTTP_REFERER' => current_url})
@@ -44,10 +50,13 @@ class Capybara::RackTest::Browser
44
50
  def process(method, path, attributes = {}, env = {})
45
51
  new_uri = URI.parse(path)
46
52
  method.downcase! unless method.is_a? Symbol
47
-
48
- new_uri.path = request_path if path.start_with?("?")
49
- new_uri.path = "/" if new_uri.path.empty?
50
- new_uri.path = request_path.sub(%r(/[^/]*$), '/') + new_uri.path unless new_uri.path.start_with?('/')
53
+ if path.empty?
54
+ new_uri.path = request_path
55
+ else
56
+ new_uri.path = request_path if path.start_with?("?")
57
+ 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?('/')
59
+ end
51
60
  new_uri.scheme ||= @current_scheme
52
61
  new_uri.host ||= @current_host
53
62
  new_uri.port ||= @current_port unless new_uri.default_port == @current_port
@@ -67,7 +76,7 @@ class Capybara::RackTest::Browser
67
76
  end
68
77
 
69
78
  def reset_host!
70
- uri = URI.parse(Capybara.app_host || Capybara.default_host)
79
+ uri = URI.parse(driver.session_options.app_host || driver.session_options.default_host)
71
80
  @current_scheme = uri.scheme
72
81
  @current_host = uri.host
73
82
  @current_port = uri.port
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  class Capybara::RackTest::CSSHandlers < BasicObject
2
3
  include ::Kernel
3
4
 
@@ -1,19 +1,21 @@
1
+ # frozen_string_literal: true
1
2
  require 'rack/test'
2
3
  require 'rack/utils'
3
- require 'mime/types'
4
+ require 'mini_mime'
4
5
  require 'nokogiri'
5
6
  require 'cgi'
6
7
 
7
8
  class Capybara::RackTest::Driver < Capybara::Driver::Base
8
9
  DEFAULT_OPTIONS = {
9
- :respect_data_method => false,
10
- :follow_redirects => true,
11
- :redirect_limit => 5
10
+ respect_data_method: false,
11
+ follow_redirects: true,
12
+ redirect_limit: 5
12
13
  }
13
14
  attr_reader :app, :options
14
15
 
15
16
  def initialize(app, options={})
16
17
  raise ArgumentError, "rack-test requires a rack application, but none was given" unless app
18
+ @session = nil
17
19
  @app = app
18
20
  @options = DEFAULT_OPTIONS.merge(options)
19
21
  end
@@ -42,6 +44,10 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
42
44
  browser.visit(path, attributes)
43
45
  end
44
46
 
47
+ def refresh
48
+ browser.refresh
49
+ end
50
+
45
51
  def submit(method, path, attributes)
46
52
  browser.submit(method, path, attributes)
47
53
  end
@@ -65,11 +71,11 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
65
71
  def find_xpath(selector)
66
72
  browser.find(:xpath, selector)
67
73
  end
68
-
74
+
69
75
  def find_css(selector)
70
76
  browser.find(:css,selector)
71
77
  end
72
-
78
+
73
79
  def html
74
80
  browser.html
75
81
  end
@@ -77,7 +83,7 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
77
83
  def dom
78
84
  browser.dom
79
85
  end
80
-
86
+
81
87
  def title
82
88
  browser.title
83
89
  end
@@ -86,8 +92,9 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
86
92
  @browser = nil
87
93
  end
88
94
 
95
+ # @deprecated This method is being removed
89
96
  def browser_initialized?
90
- !@browser.nil?
97
+ super && !@browser.nil?
91
98
  end
92
99
 
93
100
  def get(*args, &block); browser.get(*args, &block); end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  class Capybara::RackTest::Form < Capybara::RackTest::Node
2
3
  # This only needs to inherit from Rack::Test::UploadedFile because Rack::Test checks for
3
4
  # the class specifically when determining whether to construct the request as multipart.
@@ -12,18 +13,20 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
12
13
  def original_filename; ""; end
13
14
  def content_type; "application/octet-stream"; end
14
15
  def path; @empty_file.path; end
16
+ def size; 0; end
17
+ def read; ""; end
15
18
  end
16
19
 
17
20
  def params(button)
18
- params = {}
21
+ params = make_params
19
22
 
20
23
  form_element_types=[:input, :select, :textarea]
21
- form_elements_xpath=XPath.generate do |x|
24
+ form_elements_xpath=XPath.generate do |x|
22
25
  xpath=x.descendant(*form_element_types).where(~x.attr(:form))
23
26
  xpath=xpath.union(x.anywhere(*form_element_types).where(x.attr(:form) == native[:id])) if native[:id]
24
27
  xpath.where(~x.attr(:disabled))
25
28
  end.to_s
26
-
29
+
27
30
  native.xpath(form_elements_xpath).map do |field|
28
31
  case field.name
29
32
  when 'input'
@@ -31,7 +34,7 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
31
34
  if field['checked']
32
35
  node=Capybara::RackTest::Node.new(self.driver, field)
33
36
  merge_param!(params, field['name'].to_s, node.value.to_s)
34
- end
37
+ end
35
38
  elsif %w(submit image).include? field['type']
36
39
  # TO DO identify the click button here (in document order, rather
37
40
  # than leaving until the end of the params)
@@ -41,8 +44,8 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
41
44
  if (value = field['value']).to_s.empty?
42
45
  NilUploadedFile.new
43
46
  else
44
- content_type = MIME::Types.type_for(value).first.to_s
45
- Rack::Test::UploadedFile.new(value, content_type)
47
+ mime_info = MiniMime.lookup_by_filename(value)
48
+ Rack::Test::UploadedFile.new(value, (mime_info && mime_info.content_type).to_s)
46
49
  end
47
50
  merge_param!(params, field['name'].to_s, file)
48
51
  else
@@ -63,17 +66,18 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
63
66
  merge_param!(params, field['name'].to_s, (option['value'] || option.text).to_s) if option
64
67
  end
65
68
  when 'textarea'
66
- merge_param!(params, field['name'].to_s, field.text.to_s.gsub(/\n/, "\r\n"))
69
+ merge_param!(params, field['name'].to_s, field['_capybara_raw_value'].to_s.gsub(/\n/, "\r\n"))
67
70
  end
68
71
  end
69
72
  merge_param!(params, button[:name], button[:value] || "") if button[:name]
70
- params
73
+
74
+ params.to_params_hash
71
75
  end
72
76
 
73
77
  def submit(button)
74
78
  action = (button && button['formaction']) || native['action']
75
- requset_method = (button && button['formmethod']) || method
76
- driver.submit(requset_method, action.to_s, params(button))
79
+ method = (button && button['formmethod']) || request_method
80
+ driver.submit(method, action.to_s, params(button))
77
81
  end
78
82
 
79
83
  def multipart?
@@ -82,11 +86,29 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
82
86
 
83
87
  private
84
88
 
85
- def method
89
+ class ParamsHash < Hash
90
+ def to_params_hash
91
+ self
92
+ end
93
+ end
94
+
95
+ def request_method
86
96
  self[:method] =~ /post/i ? :post : :get
87
97
  end
88
98
 
89
99
  def merge_param!(params, key, value)
90
- Rack::Utils.normalize_params(params, key, value)
100
+ if Rack::Utils.respond_to?(:default_query_parser)
101
+ Rack::Utils.default_query_parser.normalize_params(params, key, value, Rack::Utils.param_depth_limit)
102
+ else
103
+ Rack::Utils.normalize_params(params, key, value)
104
+ end
105
+ end
106
+
107
+ def make_params
108
+ if Rack::Utils.respond_to?(:default_query_parser)
109
+ Rack::Utils.default_query_parser.make_params
110
+ else
111
+ ParamsHash.new
112
+ end
91
113
  end
92
114
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  class Capybara::RackTest::Node < Capybara::Driver::Node
2
3
  def all_text
3
4
  Capybara::Helpers.normalize_whitespace(native.text)
@@ -16,8 +17,14 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
16
17
  end
17
18
 
18
19
  def set(value)
19
- if (Array === value) && !self[:multiple]
20
- raise ArgumentError.new "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
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
25
+
26
+ if (Array === value) && !multiple?
27
+ raise TypeError.new "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
21
28
  end
22
29
 
23
30
  if radio?
@@ -27,11 +34,7 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
27
34
  elsif input_field?
28
35
  set_input(value)
29
36
  elsif textarea?
30
- if self[:readonly]
31
- warn "Attempt to set readonly element with value: #{value} \n * This will raise an exception in a future version of Capybara"
32
- else
33
- native.content = value.to_s
34
- end
37
+ native['_capybara_raw_value'] = value.to_s
35
38
  end
36
39
  end
37
40
 
@@ -59,6 +62,18 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
59
62
  ((tag_name == 'button') and type.nil? or type == "submit")
60
63
  associated_form = form
61
64
  Capybara::RackTest::Form.new(driver, associated_form).submit(self) if associated_form
65
+ elsif (tag_name == 'input' and %w(checkbox radio).include?(type))
66
+ 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
62
77
  end
63
78
  end
64
79
 
@@ -80,9 +95,9 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
80
95
 
81
96
  def disabled?
82
97
  if %w(option optgroup).include? tag_name
83
- string_node.disabled? || find_xpath("parent::*")[0].disabled?
98
+ string_node.disabled? || find_xpath("parent::*[self::optgroup or self::select]")[0].disabled?
84
99
  else
85
- string_node.disabled?
100
+ string_node.disabled? || !find_xpath("parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]").empty?
86
101
  end
87
102
  end
88
103
 
@@ -126,7 +141,7 @@ private
126
141
 
127
142
  # a reference to the select node if this is an option node
128
143
  def select_node
129
- find_xpath('./ancestor::select').first
144
+ find_xpath('./ancestor::select[1]').first
130
145
  end
131
146
 
132
147
  def type
@@ -141,7 +156,7 @@ private
141
156
  end
142
157
  end
143
158
 
144
- def set_radio(value)
159
+ def set_radio(_value)
145
160
  other_radios_xpath = XPath.generate { |x| x.anywhere(:input)[x.attr(:name).equals(self[:name])] }.to_s
146
161
  driver.dom.xpath(other_radios_xpath).each { |node| node.remove_attribute("checked") }
147
162
  native['checked'] = 'checked'
@@ -170,7 +185,7 @@ private
170
185
  end
171
186
  native.remove
172
187
  else
173
- if self[:readonly]
188
+ if readonly?
174
189
  warn "Attempt to set readonly element with value: #{value} \n *This will raise an exception in a future version of Capybara"
175
190
  else
176
191
  native['value'] = value.to_s
@@ -182,6 +197,8 @@ private
182
197
  self[attribute] && !self[attribute].empty?
183
198
  end
184
199
 
200
+ protected
201
+
185
202
  def checkbox?
186
203
  input_field? && type == 'checkbox'
187
204
  end
@@ -1,4 +1,4 @@
1
- require 'capybara'
1
+ # frozen_string_literal: true
2
2
  require 'capybara/dsl'
3
3
 
4
4
  Capybara.app = Rack::Builder.new do
@@ -12,9 +12,9 @@ Capybara.app = Rack::Builder.new do
12
12
  end
13
13
  end.to_app
14
14
 
15
- Capybara.save_and_open_page_path = Rails.root.join('tmp/capybara')
15
+ Capybara.save_path = Rails.root.join('tmp/capybara')
16
16
 
17
17
  # Override default rack_test driver to respect data-method attributes.
18
18
  Capybara.register_driver :rack_test do |app|
19
- Capybara::RackTest::Driver.new(app, :respect_data_method => true)
19
+ Capybara::RackTest::Driver.new(app, respect_data_method: true)
20
20
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'forwardable'
2
3
 
3
4
  module Capybara
@@ -24,27 +25,93 @@ module Capybara
24
25
 
25
26
  def initialize(elements, query)
26
27
  @elements = elements
27
- @result = elements.select { |node| query.matches_filters?(node) }
28
- @rest = @elements - @result
28
+ @result_cache = []
29
+ @results_enum = lazy_select_elements { |node| query.matches_filters?(node) }
29
30
  @query = query
30
31
  end
31
32
 
32
- def_delegators :@result, :each, :[], :at, :size, :count, :length,
33
- :first, :last, :values_at, :empty?, :inspect, :sample, :index
33
+ def_delegators :full_results, :size, :length, :last, :values_at, :inspect, :sample
34
+
35
+ alias :index :find_index
36
+
37
+ def each(&block)
38
+ return enum_for(:each) unless block_given?
39
+
40
+ @result_cache.each(&block)
41
+ loop do
42
+ next_result = @results_enum.next
43
+ @result_cache << next_result
44
+ block.call(next_result)
45
+ end
46
+ self
47
+ end
48
+
49
+ def [](*args)
50
+ if (args.size == 1) && ((idx = args[0]).is_a? Integer) && (idx >= 0)
51
+ @result_cache << @results_enum.next while @result_cache.size <= idx
52
+ @result_cache[idx]
53
+ else
54
+ full_results[*args]
55
+ end
56
+ rescue StopIteration
57
+ return nil
58
+ end
59
+ alias :at :[]
60
+
61
+ def empty?
62
+ !any?
63
+ end
34
64
 
35
65
  def matches_count?
36
- Capybara::Helpers.matches_count?(@result.size, @query.options)
66
+ # Only check filters for as many elements as necessary to determine result
67
+ if @query.options[:count]
68
+ count_opt = Integer(@query.options[:count])
69
+ loop do
70
+ break if @result_cache.size > count_opt
71
+ @result_cache << @results_enum.next
72
+ end
73
+ return @result_cache.size == count_opt
74
+ end
75
+
76
+ if @query.options[:minimum]
77
+ min_opt = Integer(@query.options[:minimum])
78
+ begin
79
+ @result_cache << @results_enum.next while @result_cache.size < min_opt
80
+ rescue StopIteration
81
+ return false
82
+ end
83
+ end
84
+
85
+ if @query.options[:maximum]
86
+ max_opt = Integer(@query.options[:maximum])
87
+ begin
88
+ @result_cache << @results_enum.next while @result_cache.size <= max_opt
89
+ return false
90
+ rescue StopIteration
91
+ end
92
+ end
93
+
94
+ if @query.options[:between]
95
+ max = Integer(@query.options[:between].max)
96
+ loop do
97
+ break if @result_cache.size > max
98
+ @result_cache << @results_enum.next
99
+ end
100
+ return false unless (@query.options[:between] === @result_cache.size)
101
+ end
102
+
103
+ return true
37
104
  end
38
105
 
39
106
  def failure_message
40
- message = Capybara::Helpers.failure_message(@query.description, @query.options)
107
+ message = @query.failure_message
41
108
  if count > 0
42
- message << ", found #{count} #{Capybara::Helpers.declension("match", "matches", count)}: " << @result.map(&:text).map(&:inspect).join(", ")
109
+ message << ", found #{count} #{Capybara::Helpers.declension("match", "matches", count)}: " << full_results.map(&:text).map(&:inspect).join(", ")
43
110
  else
44
111
  message << " but there were no matches"
45
112
  end
46
- unless @rest.empty?
47
- elements = @rest.map(&:text).map(&:inspect).join(", ")
113
+ unless rest.empty?
114
+ elements = rest.map(&:text).map(&:inspect).join(", ")
48
115
  message << ". Also found " << elements << ", which matched the selector but not all filters."
49
116
  end
50
117
  message
@@ -53,5 +120,33 @@ module Capybara
53
120
  def negative_failure_message
54
121
  failure_message.sub(/(to find)/, 'not \1')
55
122
  end
123
+
124
+ private
125
+
126
+ def full_results
127
+ loop { @result_cache << @results_enum.next }
128
+ @result_cache
129
+ end
130
+
131
+ def rest
132
+ @rest ||= @elements - full_results
133
+ end
134
+
135
+ def lazy_select_elements(&block)
136
+ # JRuby has an issue with lazy enumerators which
137
+ # causes a concurrency issue with network requests here
138
+ # https://github.com/jruby/jruby/issues/4212
139
+ 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)
143
+ else
144
+ Enumerator.new do |yielder|
145
+ @elements.each do |val|
146
+ yielder.yield(val) if block.call(val)
147
+ end
148
+ end
149
+ end
150
+ end
56
151
  end
57
152
  end