capybara 2.5.0 → 2.18.0

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