capybara 2.6.2 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (163) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +40 -15
  3. data/README.md +45 -38
  4. data/lib/capybara.rb +86 -10
  5. data/lib/capybara/cucumber.rb +1 -0
  6. data/lib/capybara/driver/base.rb +5 -2
  7. data/lib/capybara/driver/node.rb +1 -0
  8. data/lib/capybara/dsl.rb +1 -0
  9. data/lib/capybara/helpers.rb +3 -2
  10. data/lib/capybara/node/actions.rb +6 -0
  11. data/lib/capybara/node/base.rb +1 -0
  12. data/lib/capybara/node/document.rb +1 -0
  13. data/lib/capybara/node/document_matchers.rb +1 -0
  14. data/lib/capybara/node/element.rb +1 -0
  15. data/lib/capybara/node/finders.rb +3 -2
  16. data/lib/capybara/node/matchers.rb +72 -2
  17. data/lib/capybara/node/simple.rb +6 -3
  18. data/lib/capybara/queries/base_query.rb +1 -0
  19. data/lib/capybara/queries/current_path_query.rb +1 -0
  20. data/lib/capybara/queries/match_query.rb +21 -0
  21. data/lib/capybara/queries/selector_query.rb +138 -0
  22. data/lib/capybara/queries/text_query.rb +1 -0
  23. data/lib/capybara/queries/title_query.rb +1 -0
  24. data/lib/capybara/query.rb +3 -131
  25. data/lib/capybara/rack_test/browser.rb +1 -0
  26. data/lib/capybara/rack_test/css_handlers.rb +1 -0
  27. data/lib/capybara/rack_test/driver.rb +6 -4
  28. data/lib/capybara/rack_test/form.rb +1 -0
  29. data/lib/capybara/rack_test/node.rb +1 -0
  30. data/lib/capybara/rails.rb +2 -1
  31. data/lib/capybara/result.rb +1 -0
  32. data/lib/capybara/rspec.rb +1 -0
  33. data/lib/capybara/rspec/features.rb +1 -0
  34. data/lib/capybara/rspec/matchers.rb +54 -3
  35. data/lib/capybara/selector.rb +175 -75
  36. data/lib/capybara/selector/filter.rb +48 -0
  37. data/lib/capybara/selenium/driver.rb +24 -7
  38. data/lib/capybara/selenium/node.rb +1 -0
  39. data/lib/capybara/server.rb +38 -6
  40. data/lib/capybara/session.rb +30 -14
  41. data/lib/capybara/session/matchers.rb +2 -1
  42. data/lib/capybara/spec/public/test.js +6 -0
  43. data/lib/capybara/spec/session/accept_alert_spec.rb +2 -1
  44. data/lib/capybara/spec/session/accept_confirm_spec.rb +2 -1
  45. data/lib/capybara/spec/session/accept_prompt_spec.rb +2 -1
  46. data/lib/capybara/spec/session/all_spec.rb +1 -0
  47. data/lib/capybara/spec/session/assert_current_path.rb +1 -0
  48. data/lib/capybara/spec/session/assert_selector.rb +1 -0
  49. data/lib/capybara/spec/session/assert_text.rb +1 -0
  50. data/lib/capybara/spec/session/assert_title.rb +1 -0
  51. data/lib/capybara/spec/session/attach_file_spec.rb +1 -0
  52. data/lib/capybara/spec/session/body_spec.rb +1 -0
  53. data/lib/capybara/spec/session/check_spec.rb +1 -0
  54. data/lib/capybara/spec/session/choose_spec.rb +1 -0
  55. data/lib/capybara/spec/session/click_button_spec.rb +1 -0
  56. data/lib/capybara/spec/session/click_link_or_button_spec.rb +1 -0
  57. data/lib/capybara/spec/session/click_link_spec.rb +1 -0
  58. data/lib/capybara/spec/session/current_scope_spec.rb +2 -1
  59. data/lib/capybara/spec/session/current_url_spec.rb +1 -0
  60. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +2 -1
  61. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +2 -1
  62. data/lib/capybara/spec/session/element/assert_match_selector.rb +31 -0
  63. data/lib/capybara/spec/session/element/match_css_spec.rb +17 -0
  64. data/lib/capybara/spec/session/element/match_xpath_spec.rb +23 -0
  65. data/lib/capybara/spec/session/element/matches_selector_spec.rb +63 -0
  66. data/lib/capybara/spec/session/evaluate_script_spec.rb +1 -0
  67. data/lib/capybara/spec/session/execute_script_spec.rb +1 -0
  68. data/lib/capybara/spec/session/fill_in_spec.rb +1 -0
  69. data/lib/capybara/spec/session/find_button_spec.rb +1 -0
  70. data/lib/capybara/spec/session/find_by_id_spec.rb +1 -0
  71. data/lib/capybara/spec/session/find_field_spec.rb +1 -0
  72. data/lib/capybara/spec/session/find_link_spec.rb +1 -0
  73. data/lib/capybara/spec/session/find_spec.rb +1 -0
  74. data/lib/capybara/spec/session/first_spec.rb +1 -0
  75. data/lib/capybara/spec/session/go_back_spec.rb +1 -0
  76. data/lib/capybara/spec/session/go_forward_spec.rb +1 -0
  77. data/lib/capybara/spec/session/has_button_spec.rb +1 -0
  78. data/lib/capybara/spec/session/has_css_spec.rb +1 -0
  79. data/lib/capybara/spec/session/has_current_path_spec.rb +1 -0
  80. data/lib/capybara/spec/session/has_field_spec.rb +13 -0
  81. data/lib/capybara/spec/session/has_link_spec.rb +1 -0
  82. data/lib/capybara/spec/session/has_select_spec.rb +19 -0
  83. data/lib/capybara/spec/session/has_selector_spec.rb +6 -0
  84. data/lib/capybara/spec/session/has_table_spec.rb +1 -0
  85. data/lib/capybara/spec/session/has_text_spec.rb +1 -0
  86. data/lib/capybara/spec/session/has_title_spec.rb +1 -0
  87. data/lib/capybara/spec/session/has_xpath_spec.rb +1 -0
  88. data/lib/capybara/spec/session/headers.rb +1 -0
  89. data/lib/capybara/spec/session/html_spec.rb +1 -0
  90. data/lib/capybara/spec/session/node_spec.rb +29 -0
  91. data/lib/capybara/spec/session/reset_session_spec.rb +35 -1
  92. data/lib/capybara/spec/session/response_code.rb +1 -0
  93. data/lib/capybara/spec/session/save_and_open_page_spec.rb +1 -0
  94. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +3 -2
  95. data/lib/capybara/spec/session/save_page_spec.rb +34 -2
  96. data/lib/capybara/spec/session/save_screenshot_spec.rb +31 -1
  97. data/lib/capybara/spec/session/screenshot_spec.rb +3 -1
  98. data/lib/capybara/spec/session/select_spec.rb +1 -0
  99. data/lib/capybara/spec/session/selectors_spec.rb +35 -0
  100. data/lib/capybara/spec/session/text_spec.rb +1 -0
  101. data/lib/capybara/spec/session/title_spec.rb +2 -1
  102. data/lib/capybara/spec/session/uncheck_spec.rb +1 -0
  103. data/lib/capybara/spec/session/unselect_spec.rb +1 -0
  104. data/lib/capybara/spec/session/visit_spec.rb +1 -0
  105. data/lib/capybara/spec/session/window/become_closed_spec.rb +1 -0
  106. data/lib/capybara/spec/session/window/current_window_spec.rb +1 -0
  107. data/lib/capybara/spec/session/window/open_new_window_spec.rb +1 -0
  108. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +1 -0
  109. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +1 -0
  110. data/lib/capybara/spec/session/window/window_spec.rb +1 -8
  111. data/lib/capybara/spec/session/window/windows_spec.rb +1 -0
  112. data/lib/capybara/spec/session/window/within_window_spec.rb +2 -3
  113. data/lib/capybara/spec/session/within_frame_spec.rb +1 -0
  114. data/lib/capybara/spec/session/within_spec.rb +1 -0
  115. data/lib/capybara/spec/spec_helper.rb +2 -0
  116. data/lib/capybara/spec/test_app.rb +1 -0
  117. data/lib/capybara/spec/views/buttons.erb +1 -0
  118. data/lib/capybara/spec/views/fieldsets.erb +2 -1
  119. data/lib/capybara/spec/views/form.erb +24 -0
  120. data/lib/capybara/spec/views/frame_child.erb +2 -1
  121. data/lib/capybara/spec/views/frame_one.erb +2 -1
  122. data/lib/capybara/spec/views/frame_parent.erb +2 -1
  123. data/lib/capybara/spec/views/frame_two.erb +2 -1
  124. data/lib/capybara/spec/views/header_links.erb +1 -0
  125. data/lib/capybara/spec/views/host_links.erb +1 -0
  126. data/lib/capybara/spec/views/path.erb +1 -0
  127. data/lib/capybara/spec/views/popup_one.erb +1 -0
  128. data/lib/capybara/spec/views/popup_two.erb +1 -0
  129. data/lib/capybara/spec/views/postback.erb +2 -1
  130. data/lib/capybara/spec/views/tables.erb +1 -0
  131. data/lib/capybara/spec/views/with_base_tag.erb +1 -0
  132. data/lib/capybara/spec/views/with_count.erb +2 -1
  133. data/lib/capybara/spec/views/with_hover.erb +2 -1
  134. data/lib/capybara/spec/views/with_html.erb +3 -0
  135. data/lib/capybara/spec/views/with_html_entities.erb +1 -0
  136. data/lib/capybara/spec/views/with_js.erb +5 -0
  137. data/lib/capybara/spec/views/with_scope.erb +1 -0
  138. data/lib/capybara/spec/views/with_simple_html.erb +2 -1
  139. data/lib/capybara/spec/views/with_slow_unload.erb +17 -0
  140. data/lib/capybara/spec/views/with_title.erb +2 -1
  141. data/lib/capybara/spec/views/with_unload_alert.erb +12 -0
  142. data/lib/capybara/spec/views/with_windows.erb +1 -0
  143. data/lib/capybara/spec/views/within_frames.erb +2 -1
  144. data/lib/capybara/version.rb +2 -1
  145. data/lib/capybara/window.rb +1 -0
  146. data/spec/basic_node_spec.rb +1 -0
  147. data/spec/capybara_spec.rb +28 -1
  148. data/spec/dsl_spec.rb +1 -0
  149. data/spec/fixtures/selenium_driver_rspec_failure.rb +1 -0
  150. data/spec/fixtures/selenium_driver_rspec_success.rb +1 -0
  151. data/spec/rack_test_spec.rb +1 -0
  152. data/spec/result_spec.rb +1 -0
  153. data/spec/rspec/features_spec.rb +1 -0
  154. data/spec/rspec/matchers_spec.rb +1 -0
  155. data/spec/rspec/scenarios_spec.rb +1 -0
  156. data/spec/rspec/views_spec.rb +1 -0
  157. data/spec/rspec_spec.rb +1 -0
  158. data/spec/selector_spec.rb +55 -1
  159. data/spec/selenium_spec.rb +6 -2
  160. data/spec/selenium_spec_chrome.rb +1 -0
  161. data/spec/server_spec.rb +101 -13
  162. data/spec/spec_helper.rb +1 -0
  163. metadata +16 -6
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Capybara
2
3
  # @api private
