capybara 1.1.4 → 2.0.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 (111) hide show
  1. data/{History.txt → History.md} +138 -0
  2. data/License.txt +22 -0
  3. data/README.md +850 -0
  4. data/lib/capybara/cucumber.rb +2 -5
  5. data/lib/capybara/driver/base.rb +6 -6
  6. data/lib/capybara/driver/node.rb +3 -2
  7. data/lib/capybara/dsl.rb +13 -124
  8. data/lib/capybara/helpers.rb +33 -0
  9. data/lib/capybara/node/actions.rb +16 -30
  10. data/lib/capybara/node/base.rb +56 -13
  11. data/lib/capybara/node/element.rb +18 -30
  12. data/lib/capybara/node/finders.rb +28 -90
  13. data/lib/capybara/node/matchers.rb +121 -73
  14. data/lib/capybara/node/simple.rb +13 -11
  15. data/lib/capybara/query.rb +78 -0
  16. data/lib/capybara/rack_test/browser.rb +27 -39
  17. data/lib/capybara/rack_test/driver.rb +13 -3
  18. data/lib/capybara/rack_test/node.rb +31 -2
  19. data/lib/capybara/result.rb +72 -0
  20. data/lib/capybara/rspec/features.rb +4 -1
  21. data/lib/capybara/rspec/matchers.rb +33 -63
  22. data/lib/capybara/rspec.rb +7 -4
  23. data/lib/capybara/selector.rb +97 -34
  24. data/lib/capybara/selenium/driver.rb +15 -62
  25. data/lib/capybara/selenium/node.rb +14 -21
  26. data/lib/capybara/server.rb +32 -27
  27. data/lib/capybara/session.rb +90 -50
  28. data/lib/capybara/spec/fixtures/another_test_file.txt +1 -0
  29. data/lib/capybara/spec/public/jquery-ui.js +791 -0
  30. data/lib/capybara/spec/public/jquery.js +9046 -0
  31. data/lib/capybara/spec/public/test.js +3 -0
  32. data/lib/capybara/spec/session/all_spec.rb +61 -59
  33. data/lib/capybara/spec/session/assert_selector.rb +123 -0
  34. data/lib/capybara/spec/session/attach_file_spec.rb +72 -55
  35. data/lib/capybara/spec/session/body_spec.rb +21 -0
  36. data/lib/capybara/spec/session/check_spec.rb +68 -48
  37. data/lib/capybara/spec/session/choose_spec.rb +32 -18
  38. data/lib/capybara/spec/session/click_button_spec.rb +263 -232
  39. data/lib/capybara/spec/session/click_link_or_button_spec.rb +40 -29
  40. data/lib/capybara/spec/session/click_link_spec.rb +96 -96
  41. data/lib/capybara/spec/session/current_url_spec.rb +88 -10
  42. data/lib/capybara/spec/session/evaluate_script_spec.rb +6 -0
  43. data/lib/capybara/spec/session/execute_script_spec.rb +7 -0
  44. data/lib/capybara/spec/session/fill_in_spec.rb +119 -103
  45. data/lib/capybara/spec/session/find_button_spec.rb +16 -14
  46. data/lib/capybara/spec/session/find_by_id_spec.rb +16 -14
  47. data/lib/capybara/spec/session/find_field_spec.rb +23 -21
  48. data/lib/capybara/spec/session/find_link_spec.rb +15 -14
  49. data/lib/capybara/spec/session/find_spec.rb +93 -115
  50. data/lib/capybara/spec/session/first_spec.rb +51 -85
  51. data/lib/capybara/spec/session/has_button_spec.rb +22 -24
  52. data/lib/capybara/spec/session/has_css_spec.rb +190 -205
  53. data/lib/capybara/spec/session/has_field_spec.rb +170 -144
  54. data/lib/capybara/spec/session/has_link_spec.rb +26 -29
  55. data/lib/capybara/spec/session/has_select_spec.rb +161 -109
  56. data/lib/capybara/spec/session/has_selector_spec.rb +94 -100
  57. data/lib/capybara/spec/session/has_table_spec.rb +22 -88
  58. data/lib/capybara/spec/session/has_text_spec.rb +195 -0
  59. data/lib/capybara/spec/session/has_xpath_spec.rb +100 -96
  60. data/lib/capybara/spec/session/headers.rb +4 -17
  61. data/lib/capybara/spec/session/html_spec.rb +15 -0
  62. data/lib/capybara/spec/session/node_spec.rb +205 -0
  63. data/lib/capybara/spec/session/reset_session_spec.rb +42 -0
  64. data/lib/capybara/spec/session/response_code.rb +4 -17
  65. data/lib/capybara/spec/session/save_page_spec.rb +46 -0
  66. data/lib/capybara/spec/session/screenshot.rb +13 -0
  67. data/lib/capybara/spec/session/select_spec.rb +99 -88
  68. data/lib/capybara/spec/session/source_spec.rb +12 -0
  69. data/lib/capybara/spec/session/text_spec.rb +15 -12
  70. data/lib/capybara/spec/session/uncheck_spec.rb +22 -17
  71. data/lib/capybara/spec/session/unselect_spec.rb +69 -58
  72. data/lib/capybara/spec/session/visit_spec.rb +74 -0
  73. data/lib/capybara/spec/session/within_frame_spec.rb +31 -0
  74. data/lib/capybara/spec/session/within_spec.rb +118 -131
  75. data/lib/capybara/spec/session/within_window_spec.rb +38 -0
  76. data/lib/capybara/spec/spec_helper.rb +84 -0
  77. data/lib/capybara/spec/test_app.rb +32 -6
  78. data/lib/capybara/spec/views/form.erb +12 -10
  79. data/lib/capybara/spec/views/host_links.erb +2 -2
  80. data/lib/capybara/spec/views/tables.erb +6 -66
  81. data/lib/capybara/spec/views/with_html.erb +9 -4
  82. data/lib/capybara/spec/views/with_js.erb +11 -7
  83. data/lib/capybara/version.rb +1 -1
  84. data/lib/capybara.rb +125 -6
  85. data/spec/basic_node_spec.rb +17 -5
  86. data/spec/capybara_spec.rb +9 -0
  87. data/spec/dsl_spec.rb +31 -17
  88. data/spec/rack_test_spec.rb +157 -0
  89. data/spec/result_spec.rb +51 -0
  90. data/spec/rspec/features_spec.rb +19 -2
  91. data/spec/rspec/matchers_spec.rb +170 -89
  92. data/spec/rspec_spec.rb +1 -3
  93. data/spec/selenium_spec.rb +53 -0
  94. data/spec/server_spec.rb +37 -25
  95. data/spec/spec_helper.rb +1 -30
  96. metadata +39 -31
  97. data/README.rdoc +0 -722
  98. data/lib/capybara/spec/driver.rb +0 -301
  99. data/lib/capybara/spec/session/current_host_spec.rb +0 -68
  100. data/lib/capybara/spec/session/has_content_spec.rb +0 -106
  101. data/lib/capybara/spec/session/javascript.rb +0 -306
  102. data/lib/capybara/spec/session.rb +0 -154
  103. data/lib/capybara/util/save_and_open_page.rb +0 -44
  104. data/lib/capybara/util/timeout.rb +0 -27
  105. data/spec/driver/rack_test_driver_spec.rb +0 -89
  106. data/spec/driver/selenium_driver_spec.rb +0 -37
  107. data/spec/save_and_open_page_spec.rb +0 -155
  108. data/spec/session/rack_test_session_spec.rb +0 -55
  109. data/spec/session/selenium_session_spec.rb +0 -26
  110. data/spec/string_spec.rb +0 -77
  111. data/spec/timeout_spec.rb +0 -28
