capybara 0.3.0 → 0.3.5

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 (60) hide show
  1. data/Manifest.txt +4 -0
  2. data/README.rdoc +45 -5
  3. data/lib/capybara.rb +11 -4
  4. data/lib/capybara/driver/base.rb +3 -0
  5. data/lib/capybara/driver/celerity_driver.rb +44 -9
  6. data/lib/capybara/driver/rack_test_driver.rb +80 -16
  7. data/lib/capybara/driver/selenium_driver.rb +41 -9
  8. data/lib/capybara/dsl.rb +4 -10
  9. data/lib/capybara/node.rb +8 -0
  10. data/lib/capybara/rails.rb +8 -2
  11. data/lib/capybara/save_and_open_page.rb +5 -1
  12. data/lib/capybara/searchable.rb +17 -9
  13. data/lib/capybara/server.rb +35 -23
  14. data/lib/capybara/session.rb +91 -22
  15. data/lib/capybara/xpath.rb +66 -27
  16. data/spec/driver/celerity_driver_spec.rb +2 -2
  17. data/spec/driver/culerity_driver_spec.rb +1 -2
  18. data/spec/driver/rack_test_driver_spec.rb +0 -1
  19. data/spec/driver/remote_culerity_driver_spec.rb +9 -5
  20. data/spec/driver/selenium_driver_spec.rb +0 -1
  21. data/spec/drivers_spec.rb +24 -32
  22. data/spec/dsl/all_spec.rb +56 -25
  23. data/spec/dsl/attach_file_spec.rb +49 -51
  24. data/spec/dsl/check_spec.rb +12 -1
  25. data/spec/dsl/choose_spec.rb +19 -21
  26. data/spec/dsl/click_button_spec.rb +140 -87
  27. data/spec/dsl/click_link_spec.rb +88 -68
  28. data/spec/dsl/click_spec.rb +20 -22
  29. data/spec/dsl/current_url_spec.rb +6 -8
  30. data/spec/dsl/fill_in_spec.rb +75 -67
  31. data/spec/dsl/find_button_spec.rb +12 -14
  32. data/spec/dsl/find_by_id_spec.rb +16 -0
  33. data/spec/dsl/find_field_spec.rb +17 -19
  34. data/spec/dsl/find_link_spec.rb +13 -15
  35. data/spec/dsl/find_spec.rb +44 -23
  36. data/spec/dsl/has_button_spec.rb +32 -0
  37. data/spec/dsl/has_content_spec.rb +79 -81
  38. data/spec/dsl/has_css_spec.rb +81 -83
  39. data/spec/dsl/has_field_spec.rb +96 -0
  40. data/spec/dsl/has_link_spec.rb +33 -0
  41. data/spec/dsl/has_xpath_spec.rb +97 -89
  42. data/spec/dsl/locate_spec.rb +47 -26
  43. data/spec/dsl/select_spec.rb +61 -17
  44. data/spec/dsl/uncheck_spec.rb +17 -25
  45. data/spec/dsl/within_spec.rb +112 -104
  46. data/spec/public/test.js +3 -0
  47. data/spec/searchable_spec.rb +1 -1
  48. data/spec/server_spec.rb +7 -7
  49. data/spec/session/celerity_session_spec.rb +2 -2
  50. data/spec/session/culerity_session_spec.rb +1 -1
  51. data/spec/session_spec.rb +7 -0
  52. data/spec/session_with_javascript_support_spec.rb +139 -120
  53. data/spec/spec_helper.rb +7 -2
  54. data/spec/test_app.rb +8 -4
  55. data/spec/views/form.erb +50 -2
  56. data/spec/views/tables.erb +61 -1
  57. data/spec/views/with_html.erb +8 -2
  58. data/spec/views/with_js.erb +4 -0
  59. data/spec/xpath_spec.rb +17 -0
  60. metadata +6 -2
@@ -1,3 +1,5 @@
1
+ require 'capybara'
2
+
1
3
  module Capybara
2
4
  class << self
3
5
  attr_writer :default_driver, :current_driver, :javascript_driver
