capybara 0.3.0 → 0.3.5

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