@@ -18,48 +18,45 @@ class Capybara::RackTest::Browser
18
18
 
19
19
  def visit(path, attributes = {})
20
20
  reset_host!
21
- process(:get, path, attributes)
22
- follow_redirects!
21
+ process_and_follow_redirects(:get, path, attributes)
23
22
  end
24
23
 
25
24
  def submit(method, path, attributes)
26
25
  path = request_path if not path or path.empty?
27
- process(method, path, attributes)
28
- follow_redirects!
26
+ process_and_follow_redirects(method, path, attributes, {'HTTP_REFERER' => current_url})
29
27
  end
30
28
 
31
29
  def follow(method, path, attributes = {})
32
30
  return if path.gsub(/^#{request_path}/, '').start_with?('#')
33
- process(method, path, attributes)
34
- follow_redirects!
31
+ process_and_follow_redirects(method, path, attributes, {'HTTP_REFERER' => current_url})
35
32
  end
36
33
 
37
- def follow_redirects!
38
- 5.times do
39
- process(:get, last_response["Location"]) if last_response.redirect?
34
+ def process_and_follow_redirects(method, path, attributes = {}, env = {})
35
+ process(method, path, attributes, env)
36
+ if driver.follow_redirects?
37
+ driver.redirect_limit.times do
38
+ process(:get, last_response["Location"], {}, env) if last_response.redirect?
39
+ end
40
+ raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects." if last_response.redirect?
40
41
  end
41
- raise Capybara::InfiniteRedirectError, "redirected more than 5 times, check for infinite redirects." if last_response.redirect?
42
42
  end
43
43
 
44
- def process(method, path, attributes = {})
44
+ def process(method, path, attributes = {}, env = {})
45
45
  new_uri = URI.parse(path)
46
- current_uri = URI.parse(current_url)
46
+ method.downcase! unless method.is_a? Symbol
47
47
 
48
- if new_uri.host
49
- @current_host = new_uri.scheme + '://' + new_uri.host
50
- end
48
+ new_uri.path = request_path if path.start_with?("?")
49
+ new_uri.path = request_path.sub(%r(/[^/]*$), '/') + new_uri.path unless new_uri.path.start_with?('/')
50
+ new_uri.scheme ||= @current_scheme
51
+ new_uri.host ||= @current_host
52
+ new_uri.port ||= @current_port unless new_uri.default_port == @current_port
51
53
 
52
- if new_uri.relative?
53
- if path.start_with?('?')
54
- path = request_path + path
55
- elsif not path.start_with?('/')
56
- path = request_path.sub(%r(/[^/]*$), '/') + path
57
- end
58
- path = current_host + path
59
- end
54
+ @current_scheme = new_uri.scheme
55
+ @current_host = new_uri.host
56
+ @current_port = new_uri.port
60
57
 
61
58
  reset_cache!
62
- send(method, path, attributes, env)
59
+ send(method, new_uri.to_s, attributes, env.merge(options[:headers] || {}))
63
60
  end
64
61
 
65
62
  def current_url
@@ -69,14 +66,17 @@ class Capybara::RackTest::Browser
69
66
  end
70
67
 
71
68
  def reset_host!
72
- @current_host = (Capybara.app_host || Capybara.default_host)
69
+ uri = URI.parse(Capybara.app_host || Capybara.default_host)
70
+ @current_scheme = uri.scheme
71
+ @current_host = uri.host
72
+ @current_port = uri.port
73
73
  end
74
74
 
75
75
  def reset_cache!
76
76
  @dom = nil
77
77
  end
78
78
 
79
- def body
79
+ def html
80
80
  dom.to_xml
81
81
  end
82
82
 
@@ -91,7 +91,7 @@ class Capybara::RackTest::Browser
91
91
  def source
92
92
  last_response.body
93
93
  rescue Rack::Test::Error
94
- nil
94
+ ""
95
95
  end
96
96
 
97
97
  protected
@@ -106,16 +106,4 @@ protected
106
106
  rescue Rack::Test::Error
107
107
  ""
108
108
  end
109
-
110
- def env
111
- env = {}
112
- begin
113
- env["HTTP_REFERER"] = last_request.url
114
- rescue Rack::Test::Error
115
- # no request yet
116
- end
117
- env.merge!(options[:headers]) if options[:headers]
118
- env
119
- end
120
-
121
109
  end
@@ -6,7 +6,9 @@ require 'cgi'
6
6
 
7
7
  class Capybara::RackTest::Driver < Capybara::Driver::Base
8
8
  DEFAULT_OPTIONS = {
9
- :respect_data_method => true
9
+ :respect_data_method => false,
10
+ :follow_redirects => true,
11
+ :redirect_limit => 5
10
12
  }
11
13
  attr_reader :app, :options
12
14
 
@@ -20,6 +22,14 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
20
22
  @browser ||= Capybara::RackTest::Browser.new(self)
21
23
  end
22
24
 
25
+ def follow_redirects?
26
+ @options[:follow_redirects]
27
+ end
28
+
29
+ def redirect_limit
30
+ @options[:redirect_limit]
31
+ end
32
+
23
33
  def response
24
34
  browser.last_response
25
35
  end
@@ -56,8 +66,8 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
56
66
  browser.find(selector)
57
67
  end
58
68
 
59
- def body
60
- browser.body
69
+ def html
70
+ browser.html
61
71
  end
62
72
 
63
73
  def source
@@ -1,6 +1,6 @@
1
1
  class Capybara::RackTest::Node < Capybara::Driver::Node
2
2
  def text
3
- native.text
3
+ Capybara::Helpers.normalize_whitespace(unnormalized_text)
4
4
  end
5
5
 
6
6
  def [](name)
@@ -12,6 +12,9 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
12
12
  end
13
13
 
14
14
  def set(value)
15
+ if (Array === value) && !self[:multiple]
16
+ raise ArgumentError.new "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
17
+ end
15
18
  if tag_name == 'input' and type == 'radio'
16
19
  other_radios_xpath = XPath.generate { |x| x.anywhere(:input)[x.attr(:name).equals(self[:name])] }.to_s
17
20
  driver.dom.xpath(other_radios_xpath).each { |node| node.remove_attribute("checked") }
@@ -29,7 +32,17 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
29
32
  # Firefox, allowing no input
30
33
  value = value[0...self[:maxlength].to_i]
31
34
  end
32
- native['value'] = value.to_s
35
+ if Array === value #Assert multiple attribute is present
36
+ value.each do |v|
37
+ new_native = native.clone
38
+ new_native.remove_attribute('value')
39
+ native.add_next_sibling(new_native)
40
+ new_native['value'] = v.to_s
41
+ end
42
+ native.remove
43
+ else
44
+ native['value'] = value.to_s
45
+ end
33
46
  elsif tag_name == "textarea"
34
47
  native.content = value.to_s
35
48
  end
@@ -84,6 +97,22 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
84
97
  native.xpath(locator).map { |n| self.class.new(driver, n) }
85
98
  end
86
99
 
100
+ protected
101
+
102
+ def unnormalized_text
103
+ if !visible?
104
+ ''
105
+ elsif native.text?
106
+ native.text
107
+ elsif native.element?
108
+ native.children.map do |child|
109
+ Capybara::RackTest::Node.new(driver, child).unnormalized_text
110
+ end.join
111
+ else
112
+ ''
113
+ end
114
+ end
115
+
87
116
  private
88
117
 
89
118
  def string_node
@@ -0,0 +1,72 @@
1
+ require 'forwardable'
2
+
3
+ module Capybara
4
+ class Result
5
+ include Enumerable
6
+ extend Forwardable
7
+
8
+ def initialize(elements, query)
9
+ @elements = elements
10
+ @result = elements.select { |node| query.matches_filters?(node) }
11
+ @rest = @elements - @result
12
+ @query = query
13
+ end
14
+
15
+ def_delegators :@result, :each, :[], :at, :size, :count, :length, :first, :last, :empty?
16
+
17
+ def matches_count?
18
+ @query.matches_count?(@result.size)
19
+ end
20
+
21
+ def find!
22
+ raise find_error if @result.size != 1
23
+ @result.first
24
+ end
25
+
26
+ def find_error
27
+ if @result.size == 0
28
+ Capybara::ElementNotFound.new("Unable to find #{@query.description}")
29
+ elsif @result.size > 1
30
+ Capybara::Ambiguous.new("Ambiguous match, found #{size} elements matching #{@query.description}")
31
+ end
32
+ end
33
+
34
+ def failure_message
35
+ message = if @query.options[:count]
36
+ "expected #{@query.description} to be found #{@query.options[:count]} #{declension("time", "times", @query.options[:count])}"
37
+ elsif @query.options[:between]
38
+ "expected #{@query.description} to be found between #{@query.options[:between].first} and #{@query.options[:between].last} times"
39
+ elsif @query.options[:maximum]
40
+ "expected #{@query.description} to be found at most #{@query.options[:maximum]} #{declension("time", "times", @query.options[:maximum])}"
41
+ elsif @query.options[:minimum]
42
+ "expected #{@query.description} to be found at least #{@query.options[:minimum]} #{declension("time", "times", @query.options[:minimum])}"
43
+ else
44
+ "expected to find #{@query.description}"
45
+ end
46
+ if count > 0
47
+ message << ", found #{count} #{declension("match", "matches")}: " << @result.map(&:text).map(&:inspect).join(", ")
48
+ else
49
+ message << " but there were no matches"
50
+ end
51
+ unless @rest.empty?
52
+ elements = @rest.map(&:text).map(&:inspect).join(", ")
53
+ message << ". Also found " << elements << ", which matched the selector but not all filters."
54
+ end
55
+ message
56
+ end
57
+
58
+ def negative_failure_message
59
+ "expected not to find #{@query.description}, but there #{declension("was", "were")} #{count} #{declension("match", "matches")}"
60
+ end
61
+
62
+ private
63
+
64
+ def declension(singular, plural, count=count)
65
+ if count == 1
66
+ singular
67
+ else
68
+ plural
69
+ end
70
+ end
71
+ end
72
+ end
@@ -4,6 +4,9 @@ module Capybara
4
4
  base.instance_eval do
5
5
  alias :background :before
6
6
  alias :scenario :it
7
+ alias :xscenario :xit
8
+ alias :given :let
9
+ alias :given! :let!
7
10
  end
8
11
  end
9
12
  end
@@ -12,7 +15,7 @@ end
12
15
  def self.feature(*args, &block)
13
16
  options = if args.last.is_a?(Hash) then args.pop else {} end
14
17
  options[:capybara_feature] = true
15
- options[:type] = :request
18
+ options[:type] = :feature
16
19
  options[:caller] ||= caller
17
20
  args.push(options)
18
21
 
@@ -6,35 +6,15 @@ module Capybara
6
6
  end
7
7
 
8
8
  def matches?(actual)
9
- @actual = wrap(actual)
10
- @actual.has_selector?(*@args)
9
+ wrap(actual).assert_selector(*@args)
11
10
  end
12
11
 
13
12
  def does_not_match?(actual)
14
- @actual = wrap(actual)
15
- @actual.has_no_selector?(*@args)
16
- end
17
-
18
- def failure_message_for_should
19
- if normalized.failure_message
20
- normalized.failure_message.call(@actual, normalized)
21
- else
22
- "expected #{selector_name} to return something"
23
- end
24
- end
25
-
26
- def failure_message_for_should_not
27
- "expected #{selector_name} not to return anything"
13
+ wrap(actual).assert_no_selector(*@args)
28
14
  end
29
15
 
30
16
  def description
31
- "has #{selector_name}"
32
- end
33
-
34
- def selector_name
35
- name = "#{normalized.name} #{normalized.locator.inspect}"
36
- name << " with text #{normalized.options[:text].inspect}" if normalized.options[:text]
37
- name
17
+ "have #{query.description}"
38
18
  end
39
19
 
40
20
  def wrap(actual)
@@ -45,55 +25,38 @@ module Capybara
45
25
  end
46
26
  end
47
27
 
48
- def normalized
49
- @normalized ||= Capybara::Selector.normalize(*@args)
28
+ def query
29
+ @query ||= Capybara::Query.new(*@args)
50
30
  end
51
31
  end
52
32
 
53
- class HaveMatcher
54
- attr_reader :name, :locator, :options, :failure_message, :actual
33
+ class HaveText
34
+ attr_reader :text
55
35
 
56
- def initialize(name, locator, options={}, &block)
57
- @name = name
58
- @locator = locator
59
- @options = options
60
- @failure_message = block
61
- end
62
-
63
- def arguments
64
- if options.empty? then [locator] else [locator, options] end
36
+ def initialize(text)
37
+ @text = text
65
38
  end
66
39
 
67
40
  def matches?(actual)
68
41
  @actual = wrap(actual)
69
- @actual.send(:"has_#{name}?", *arguments)
42
+ @actual.has_text?(text)
70
43
  end
71
44
 
72
45
  def does_not_match?(actual)
73
46
  @actual = wrap(actual)
74
- @actual.send(:"has_no_#{name}?", *arguments)
47
+ @actual.has_no_text?(text)
75
48
  end
76
49
 
77
50
  def failure_message_for_should
78
- if failure_message
79
- failure_message.call(actual, self)
80
- else
81
- "expected #{selector_name} to return something"
82
- end
51
+ "expected there to be text #{format(text)} in #{format(@actual.text)}"
83
52
  end
84
53
 
85
54
  def failure_message_for_should_not
86
- "expected #{selector_name} not to return anything"
55
+ "expected there not to be text #{format(text)} in #{format(@actual.text)}"
87
56
  end
88
57
 
89
58
  def description
90
- "has #{selector_name}"
91
- end
92
-
93
- def selector_name
94
- selector_name = "#{name} #{locator.inspect}"
95
- selector_name << " with text #{options[:text].inspect}" if options[:text]
96
- selector_name
59
+ "have text #{format(text)}"
97
60
  end
98
61
 
99
62
  def wrap(actual)
@@ -103,6 +66,11 @@ module Capybara
103
66
  Capybara.string(actual.to_s)
104
67
  end
105
68
  end
69
+
70
+ def format(text)
71
+ text = Capybara::Helpers.normalize_whitespace(text) unless text.is_a? Regexp
72
+ text.inspect
73
+ end
106
74
  end
107
75
 
108
76
  def have_selector(*args)
@@ -110,45 +78,47 @@ module Capybara
110
78
  end
111
79
 
112
80
  def have_xpath(xpath, options={})
113
- HaveMatcher.new(:xpath, xpath, options)
81
+ HaveSelector.new(:xpath, xpath, options)
114
82
  end
115
83
 
116
84
  def have_css(css, options={})
117
- HaveMatcher.new(:css, css, options)
85
+ HaveSelector.new(:css, css, options)
118
86
  end
119
87
 
120
88
  def have_content(text)
121
- HaveMatcher.new(:content, text.to_s) do |page, matcher|
122
- %(expected there to be content #{matcher.locator.inspect} in #{page.text.inspect})
123
- end
89
+ HaveText.new(text)
90
+ end
91
+
92
+ def have_text(text)
93
+ HaveText.new(text)
124
94
  end
125
95
 
126
96
  def have_link(locator, options={})
127
- HaveMatcher.new(:link, locator, options)
97
+ HaveSelector.new(:link, locator, options)
128
98
  end
129
99
 
130
100
  def have_button(locator)
131
- HaveMatcher.new(:button, locator)
101
+ HaveSelector.new(:button, locator)
132
102
  end
133
103
 
134
104
  def have_field(locator, options={})
135
- HaveMatcher.new(:field, locator, options)
105
+ HaveSelector.new(:field, locator, options)
136
106
  end
137
107
 
138
108
  def have_checked_field(locator)
139
- HaveMatcher.new(:checked_field, locator)
109
+ HaveSelector.new(:field, locator, :checked => true)
140
110
  end
141
111
 
142
112
  def have_unchecked_field(locator)
143
- HaveMatcher.new(:unchecked_field, locator)
113
+ HaveSelector.new(:field, locator, :unchecked => true)
144
114
  end
145
115
 
146
116
  def have_select(locator, options={})
147
- HaveMatcher.new(:select, locator, options)
117
+ HaveSelector.new(:select, locator, options)
148
118
  end
149
119
 
150
120
  def have_table(locator, options={})
151
- HaveMatcher.new(:table, locator, options)
121
+ HaveSelector.new(:table, locator, options)
152
122
  end
153
123
  end
154
124
  end
@@ -5,10 +5,8 @@ require 'capybara/rspec/matchers'
5
5
  require 'capybara/rspec/features'
6
6
 
7
7
  RSpec.configure do |config|
8
- config.include Capybara::DSL, :type => :request
9
- config.include Capybara::DSL, :type => :acceptance
10
- config.include Capybara::RSpecMatchers, :type => :request
11
- config.include Capybara::RSpecMatchers, :type => :acceptance
8
+ config.include Capybara::DSL, :type => :feature
9
+ config.include Capybara::RSpecMatchers, :type => :feature
12
10
  # The before and after blocks must run instantaneously, because Capybara
13
11
  # might not actually be used in all examples where it's included.
14
12
  config.after do
@@ -24,3 +22,8 @@ RSpec.configure do |config|
24
22
  end
25
23
  end
26
24
  end
25
+
26
+ # Override default rack_test driver to respect data-method attributes.
27
+ Capybara.register_driver :rack_test do |app|
28
+ Capybara::RackTest::Driver.new(app, :respect_data_method => true)
29
+ end
@@ -1,13 +1,7 @@
1
1
  module Capybara
2
2
  class Selector
3
- attr_reader :name
3
+ attr_reader :name, :custom_filters
4
4
 
5
- class Normalized
6
- attr_accessor :selector, :locator, :options, :xpaths
7
-
8
- def failure_message; selector.failure_message; end
9
- def name; selector.name; end
10
- end
11
5
 
12
6
  class << self
13
7
  def all
@@ -21,32 +15,13 @@ module Capybara
21
15
  def remove(name)
22
16
  all.delete(name.to_sym)
23
17
  end
24
-
25
- def normalize(*args)
26
- normalized = Normalized.new
27
- normalized.options = if args.last.is_a?(Hash) then args.pop else {} end
28
-
29
- if args[1]
30
- normalized.selector = all[args[0]]
31
- normalized.locator = args[1]
32
- else
33
- normalized.selector = all.values.find { |s| s.match?(args[0]) }
34
- normalized.locator = args[0]
35
- end
36
- normalized.selector ||= all[Capybara.default_selector]
37
-
38
- xpath = normalized.selector.call(normalized.locator)
39
- if xpath.respond_to?(:to_xpaths)
40
- normalized.xpaths = xpath.to_xpaths
41
- else
42
- normalized.xpaths = [xpath.to_s].flatten
43
- end
44
- normalized
45
- end
46
18
  end
47
19
 
48
20
  def initialize(name, &block)
49
21
  @name = name
22
+ @custom_filters = {}
23
+ @match = nil
24
+ @failure_message = nil
50
25
  instance_eval(&block)
51
26
  end
52
27
 
@@ -55,14 +30,22 @@ module Capybara
55
30
  @xpath
56
31
  end
57
32
 
33
+ # Same as xpath, but wrap in XPath.css().
34
+ def css(&block)
35
+ if block
36
+ @xpath = xpath { |*args| XPath.css(block.call(*args)) }
37
+ end
38
+ @xpath
39
+ end
40
+
58
41
  def match(&block)
59
42
  @match = block if block
60
43
  @match
61
44
  end
62
45
 
63
- def failure_message(&block)
64
- @failure_message = block if block
65
- @failure_message
46
+ def label(label=nil)
47
+ @label = label if label
48
+ @label
66
49
  end
67
50
 
68
51
  def call(locator)
@@ -72,6 +55,10 @@ module Capybara
72
55
  def match?(locator)
73
56
  @match and @match.call(locator)
74
57
  end
58
+
59
+ def filter(name, &block)
60
+ @custom_filters[name] = block
61
+ end
75
62
  end
76
63
  end
77
64
 
@@ -80,10 +67,86 @@ Capybara.add_selector(:xpath) do
80
67
  end
81
68
 
82
69
  Capybara.add_selector(:css) do
83
- xpath { |css| XPath.css(css) }
70
+ css { |css| css }
84
71
  end
85
72
 
86
73
  Capybara.add_selector(:id) do
87
74
  xpath { |id| XPath.descendant[XPath.attr(:id) == id.to_s] }
88
- match { |value| value.is_a?(Symbol) }
75
+ end
76
+
77
+ Capybara.add_selector(:field) do
78
+ xpath { |locator| XPath::HTML.field(locator) }
79
+ filter(:checked) { |node, value| not(value ^ node.checked?) }
80
+ filter(:unchecked) { |node, value| (value ^ node.checked?) }
81
+ filter(:with) { |node, with| node.value == with }
82
+ filter(:type) { |node, type| node[:type] == type }
83
+ end
84
+
85
+ Capybara.add_selector(:fieldset) do
86
+ xpath { |locator| XPath::HTML.fieldset(locator) }
87
+ end
88
+
89
+ Capybara.add_selector(:link_or_button) do
90
+ label "link or button"
91
+ xpath { |locator| XPath::HTML.link_or_button(locator) }
92
+ end
93
+
94
+ Capybara.add_selector(:link) do
95
+ xpath { |locator| XPath::HTML.link(locator) }
96
+ filter(:href) do |node, href|
97
+ node.first(:xpath, XPath.axis(:self)[XPath.attr(:href).equals(href.to_s)])
98
+ end
99
+ end
100
+
101
+ Capybara.add_selector(:button) do
102
+ xpath { |locator| XPath::HTML.button(locator) }
103
+ end
104
+
105
+ Capybara.add_selector(:fillable_field) do
106
+ label "field"
107
+ xpath { |locator| XPath::HTML.fillable_field(locator) }
108
+ end
109
+
110
+ Capybara.add_selector(:radio_button) do
111
+ label "radio button"
112
+ xpath { |locator| XPath::HTML.radio_button(locator) }
113
+ filter(:checked) { |node, value| not(value ^ node.checked?) }
114
+ filter(:unchecked) { |node, value| (value ^ node.checked?) }
115
+ end
116
+
117
+ Capybara.add_selector(:checkbox) do
118
+ xpath { |locator| XPath::HTML.checkbox(locator) }
119
+ filter(:checked) { |node, value| not(value ^ node.checked?) }
120
+ filter(:unchecked) { |node, value| (value ^ node.checked?) }
121
+ end
122
+
123
+ Capybara.add_selector(:select) do
124
+ label "select box"
125
+ xpath { |locator| XPath::HTML.select(locator) }
126
+ filter(:options) do |node, options|
127
+ actual = node.all(:xpath, './/option').map { |option| option.text }
128
+ options.sort == actual.sort
129
+ end
130
+ filter(:with_options) { |node, options| options.all? { |option| node.first(:option, option) } }
131
+ filter(:selected) do |node, selected|
132
+ actual = node.all(:xpath, './/option').select { |option| option.selected? }.map { |option| option.text }
133
+ [selected].flatten.sort == actual.sort
134
+ end
135
+ end
136
+
137
+ Capybara.add_selector(:option) do
138
+ xpath { |locator| XPath::HTML.option(locator) }
139
+ end
140
+
141
+ Capybara.add_selector(:file_field) do
142
+ label "file field"
143
+ xpath { |locator| XPath::HTML.file_field(locator) }
144
+ end
145
+
146
+ Capybara.add_selector(:content) do
147
+ xpath { |content| XPath::HTML.content(content) }
148
+ end
149
+
150
+ Capybara.add_selector(:table) do
151
+ xpath { |locator| XPath::HTML.table(locator) }
89
152
  end