@@ -30,6 +32,7 @@ module Capybara
30
32
  end
31
33
 
32
34
  def reset_sessions!
35
+ session_pool.each { |mode, session| session.cleanup! }
33
36
  @session_pool = nil
34
37
  end
35
38
 
@@ -46,16 +49,7 @@ module Capybara
46
49
  Capybara.current_session
47
50
  end
48
51
 
49
- SESSION_METHODS = [
50
- :visit, :current_url, :body, :click_link, :click_button, :drag, :fill_in,
51
- :choose, :has_xpath?, :has_no_xpath?, :has_css?, :has_no_css?,
52
- :check, :uncheck, :attach_file, :select, :source,
53
- :has_content?, :has_no_content?, :within, :within_fieldset, :within_table,
54
- :save_and_open_page, :find, :find_field, :find_link, :find_button,
55
- :field_labeled, :all, :locate, :evaluate_script,
56
- :click, :wait_until
57
- ]
58
- SESSION_METHODS.each do |method|
52
+ Session::DSL_METHODS.each do |method|
59
53
  class_eval <<-RUBY, __FILE__, __LINE__+1
60
54
  def #{method}(*args, &block)
61
55
  page.#{method}(*args, &block)
@@ -29,6 +29,10 @@ module Capybara
29
29
  raise NotImplementedError
30
30
  end
31
31
 
32
+ def unselect(option)
33
+ raise NotImplementedError
34
+ end
35
+
32
36
  def click
33
37
  raise NotImplementedError
34
38
  end
@@ -48,6 +52,10 @@ module Capybara
48
52
  def path
49
53
  raise NotSupportedByDriverError
50
54
  end
55
+
56
+ def trigger(event)
57
+ raise NotSupportedByDriverError
58
+ end
51
59
 
52
60
  private
53
61
 
@@ -3,9 +3,15 @@ require 'capybara/dsl'
3
3
 
4
4
  Capybara.app = Rack::Builder.new do
5
5
  map "/" do
6
- use Rails::Rack::Static
7
- run ActionController::Dispatcher.new
6
+ if Rails.version.to_f >= 3.0
7
+ ActionDispatch::Static
8
+ run Rails.application
9
+ else # Rails 2
10
+ use Rails::Rack::Static
11
+ run ActionController::Dispatcher.new
12
+ end
8
13
  end
9
14
  end.to_app
10
15
 
11
16
  Capybara.asset_root = Rails.root.join('public')
17
+
@@ -23,7 +23,11 @@ module Capybara
23
23
 
24
24
  def rewrite_css_and_image_references(response_html) # :nodoc:
25
25
  return response_html unless Capybara.asset_root