3
4
  module Queries
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Capybara
2
3
  # @api private
3
4
  module Queries
@@ -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
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  class Capybara::RackTest::CSSHandlers < BasicObject
2
3
  include ::Kernel
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'rack/test'
2
3
  require 'rack/utils'
3
4
  require 'mime/types'
@@ -65,11 +66,11 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
65
66
  def find_xpath(selector)
66
67
  browser.find(:xpath, selector)
67
68
  end
68
-
69
+
69
70
  def find_css(selector)
70
71
  browser.find(:css,selector)
71
72
  end
72
-
73
+
73
74
  def html
74
75
  browser.html
75
76
  end
@@ -77,7 +78,7 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
77
78
  def dom
78
79
  browser.dom
79
80
  end
80
-
81
+
81
82
  def title
82
83
  browser.title
83
84
  end
@@ -86,8 +87,9 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
86
87
  @browser = nil
87
88
  end
88
89
 
90
+ # @deprecated This method is being removed
89
91
  def browser_initialized?
90
- !@browser.nil?
92
+ super && !@browser.nil?
91
93
  end
92
94
 
93
95
  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.
@@ -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)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'capybara/dsl'
2
3
 
3
4
  Capybara.app = Rack::Builder.new do
@@ -11,7 +12,7 @@ Capybara.app = Rack::Builder.new do
11
12
  end
