capybara 2.6.2 → 2.7.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 (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