26
- response_html.gsub(/("|')\/(stylesheets|images|javascripts)/, '\1' + Capybara.asset_root + '/\2')
26
+ directories = Dir.new(Capybara.asset_root).entries.inject([]) do |list, name|
27
+ list << name if File.directory?(name) and not name.to_s =~ /^\./
28
+ list
29
+ end
30
+ response_html.gsub(/("|')\/(#{directories.join('|')})/, '\1' + Capybara.asset_root.to_s + '/\2')
27
31
  end
28
32
  end
29
33
  end
@@ -1,35 +1,43 @@
1
1
  module Capybara
2
2
  module Searchable
3
- def find(locator, options = {})
4
- all(locator, options).first
3
+ def find(*args)
4
+ all(*args).first
5
5
  end
6
6
 
7
7
  def find_field(locator)
8
- find(XPath.field(locator))
8
+ find(:xpath, XPath.field(locator))
9
9
  end
10
10
  alias_method :field_labeled, :find_field
11
11
 
12
12
  def find_link(locator)
13
- find(XPath.link(locator))
13
+ find(:xpath, XPath.link(locator))
14
14
  end
15
15
 
16
16
  def find_button(locator)
17
- find(XPath.button(locator))
17
+ find(:xpath, XPath.button(locator))
18
18
  end
19
19
 
20
20
  def find_by_id(id)
21
- find(Xpath.for_css("##{id}"))
21
+ find(:css, "##{id}")
22
22
  end
23
23
 
24
- def all(locator, options = {})
24
+ def all(*args)
25
+ options = if args.last.is_a?(Hash) then args.pop else {} end
26
+ if args[1].nil?
27
+ kind, locator = Capybara.default_selector, args.first
28
+ else
29
+ kind, locator = args
30
+ end
31
+ locator = XPath.from_css(locator) if kind == :css
32
+
25
33
  results = all_unfiltered(locator)
26
34
 
27
35
  if options[:text]
28
36
  results = results.select { |n| n.text.match(options[:text]) }
29
37
  end
30
38
 
31
- if options[:visible] == true
32
- results.reject! { |n| !n.visible? }
39
+ if options[:visible] or Capybara.ignore_hidden_elements
40
+ results = results.select { |n| n.visible? }
33
41
  end
34
42
 
35
43
  results
@@ -1,11 +1,6 @@
1
1
  require 'uri'
2
2
  require 'net/http'
3
3
  require 'rack'
4
- begin
5
- require 'rack/handler/mongrel'
6
- rescue LoadError
7
- require 'rack/handler/webrick'
8
- end
9
4
 
10
5
  class Capybara::Server
11
6
  class Identify
@@ -26,49 +21,66 @@ class Capybara::Server
26
21
 
27
22
  def initialize(app)
28
23
  @app = app
29
- find_available_port
30
- boot
31
24
  end
32
25
 
33
26
  def host
34
- 'localhost'
27
+ "localhost"
35
28
  end
36
29
 
37
30
  def url(path)
38
- path = URI.parse(path).request_uri if path =~ /^http/
39
- "http://#{host}:#{port}#{path}"
31
+ if path =~ /^http/
32
+ path
33
+ else
34
+ (Capybara.app_host || "http://#{host}:#{port}") + path.to_s
35
+ end
40
36
  end
41
37
 
42
38
  def responsive?
43
39
  is_running_on_port?(port)
44
40
  end
45
41
 
46
- private
42
+ def handler
43
+ begin
44
+ require 'rack/handler/thin'
45
+ Rack::Handler::Thin
46
+ rescue LoadError
47
+ begin
48
+ require 'rack/handler/mongrel'
49
+ Rack::Handler::Mongrel
50
+ rescue LoadError
51
+ require 'rack/handler/webrick'
52
+ Rack::Handler::WEBrick
53
+ end
54
+ end
55
+ end
47
56
 
48
57
  def boot
49
- Capybara.log "application has already booted" and return if responsive?
58
+ find_available_port
59
+ Capybara.log "application has already booted" and return self if responsive?
50
60
  Capybara.log "booting Rack applicartion on port #{port}"
51
61
 
52
- Timeout.timeout(10) do
53
- Thread.new do
54
- if defined?(Rack::Handler::Mongrel)
55
- Rack::Handler::Mongrel.run(Identify.new(@app), :Port => port)
56
- else
57
- Rack::Handler::WEBrick.run(Identify.new(@app), :Port => port, :AccessLog => [])
58
- end
59
- end
60
- Capybara.log "checking if application has booted"
62
+ Thread.new do
63
+ handler.run(Identify.new(@app), :Port => port, :AccessLog => [])
64
+ end
65
+ Capybara.log "checking if application has booted"
61
66
 
62
- loop do
63
- Capybara.log("application has booted") and break if responsive?
67
+ Capybara::WaitUntil.timeout(10) do
68
+ if responsive?
69
+ Capybara.log("application has booted")
70
+ true
71
+ else
64
72
  sleep 0.5
73
+ false
65
74
  end
66
75
  end
76
+ self
67
77
  rescue Timeout::Error
68
78
  Capybara.log "Rack application timed out during boot"
69
79
  exit
70
80
  end
71
81
 
82
+ private
83
+
72
84
  def find_available_port
73
85
  @port = 9887
74
86
  @port += 1 while is_port_open?(@port) and not is_running_on_port?(@port)
@@ -4,6 +4,15 @@ module Capybara
4
4
  class Session
5
5
  include Searchable
6
6
 
7
+ DSL_METHODS = [
8
+ :all, :attach_file, :body, :check, :choose, :click, :click_button, :click_link, :current_url, :drag, :evaluate_script,
9
+ :field_labeled, :fill_in, :find, :find_button, :find_by_id, :find_field, :find_link, :has_content?, :has_css?,
10
+ :has_no_content?, :has_no_css?, :has_no_xpath?, :has_xpath?, :locate, :save_and_open_page, :select, :source, :uncheck,
11
+ :visit, :wait_until, :within, :within_fieldset, :within_table, :has_link?, :has_no_link?, :has_button?, :has_no_button?,
12
+ :has_field?, :has_no_field?, :has_checked_field?, :has_unchecked_field?, :has_no_table?, :has_table?, :unselect,
13
+ :has_select?, :has_no_select?
14
+ ]
15
+
7
16
  attr_reader :mode, :app
8
17
 
9
18
  def initialize(mode, app)
@@ -26,6 +35,10 @@ module Capybara
26
35
  end
27
36
  end
28
37
 
38
+ def cleanup!
39
+ driver.cleanup!
40
+ end
41
+
29
42
  def current_url
30
43
  driver.current_url
31
44
  end
@@ -40,53 +53,58 @@ module Capybara
40
53
 
41
54
  def click(locator)
42
55
  msg = "no link or button '#{locator}' found"
43
- locate(XPath.link(locator).button(locator), msg).click
56
+ locate(:xpath, XPath.link(locator).button(locator), msg).click
44
57
  end
45
58
 
46
59
  def click_link(locator)
47
60
  msg = "no link with title, id or text '#{locator}' found"
48
- locate(XPath.link(locator), msg).click
61
+ locate(:xpath, XPath.link(locator), msg).click
49
62
  end
50
63
 
51
64
  def click_button(locator)
52
65
  msg = "no button with value or id or text '#{locator}' found"
53
- locate(XPath.button(locator)).click
66
+ locate(:xpath, XPath.button(locator), msg).click
54
67
  end
55
68
 
56
69
  def drag(source_locator, target_locator)
57
- source = locate(source_locator, "drag source '#{source_locator}' not found on page")
58
- target = locate(target_locator, "drag target '#{target_locator}' not found on page")
70
+ source = locate(:xpath, source_locator, "drag source '#{source_locator}' not found on page")
71
+ target = locate(:xpath, target_locator, "drag target '#{target_locator}' not found on page")
59
72
  source.drag_to(target)
60
73
  end
61
74
 
62
75
  def fill_in(locator, options={})
63
76
  msg = "cannot fill in, no text field, text area or password field with id, name, or label '#{locator}' found"
64
- locate(XPath.fillable_field(locator), msg).set(options[:with])
77
+ locate(:xpath, XPath.fillable_field(locator), msg).set(options[:with])
65
78
  end
66
79
 
67
80
  def choose(locator)
68
81
  msg = "cannot choose field, no radio button with id, name, or label '#{locator}' found"
69
- locate(XPath.radio_button(locator), msg).set(true)
82
+ locate(:xpath, XPath.radio_button(locator), msg).set(true)
70
83
  end
71
84
 
72
85
  def check(locator)
73
86
  msg = "cannot check field, no checkbox with id, name, or label '#{locator}' found"
74
- locate(XPath.checkbox(locator), msg).set(true)
87
+ locate(:xpath, XPath.checkbox(locator), msg).set(true)
75
88
  end
76
89
 
77
90
  def uncheck(locator)
78
91
  msg = "cannot uncheck field, no checkbox with id, name, or label '#{locator}' found"
79
- locate(XPath.checkbox(locator), msg).set(false)
92
+ locate(:xpath, XPath.checkbox(locator), msg).set(false)
80
93
  end
81
94
 
82
95
  def select(value, options={})
83
96
  msg = "cannot select option, no select box with id, name, or label '#{options[:from]}' found"
84
- locate(XPath.select(options[:from]), msg).select(value)
97
+ locate(:xpath, XPath.select(options[:from]), msg).select(value)
98
+ end
99
+
100
+ def unselect(value, options={})
101
+ msg = "cannot unselect option, no select box with id, name, or label '#{options[:from]}' found"
102
+ locate(:xpath, XPath.select(options[:from]), msg).unselect(value)
85
103
  end
86
104
 
87
105
  def attach_file(locator, path)
88
106
  msg = "cannot attach file, no file field with id, name, or label '#{locator}' found"
89
- locate(XPath.file_field(locator), msg).set(path)
107
+ locate(:xpath, XPath.file_field(locator), msg).set(path)
90
108
  end
91
109
 
92
110
  def body
@@ -100,27 +118,30 @@ module Capybara
100
118
  def within(kind, scope=nil)
101
119
  kind, scope = Capybara.default_selector, kind unless scope
102
120
  scope = XPath.from_css(scope) if kind == :css
103
- locate(scope, "scope '#{scope}' not found on page")
104
- scopes.push(scope)
105
- yield
106
- scopes.pop
121
+ locate(:xpath, scope, "scope '#{scope}' not found on page")
122
+ begin
123
+ scopes.push(scope)
124
+ yield
125
+ ensure
126
+ scopes.pop
127
+ end
107
128
  end
108
129
 
109
130
  def within_fieldset(locator)
110
- within XPath.fieldset(locator) do
131
+ within :xpath, XPath.fieldset(locator) do
111
132
  yield
112
133
  end
113
134
  end
114
135
 
115
136
  def within_table(locator)
116
- within XPath.table(locator) do
137
+ within :xpath, XPath.table(locator) do
117
138
  yield
118
139
  end
119
140
  end
120
141
 
121
142
  def has_xpath?(path, options={})
122
143
  wait_conditionally_until do
123
- results = all(path, options)
144
+ results = all(:xpath, path, options)
124
145
 
125
146
  if options[:count]
126
147
  results.size == options[:count]
@@ -134,7 +155,7 @@ module Capybara
134
155
 
135
156
  def has_no_xpath?(path, options={})
136
157
  wait_conditionally_until do
137
- results = all(path, options)
158
+ results = all(:xpath, path, options)
138
159
 
139
160
  if options[:count]
140
161
  results.size != options[:count]
@@ -162,16 +183,64 @@ module Capybara
162
183
  has_no_xpath?(XPath.content(content))
163
184
  end
164
185
 
186
+ def has_link?(locator)
187
+ has_xpath?(XPath.link(locator))
188
+ end
189
+
190
+ def has_no_link?(locator)
191
+ has_no_xpath?(XPath.link(locator))
192
+ end
193
+
194
+ def has_button?(locator)
195
+ has_xpath?(XPath.button(locator))
196
+ end
197
+
198
+ def has_no_button?(locator)
199
+ has_no_xpath?(XPath.button(locator))
200
+ end
201
+
202
+ def has_field?(locator, options={})
203
+ has_xpath?(XPath.field(locator, options))
204
+ end
205
+
206
+ def has_no_field?(locator, options={})
207
+ has_no_xpath?(XPath.field(locator, options))
208
+ end
209
+
210
+ def has_checked_field?(locator)
211
+ has_xpath?(XPath.field(locator, :checked => true))
212
+ end
213
+
214
+ def has_unchecked_field?(locator)
215
+ has_xpath?(XPath.field(locator, :unchecked => true))
216
+ end
217
+
218
+ def has_select?(locator, options={})
219
+ has_xpath?(XPath.select(locator, options))
220
+ end
221
+
222
+ def has_no_select?(locator, options={})
223
+ has_no_xpath?(XPath.select(locator, options))
224
+ end
225
+
226
+ def has_table?(locator, options={})
227
+ has_xpath?(XPath.table(locator, options))
228
+ end
229
+
230
+ def has_no_table?(locator, options={})
231
+ has_no_xpath?(XPath.table(locator, options))
232
+ end
233
+
165
234
  def save_and_open_page
166
235
  require 'capybara/save_and_open_page'
167
236
  Capybara::SaveAndOpenPage.save_and_open_page(body)
168
237
  end
169
238
 
170
239
  #return node identified by locator or raise ElementNotFound(using desc)
171
- def locate(locator, fail_msg = nil)
172
- node = wait_conditionally_until { find(locator) }
240
+ def locate(kind_or_locator, locator=nil, fail_msg = nil)
241
+ node = wait_conditionally_until { find(kind_or_locator, locator) }
173
242
  ensure
174
- raise Capybara::ElementNotFound, fail_msg || "Unable to locate '#{locator}'" unless node
243
+ raise Capybara::ElementNotFound, fail_msg || "Unable to locate '#{kind_or_locator}'" unless node
175
244
  return node
176
245
  end
177
246
 
@@ -33,13 +33,21 @@ module Capybara
33
33
  @paths = paths
34
34
  end
35
35
 
36
- def field(locator)
37
- fillable_field(locator).input_field(:file, locator).checkbox(locator).radio_button(locator).select(locator)
36
+ def field(locator, options={})
37
+ if options[:with]
38
+ fillable_field(locator, options)
39
+ else
40
+ xpath = fillable_field(locator)
41
+ xpath = xpath.input_field(:file, locator, options)
42
+ xpath = xpath.checkbox(locator, options)
43
+ xpath = xpath.radio_button(locator, options)
44
+ xpath.select(locator, options)
45
+ end
38
46
  end
39
47
 
40
- def fillable_field(locator)
41
- [:text, :password, :email, :url, :search, :tel, :color].inject(text_area(locator)) do |all, type|
42
- all.input_field(type, locator)
48
+ def fillable_field(locator, options={})
49
+ [:text, :password, :email, :url, :search, :tel, :color].inject(text_area(locator, options)) do |all, type|
50
+ all.input_field(type, locator, options)
43
51
  end
44
52
  end
45
53
 
@@ -47,8 +55,16 @@ module Capybara
47
55
  append("/descendant-or-self::*[contains(.,#{s(locator)})]")
48
56
  end
49
57
 
50
- def table(locator)
51
- append("//table[@id=#{s(locator)} or contains(caption,#{s(locator)})]")
58
+ def table(locator, options={})
59
+ conditions = ""
60
+ if options[:rows]
61
+ row_conditions = options[:rows].map do |row|
62
+ row = row.map { |column| "*[self::td or self::th][text()=#{s(column)}]" }.join(sibling)
63
+ "tr[./#{row}]"
64
+ end.join(sibling)
65
+ conditions << "[.//#{row_conditions}]"
66
+ end
67
+ append("//table[@id=#{s(locator)} or contains(caption,#{s(locator)})]#{conditions}")
52
68
  end
53
69
 
54
70
  def fieldset(locator)
@@ -56,27 +72,30 @@ module Capybara
56
72
  end
57
73
 
58
74
  def link(locator)
59
- xpath = append("//a[@href][@id=#{s(locator)} or contains(.,#{s(locator)}) or contains(@title,#{s(locator)})]")
60
- xpath.prepend("//a[@href][text()=#{s(locator)} or @title=#{s(locator)}]")
75
+ xpath = append("//a[@href][@id=#{s(locator)} or contains(.,#{s(locator)}) or contains(@title,#{s(locator)}) or img[contains(@alt,#{s(locator)})]]")
76
+ xpath.prepend("//a[@href][text()=#{s(locator)} or @title=#{s(locator)} or img[@alt=#{s(locator)}]]")
61
77
  end
62
78
 
63
79
  def button(locator)
64
- xpath = append("//input[@type='submit' or @type='image'][@id=#{s(locator)} or contains(@value,#{s(locator)})]")
80
+ xpath = append("//input[@type='submit' or @type='image' or @type='button'][@id=#{s(locator)} or contains(@value,#{s(locator)})]")
65
81
  xpath = xpath.append("//button[@id=#{s(locator)} or contains(@value,#{s(locator)}) or contains(.,#{s(locator)})]")
66
- xpath = xpath.prepend("//input[@type='submit' or @type='image'][@value=#{s(locator)}]")
82
+ xpath = xpath.prepend("//input[@type='submit' or @type='image' or @type='button'][@value=#{s(locator)}]")
83
+ xpath = xpath.prepend("//input[@type='image'][@alt=#{s(locator)} or contains(@alt,#{s(locator)})]")
67
84
  xpath = xpath.prepend("//button[@value=#{s(locator)} or text()=#{s(locator)}]")
68
85
  end
69
86
 
70
- def text_area(locator)
71
- add_field(locator, "//textarea")
87
+ def text_area(locator, options={})
88
+ options = options.merge(:text => options[:with]) if options.has_key?(:with)
89
+ add_field(locator, "//textarea", options)
72
90
  end
73
91
 
74
- def select(locator)
75
- add_field(locator, "//select")
92
+ def select(locator, options={})
93
+ add_field(locator, "//select", options)
76
94
  end
77
95
 
78
- def input_field(type, locator)
79
- add_field(locator, "//input[@type='#{type}']")
96
+ def input_field(type, locator, options={})
97
+ options = options.merge(:value => options[:with]) if options.has_key?(:with)
98
+ add_field(locator, "//input[@type='#{type}']", options)
80
99
  end
81
100
 
82
101
  def scope(scope)
@@ -95,12 +114,12 @@ module Capybara
95
114
  XPath.new(*[XPath.wrap(path).paths, @paths].flatten)
96
115
  end
97
116
 
98
- def checkbox(locator)
99
- input_field(:checkbox, locator)
117
+ def checkbox(locator, options={})
118
+ input_field(:checkbox, locator, options)
100
119
  end
101
120
 
102
- def radio_button(locator)
103
- input_field(:radio, locator)
121
+ def radio_button(locator, options={})
122
+ input_field(:radio, locator, options)
104
123
  end
105
124
 
106
125
  [:text, :password, :email, :url, :search, :tel, :color, :file].each do |type|
@@ -113,12 +132,32 @@ module Capybara
113
132
 
114
133
  protected
115
134
 
116
- def add_field(locator, field)
117
- xpath = append("#{field}[@id=#{s(locator)}]")
118
- xpath = xpath.append("#{field}[@name=#{s(locator)}]")
119
- xpath = xpath.append("#{field}[@id=//label[contains(.,#{s(locator)})]/@for]")
120
- xpath = xpath.append("//label[contains(.,#{s(locator)})]#{field}")
121
- xpath.prepend("#{field}[@id=//label[text()=#{s(locator)}]/@for]")
135
+ # place this between to nodes to indicate that they should be siblings
136
+ def sibling
137
+ '/following-sibling::*[1]/self::'
138
+ end
139
+
140
+ def add_field(locator, field, options={})
141
+ postfix = extract_postfix(options)
142
+ xpath = append("#{field}[@id=#{s(locator)}]#{postfix}")
143
+ xpath = xpath.append("#{field}[@name=#{s(locator)}]#{postfix}")
144
+ xpath = xpath.append("#{field}[@id=//label[contains(.,#{s(locator)})]/@for]#{postfix}")
145
+ xpath = xpath.append("//label[contains(.,#{s(locator)})]#{field}#{postfix}")
146
+ xpath.prepend("#{field}[@id=//label[text()=#{s(locator)}]/@for]#{postfix}")
147
+ end
148
+
149
+ def extract_postfix(options)
150
+ options.inject("") do |postfix, (key, value)|
151
+ case key
152
+ when :value then postfix += "[@value=#{s(value)}]"
153
+ when :text then postfix += "[text()=#{s(value)}]"
154
+ when :checked then postfix += "[@checked]"
155
+ when :unchecked then postfix += "[not(@checked)]"
156
+ when :options then postfix += value.map { |o| "[./option/text()=#{s(o)}]" }.join
157
+ when :selected then postfix += [value].flatten.map { |o| "[./option[@selected]/text()=#{s(o)}]" }.join
158
+ end
159
+ postfix
160
+ end
122
161
  end
123
162
 
124
163
  # Sanitize a String for putting it into an xpath query