12
13
  end.to_app
13
14
 
14
- Capybara.save_and_open_page_path = Rails.root.join('tmp/capybara')
15
+ Capybara.save_path = Rails.root.join('tmp/capybara')
15
16
 
16
17
  # Override default rack_test driver to respect data-method attributes.
17
18
  Capybara.register_driver :rack_test do |app|
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'forwardable'
2
3
 
3
4
  module Capybara
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'capybara/dsl'
2
3
  require 'rspec/core'
3
4
  require 'capybara/rspec/matchers'
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  if RSpec::Core::Version::STRING.to_f >= 3.0
2
3
  RSpec.shared_context "Capybara Features", :capybara_feature => true do
3
4
  instance_eval do
@@ -1,7 +1,8 @@
1
+ # frozen_string_literal: true
1
2
  module Capybara
2
3
  module RSpecMatchers
3
4
  class Matcher
4
- include ::RSpec::Matchers::Composable if defined?(::RSpec::Expectations::Version) && RSpec::Expectations::Version::STRING.to_f >= 3.0
5
+ include ::RSpec::Matchers::Composable if defined?(::RSpec::Expectations::Version) && (Gem::Version.new(RSpec::Expectations::Version::STRING) >= Gem::Version.new('3.0'))
5
6
 
6
7
  def wrap(actual)
7
8
  if actual.respond_to?("has_selector?")
@@ -38,7 +39,7 @@ module Capybara
38
39
  end
39
40
 
40
41
  def query
41
- @query ||= Capybara::Query.new(*@args)
42
+ @query ||= Capybara::Queries::SelectorQuery.new(*@args)
42
43
  end
43
44
 
44
45
  # RSpec 2 compatibility:
@@ -160,7 +161,7 @@ module Capybara
160
161
 
161
162
  class BecomeClosed
162
163
  def initialize(options)
163
- @wait_time = Capybara::Query.new(options).wait
164
+ @wait_time = Capybara::Queries::SelectorQuery.new(options).wait
164
165
  end
165
166
 
166
167
  def matches?(window)
@@ -186,18 +187,68 @@ module Capybara
186
187
  alias_method :failure_message_for_should_not, :failure_message_when_negated
187
188
  end
188
189
 
190
+ class MatchSelector < Matcher
191
+ attr_reader :failure_message, :failure_message_when_negated
192
+
193
+ def initialize(*args)
194
+ @args = args
195
+ end
196
+
197
+ def matches?(actual)
198
+ actual.assert_matches_selector(*@args)
199
+ rescue Capybara::ExpectationNotMet => e
200
+ @failure_message = e.message
201
+ return false
202
+ end
203
+
204
+ def does_not_match?(actual)
205
+ actual.assert_not_matches_selector(*@args)
206
+ rescue Capybara::ExpectationNotMet => e
207
+ @failure_message_when_negated = e.message
208
+ return false
209
+ end
210
+
211
+ def description
212
+ "match #{query.description}"
213
+ end
214
+
215
+ def query
216
+ @query ||= Capybara::Queries::MatchQuery.new(*@args)
217
+ end
218
+
219
+ # RSpec 2 compatibility:
220
+ alias_method :failure_message_for_should, :failure_message
221
+ alias_method :failure_message_for_should_not, :failure_message_when_negated
222
+ end
223
+
189
224
  def have_selector(*args)
190
225
  HaveSelector.new(*args)
191
226
  end
192
227
 
228
+ def match_selector(*args)
229
+ MatchSelector.new(*args)
230
+ end
231
+ # defined_negated_matcher was added in RSpec 3.1 - it's syntactic sugar only since a user can do
232
+ # expect(page).not_to match_selector, so not sure we really need to support not_match_selector for prior to RSpec 3.1
233
+ ::RSpec::Matchers.define_negated_matcher :not_match_selector, :match_selector if defined?(::RSpec::Expectations::Version) && (Gem::Version.new(RSpec::Expectations::Version::STRING) >= Gem::Version.new('3.1'))
234
+
235
+
193
236
  def have_xpath(xpath, options={})
194
237
  HaveSelector.new(:xpath, xpath, options)
195
238
  end
196
239
 
240
+ def match_xpath(xpath, options={})
241
+ MatchSelector.new(:xpath, xpath, options)
242
+ end
243
+
197
244
  def have_css(css, options={})
198
245
  HaveSelector.new(:css, css, options)
199
246
  end
200
247
 
248
+ def match_css(css, options={})
249
+ MatchSelector.new(:css, css, options)
250
+ end
251
+
201
252
  def have_text(*args)
202
253
  HaveText.new(*args)
203
254
  end
@@ -1,42 +1,8 @@
1
+ # frozen_string_literal: true
2
+ require 'capybara/selector/filter'
3
+
1
4
  module Capybara
2
5
  class Selector
3
- class Filter
4
- def initialize(name, block, options={})
5
- @name = name
6
- @block = block
7
- @options = options
8
- @options[:valid_values] = [true,false] if options[:boolean]
9
- end
10
-
11
- def default?
12
- @options.has_key?(:default)
13
- end
14
-
15
- def default
16
- @options[:default]
17
- end
18
-
19
- def matches?(node, value)
20
- return true if skip?(value)
21
-
22
- if @options.has_key?(:valid_values) && !Array(@options[:valid_values]).include?(value)
23
- msg = "Invalid value #{value.inspect} passed to filter #{@name} - "
24
- if default?
25
- warn msg + "defaulting to #{default}"
26
- value = default
27
- else
28
- warn msg + "skipping"
29
- return true
30
- end
31
- end
32
-
33
- @block.call(node, value)
34
- end
35
-
36
- def skip?(value)
37
- @options.has_key?(:skip_if) && value == @options[:skip_if]
38
- end
39
- end
40
6
 
41
7
  attr_reader :name, :custom_filters, :format
42
8
 
@@ -65,24 +31,19 @@ module Capybara
65
31
  @label = nil
66
32
  @failure_message = nil
67
33
  @description = nil
34
+ @format = nil
35
+ @expression = nil
68
36
  instance_eval(&block)
69
37
  end
70
38
 
71
39
  def xpath(&block)
72
- if block
73
- @format = :xpath
74
- @xpath, @css = block, nil
75
- end
76
- @xpath
40
+ @format, @expression = :xpath, block if block
41
+ format == :xpath ? @expression : nil
77
42
  end
78
43
 
79
- # Same as xpath, but wrap in XPath.css().
80
44
  def css(&block)
81
- if block
82
- @format = :css
83
- @css, @xpath = block, nil
84
- end
85
- @css
45
+ @format, @expression = :css, block if block
46
+ format == :css ? @expression : nil
86
47
  end
87
48
 
88
49
  def match(&block)
@@ -100,10 +61,10 @@ module Capybara
100
61
  end
101
62
 
102
63
  def call(locator)
103
- if @format==:css
104
- @css.call(locator)
64
+ if format
65
+ @expression.call(locator)
105
66
  else
106
- @xpath.call(locator)
67
+ warn "Selector has no format"
107
68
  end
108
69
  end
109
70
 
@@ -118,6 +79,17 @@ module Capybara
118
79
  def describe &block
119
80
  @description = block
120
81
  end
82
+
83
+ private
84
+
85
+ def locate_field(xpath, locator)
86
+ locate_field = xpath[XPath.attr(:id).equals(locator) |
87
+ XPath.attr(:name).equals(locator) |
88
+ XPath.attr(:placeholder).equals(locator) |
89
+ XPath.attr(:id).equals(XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for))]
90
+ locate_field += XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath)
91
+ locate_field
92
+ end
121
93
  end
122
94
  end
123
95
 
@@ -134,7 +106,11 @@ Capybara.add_selector(:id) do
134
106
  end
135
107
 
136
108
  Capybara.add_selector(:field) do
137
- xpath { |locator| XPath::HTML.field(locator) }
109
+ xpath do |locator|
110
+ xpath = XPath.descendant(:input, :textarea, :select)[~XPath.attr(:type).one_of('submit', 'image', 'hidden')]
111
+ xpath = locate_field(xpath, locator.to_s) unless locator.nil?
112
+ xpath
113
+ end
138
114
  filter(:checked, boolean: true) { |node, value| not(value ^ node.checked?) }
139
115
  filter(:unchecked, boolean: true) { |node, value| (value ^ node.checked?) }
140
116
  filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
@@ -147,31 +123,42 @@ Capybara.add_selector(:field) do
147
123
  node[:type] == type
148
124
  end
149
125
  end
126
+ filter(:multiple, boolean: true) { |node, value| !(value ^ node[:multiple]) }
150
127
  describe do |options|
151
- desc, states = "", []
128
+ desc, states = String.new, []
152
129
  desc << " of type #{options[:type].inspect}" if options[:type]
153
130
  desc << " with value #{options[:with].to_s.inspect}" if options.has_key?(:with)
154
131
  states << 'checked' if options[:checked] || (options.has_key?(:unchecked) && !options[:unchecked])
155
132
  states << 'not checked' if options[:unchecked] || (options.has_key?(:checked) && !options[:checked])
156
133
  states << 'disabled' if options[:disabled] == true
157
134
  desc << " that is #{states.join(' and ')}" unless states.empty?
135
+ desc << " with the multiple attribute" if options[:multiple] == true
136
+ desc << " without the multiple attribute" if options[:multiple] === false
158
137
  desc
159
138
  end
160
139
  end
161
140
 
162
141
  Capybara.add_selector(:fieldset) do
163
- xpath { |locator| XPath::HTML.fieldset(locator) }
164
- end
165
-
166
- Capybara.add_selector(:link_or_button) do
167
- label "link or button"
168
- xpath { |locator| XPath::HTML.link_or_button(locator) }
169
- filter(:disabled, default: false, boolean: true) { |node, value| node.tag_name == "a" or not(value ^ node.disabled?) }
170
- describe { |options| " that is disabled" if options[:disabled] }
142
+ xpath do |locator|
143
+ xpath = XPath.descendant(:fieldset)
144
+ xpath = xpath[XPath.attr(:id).equals(locator.to_s) | XPath.child(:legend)[XPath.string.n.is(locator.to_s)]] unless locator.nil?
145
+ xpath
146
+ end
171
147
  end
172
148
 
173
149
  Capybara.add_selector(:link) do
174
- xpath { |locator| XPath::HTML.link(locator) }
150
+ xpath do |locator|
151
+ xpath = XPath.descendant(:a)[XPath.attr(:href)]
152
+ unless locator.nil?
153
+ locator = locator.to_s
154
+ xpath = xpath[XPath.attr(:id).equals(locator) |
155
+ XPath.string.n.is(locator) |
156
+ XPath.attr(:title).is(locator) |
157
+ XPath.descendant(:img)[XPath.attr(:alt).is(locator)]]
158
+ end
159
+ xpath
160
+ end
161
+
175
162
  filter(:href) do |node, href|
176
163
  if href.is_a? Regexp
177
164
  node[:href].match href
@@ -179,31 +166,77 @@ Capybara.add_selector(:link) do
179
166
  node.first(:xpath, XPath.axis(:self)[XPath.attr(:href).equals(href.to_s)], minimum: 0)
180
167
  end
181
168
  end
169
+
182
170
  describe { |options| " with href #{options[:href].inspect}" if options[:href] }
183
171
  end
184
172
 
185
173
  Capybara.add_selector(:button) do
186
- xpath { |locator| XPath::HTML.button(locator) }
174
+ xpath do |locator|
175
+ input_btn_xpath = XPath.descendant(:input)[XPath.attr(:type).one_of('submit', 'reset', 'image', 'button')]
176
+ btn_xpath = XPath.descendant(:button)
177
+ image_btn_xpath = XPath.descendant(:input)[XPath.attr(:type).equals('image')]
178
+
179
+ unless locator.nil?
180
+ locator = locator.to_s
181
+ input_btn_xpath = input_btn_xpath[XPath.attr(:id).equals(locator) | XPath.attr(:value).is(locator) | XPath.attr(:title).is(locator)]
182
+ btn_xpath = btn_xpath[XPath.attr(:id).equals(locator) | XPath.attr(:value).is(locator) | XPath.string.n.is(locator) | XPath.attr(:title).is(locator)]
183
+ image_btn_xpath = image_btn_xpath[XPath.attr(:alt).is(locator)]
184
+ end
185
+
186
+ input_btn_xpath + btn_xpath + image_btn_xpath
187
+ end
188
+
187
189
  filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
190
+
188
191
  describe { |options| " that is disabled" if options[:disabled] == true }
189
192
  end
190
193
 
194
+ Capybara.add_selector(:link_or_button) do
195
+ label "link or button"
196
+ xpath do |locator|
197
+ self.class.all.values_at(:link, :button).map {|selector| selector.xpath.call(locator)}.reduce(:+)
198
+ end
199
+
200
+ filter(:disabled, default: false, boolean: true) { |node, value| node.tag_name == "a" or not(value ^ node.disabled?) }
201
+
202
+ describe { |options| " that is disabled" if options[:disabled] }
203
+ end
204
+
191
205
  Capybara.add_selector(:fillable_field) do
192
206
  label "field"
193
- xpath { |locator| XPath::HTML.fillable_field(locator) }
207
+ xpath do |locator|
208
+ xpath = XPath.descendant(:input, :textarea)[~XPath.attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file')]
209
+ xpath = locate_field(xpath, locator.to_s) unless locator.nil?
210
+ xpath
211
+ end
212
+
194
213
  filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
195
- describe { |options| " that is disabled" if options[:disabled] == true }
214
+ filter(:multiple, boolean: true) { |node, value| !(value ^ node[:multiple]) }
215
+
216
+ describe do |options|
217
+ desc = String.new
218
+ desc << " that is disabled" if options[:disabled] == true
219
+ desc << " with the multiple attribute" if options[:multiple] == true
220
+ desc << " without the multiple attribute" if options[:multiple] === false
221
+ desc
222
+ end
196
223
  end
197
224
 
198
225
  Capybara.add_selector(:radio_button) do
199
226
  label "radio button"
200
- xpath { |locator| XPath::HTML.radio_button(locator) }
227
+ xpath do |locator|
228
+ xpath = XPath.descendant(:input)[XPath.attr(:type).equals('radio')]
229
+ xpath = locate_field(xpath, locator.to_s) unless locator.nil?
230
+ xpath
231
+ end
232
+
201
233
  filter(:checked, boolean: true) { |node, value| not(value ^ node.checked?) }
202
234
  filter(:unchecked, boolean: true) { |node, value| (value ^ node.checked?) }
203
235
  filter(:option) { |node, value| node.value == value.to_s }
204
236
  filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
237
+
205
238
  describe do |options|
206
- desc, states = "", []
239
+ desc, states = String.new, []
207
240
  desc << " with value #{options[:option].inspect}" if options[:option]
208
241
  states << 'checked' if options[:checked] || (options.has_key?(:unchecked) && !options[:unchecked])
209
242
  states << 'not checked' if options[:unchecked] || (options.has_key?(:checked) && !options[:checked])
@@ -214,13 +247,19 @@ Capybara.add_selector(:radio_button) do
214
247
  end
215
248
 
216
249
  Capybara.add_selector(:checkbox) do
217
- xpath { |locator| XPath::HTML.checkbox(locator) }
250
+ xpath do |locator|
251
+ xpath = XPath.descendant(:input)[XPath.attr(:type).equals('checkbox')]
252
+ xpath = locate_field(xpath, locator.to_s) unless locator.nil?
253
+ xpath
254
+ end
255
+
218
256
  filter(:checked, boolean: true) { |node, value| not(value ^ node.checked?) }
219
257
  filter(:unchecked, boolean: true) { |node, value| (value ^ node.checked?) }
220
258
  filter(:option) { |node, value| node.value == value.to_s }
221
259
  filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
260
+
222
261
  describe do |options|
223
- desc, states = "", []
262
+ desc, states = String.new, []
224
263
  desc << " with value #{options[:option].inspect}" if options[:option]
225
264
  states << 'checked' if options[:checked] || (options.has_key?(:unchecked) && !options[:unchecked])
226
265
  states << 'not checked' if options[:unchecked] || (options.has_key?(:checked) && !options[:checked])
@@ -232,7 +271,12 @@ end
232
271
 
233
272
  Capybara.add_selector(:select) do
234
273
  label "select box"
235
- xpath { |locator| XPath::HTML.select(locator) }
274
+ xpath do |locator|
275
+ xpath = XPath.descendant(:select)
276
+ xpath = locate_field(xpath, locator.to_s) unless locator.nil?
277
+ xpath
278
+ end
279
+
236
280
  filter(:options) do |node, options|
237
281
  if node.visible?
238
282
  actual = node.all(:xpath, './/option').map { |option| option.text }
@@ -253,27 +297,83 @@ Capybara.add_selector(:select) do
253
297
  [selected].flatten.sort == actual.sort
254
298
  end
255
299
  filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
300
+ filter(:multiple, boolean: true) { |node, value| !(value ^ node[:multiple]) }
301
+
256
302
  describe do |options|
257
- desc = ""
303
+ desc = String.new
258
304
  desc << " with options #{options[:options].inspect}" if options[:options]
259
305
  desc << " with at least options #{options[:with_options].inspect}" if options[:with_options]
260
306
  desc << " with #{options[:selected].inspect} selected" if options[:selected]
261
307
  desc << " that is disabled" if options[:disabled] == true
308
+ desc << " that allows multiple selection" if options[:multiple] == true
309
+ desc << " that only allows single selection" if options[:multiple] === false
262
310
  desc
263
311
  end
264
312
  end
265
313
 
266
314
  Capybara.add_selector(:option) do
267
- xpath { |locator| XPath::HTML.option(locator) }
315
+ xpath do |locator|
316
+ xpath = XPath.descendant(:option)
317
+ xpath = xpath[XPath.string.n.is(locator.to_s)] unless locator.nil?
318
+ xpath
319
+ end
320
+
321
+ filter(:disabled, boolean: true) { |node, value| not(value ^ node.disabled?) }
322
+ filter(:selected, boolean: true) { |node, value| not(value ^ node.selected?) }
323
+
324
+ describe do |options|
325
+ desc = String.new
326
+ desc << " that is#{' not' unless options[:disabled]} disabled" if options.has_key?(:disabled)
327
+ desc << " that is#{' not' unless options[:selected]} selected" if options.has_key?(:selected)
328
+ desc
329
+ end
268
330
  end
269
331
 
270
332
  Capybara.add_selector(:file_field) do
271
333
  label "file field"
272
- xpath { |locator| XPath::HTML.file_field(locator) }
334
+ xpath do |locator|
335
+ xpath = XPath.descendant(:input)[XPath.attr(:type).equals('file')]
336
+ xpath = locate_field(xpath, locator.to_s) unless locator.nil?
337
+ xpath
338
+ end
339
+
273
340
  filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
274
- describe { |options| " that is disabled" if options[:disabled] == true}
341
+ filter(:multiple, boolean: true) { |node, value| !(value ^ node[:multiple]) }
342
+
343
+ describe do |options|
344
+ desc = String.new
345
+ desc << " that is disabled" if options[:disabled] == true
346
+ desc << " that allows multiple files" if options[:multiple] == true
347
+ desc << " that only allows a single file" if options[:multiple] === false
348
+ desc
349
+ end
350
+ end
351
+
352
+ Capybara.add_selector(:label) do
353
+ label "label"
354
+ xpath do |locator|
355
+ xpath = XPath.descendant(:label)
356
+ xpath = xpath[XPath.string.n.is(locator.to_s) | XPath.attr(:id).equals(locator.to_s)] unless locator.nil?
357
+ xpath
358
+ end
359
+
360
+ filter(:for) do |node, field_or_value|
361
+ if field_or_value.is_a? Capybara::Node::Element
362
+ if field_or_value[:id] && (field_or_value[:id] == node[:for])
363
+ true
364
+ else
365
+ field_or_value.find_xpath('./ancestor::label[1]').include? node.base
366
+ end
367
+ else
368
+ node[:for] == field_or_value.to_s
369
+ end
370
+ end
275
371
  end
276
372
 
277
373
  Capybara.add_selector(:table) do
278
- xpath { |locator| XPath::HTML.table(locator) }
374
+ xpath do |locator|
375
+ xpath = XPath.descendant(:table)
376
+ xpath = xpath[XPath.attr(:id).equals(locator.to_s) | XPath.descendant(:caption).is(locator.to_s)] unless locator.nil?
377
+ xpath
378
+ end
279
379
  end