capybara 0.3.9 → 0.4.0.rc

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 (70) hide show
  1. data/History.txt +43 -1
  2. data/README.rdoc +168 -98
  3. data/lib/capybara.rb +77 -15
  4. data/lib/capybara/driver/base.rb +21 -16
  5. data/lib/capybara/driver/celerity_driver.rb +39 -41
  6. data/lib/capybara/driver/culerity_driver.rb +2 -1
  7. data/lib/capybara/driver/node.rb +66 -0
  8. data/lib/capybara/driver/rack_test_driver.rb +66 -67
  9. data/lib/capybara/driver/selenium_driver.rb +43 -47
  10. data/lib/capybara/dsl.rb +44 -6
  11. data/lib/capybara/node.rb +185 -24
  12. data/lib/capybara/node/actions.rb +170 -0
  13. data/lib/capybara/node/finders.rb +150 -0
  14. data/lib/capybara/node/matchers.rb +360 -0
  15. data/lib/capybara/rails.rb +1 -0
  16. data/lib/capybara/selector.rb +52 -0
  17. data/lib/capybara/server.rb +68 -87
  18. data/lib/capybara/session.rb +221 -207
  19. data/lib/capybara/spec/driver.rb +45 -35
  20. data/lib/capybara/spec/public/test.js +1 -1
  21. data/lib/capybara/spec/session.rb +28 -53
  22. data/lib/capybara/spec/session/all_spec.rb +7 -3
  23. data/lib/capybara/spec/session/check_spec.rb +50 -52
  24. data/lib/capybara/spec/session/click_button_spec.rb +9 -0
  25. data/lib/capybara/spec/session/click_link_or_button_spec.rb +37 -0
  26. data/lib/capybara/spec/session/current_url_spec.rb +7 -0
  27. data/lib/capybara/spec/session/find_button_spec.rb +4 -2
  28. data/lib/capybara/spec/session/find_by_id_spec.rb +4 -2
  29. data/lib/capybara/spec/session/find_field_spec.rb +7 -3
  30. data/lib/capybara/spec/session/find_link_spec.rb +5 -3
  31. data/lib/capybara/spec/session/find_spec.rb +71 -6
  32. data/lib/capybara/spec/session/has_field_spec.rb +1 -1
  33. data/lib/capybara/spec/session/has_selector_spec.rb +129 -0
  34. data/lib/capybara/spec/session/has_xpath_spec.rb +4 -4
  35. data/lib/capybara/spec/session/javascript.rb +25 -5
  36. data/lib/capybara/spec/session/select_spec.rb +16 -2
  37. data/lib/capybara/spec/session/unselect_spec.rb +8 -1
  38. data/lib/capybara/spec/session/within_spec.rb +5 -5
  39. data/lib/capybara/spec/views/form.erb +65 -1
  40. data/lib/capybara/spec/views/popup_one.erb +8 -0
  41. data/lib/capybara/spec/views/popup_two.erb +8 -0
  42. data/lib/capybara/spec/views/with_html.erb +5 -0
  43. data/lib/capybara/spec/views/within_popups.erb +25 -0
  44. data/lib/capybara/{save_and_open_page.rb → util/save_and_open_page.rb} +3 -3
  45. data/lib/capybara/util/timeout.rb +27 -0
  46. data/lib/capybara/version.rb +1 -1
  47. data/spec/capybara_spec.rb +18 -8
  48. data/spec/driver/celerity_driver_spec.rb +10 -14
  49. data/spec/driver/culerity_driver_spec.rb +4 -3
  50. data/spec/driver/rack_test_driver_spec.rb +39 -2
  51. data/spec/driver/remote_culerity_driver_spec.rb +5 -7
  52. data/spec/driver/remote_selenium_driver_spec.rb +7 -10
  53. data/spec/driver/selenium_driver_spec.rb +3 -2
  54. data/spec/dsl_spec.rb +5 -14
  55. data/spec/save_and_open_page_spec.rb +19 -19
  56. data/spec/server_spec.rb +22 -10
  57. data/spec/session/celerity_session_spec.rb +17 -21
  58. data/spec/session/culerity_session_spec.rb +3 -3
  59. data/spec/session/rack_test_session_spec.rb +2 -2
  60. data/spec/session/selenium_session_spec.rb +2 -2
  61. data/spec/spec_helper.rb +27 -6
  62. data/spec/{wait_until_spec.rb → timeout_spec.rb} +14 -14
  63. metadata +88 -46
  64. data/lib/capybara/searchable.rb +0 -54
  65. data/lib/capybara/spec/session/click_spec.rb +0 -24
  66. data/lib/capybara/spec/session/locate_spec.rb +0 -65
  67. data/lib/capybara/wait_until.rb +0 -28
  68. data/lib/capybara/xpath.rb +0 -179
  69. data/spec/searchable_spec.rb +0 -66
  70. data/spec/xpath_spec.rb +0 -180
@@ -3,10 +3,6 @@ class Capybara::Driver::Base
3
3
  raise NotImplementedError
4
4
  end
5
5
 
6
- def current_path
7
- URI.parse(current_url).path
8
- end
9
-
10
6
  def visit(path)
11
7
  raise NotImplementedError
12
8
  end
@@ -15,19 +11,20 @@ class Capybara::Driver::Base
15
11
  raise NotImplementedError
16
12
  end
17
13
 
18
- def execute_script(script)
19
- raise Capybara::NotSupportedByDriverError
14
+ def source
15
+ raise NotImplementedError
20
16
  end
21
17
 
22
- def evaluate_script(script)
23
- raise Capybara::NotSupportedByDriverError
18
+ def body
19
+ raise NotImplementedError
24
20
  end
25
21
 
26
- def wait?
27
- false
22
+ def execute_script(script)
23
+ raise Capybara::NotSupportedByDriverError
28
24
  end
29
25
 
30
- def wait_until *args
26
+ def evaluate_script(script)
27
+ raise Capybara::NotSupportedByDriverError
31
28
  end
32
29
 
33
30
  def response_headers
@@ -38,19 +35,27 @@ class Capybara::Driver::Base
38
35
  raise Capybara::NotSupportedByDriverError
39
36
  end
40
37
 
41
- def body
42
- raise NotImplementedError
38
+ def within_frame(frame_id)
39
+ raise Capybara::NotSupportedByDriverError
43
40
  end
44
41
 
45
- def within_frame(frame_id)
42
+ def within_window(handle)
46
43
  raise Capybara::NotSupportedByDriverError
47
44
  end
48
45
 
49
- def source
50
- raise NotImplementedError
46
+ def wait?
47
+ false
48
+ end
49
+
50
+ def wait_until(*args)
51
+ end
52
+
53
+ def reset!
51
54
  end
52
55
 
53
56
  def cleanup!
57
+ Capybara.deprecate("cleanup!", "reset!")
58
+ reset!
54
59
  end
55
60
 
56
61
  def has_shortcircuit_timeout?
@@ -1,96 +1,94 @@
1
1
  class Capybara::Driver::Celerity < Capybara::Driver::Base
2
- class Node < Capybara::Node
2
+ class Node < Capybara::Driver::Node
3
3
  def text
4
- node.text
4
+ native.text
5
5
  end
6
6
 
7
7
  def [](name)
8
8
  value = if name.to_sym == :class
9
- node.class_name
9
+ native.class_name
10
10
  else
11
- node.send(name.to_sym)
11
+ native.send(name.to_sym)
12
12
  end
13
13
  return value if value and not value.to_s.empty?
14
14
  end
15
15
 
16
16
  def value
17
- if tag_name == "select" and node.multiple?
18
- node.selected_options
17
+ if tag_name == "select" and native.multiple?
18
+ native.selected_options
19
19
  else
20
- super
20
+ self[:value]
21
21
  end
22
22
  end
23
23
 
24
24
  def set(value)
25
- node.set(value)
25
+ native.set(value)
26
26
  end
27
27
 
28
- def select(option)
29
- node.select(option)
30
- rescue
31
- options = all(:xpath, "//option").map { |o| "'#{o.text}'" }.join(', ')
32
- raise Capybara::OptionNotFound, "No such option '#{option}' in this select box. Available options: #{options}"
28
+ def select_option
29
+ native.click
33
30
  end
34
31
 
35
- def unselect(option)
36
- unless node.multiple?
37
- raise Capybara::UnselectNotAllowed, "Cannot unselect option '#{option}' from single select box."
32
+ def unselect_option
33
+ unless select_node.native.multiple?
34
+ raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
38
35
  end
39
36
 
40
37
  # FIXME: couldn't find a clean way to unselect, so clear and reselect
41
- selected_options = node.selected_options
42
- if unselect_option = selected_options.detect { |value| value == option } ||
43
- selected_options.detect { |value| value.index(option) }
44
- node.clear
45
- (selected_options - [unselect_option]).each { |value| node.select_value(value) }
46
- else
47
- options = all(:xpath, "//option").map { |o| "'#{o.text}'" }.join(', ')
48
- raise Capybara::OptionNotFound, "No such option '#{option}' in this select box. Available options: #{options}"
49
- end
38
+ selected_nodes = select_node.find('.//option[@selected]')
39
+ select_node.native.clear
40
+ selected_nodes.each { |n| n.click unless n.path == path }
50
41
  end
51
42
 
52
43
  def click
53
- node.click
44
+ native.click
54
45
  end
55
46
 
56
47
  def drag_to(element)
57
- node.fire_event('mousedown')
58
- element.node.fire_event('mousemove')
59
- element.node.fire_event('mouseup')
48
+ native.fire_event('mousedown')
49
+ element.native.fire_event('mousemove')
50
+ element.native.fire_event('mouseup')
60
51
  end
61
52
 
62
53
  def tag_name
63
54
  # FIXME: this might be the dumbest way ever of getting the tag name
64
55
  # there has to be something better...
65
- node.to_xml[/^\s*<([a-z0-9\-\:]+)/, 1]
56
+ native.to_xml[/^\s*<([a-z0-9\-\:]+)/, 1]
66
57
  end
67
58
 
68
59
  def visible?
69
- node.visible?
60
+ native.visible?
70
61
  end
71
62
 
72
63
  def path
73
- node.xpath
64
+ native.xpath
74
65
  end
75
66
 
76
67
  def trigger(event)
77
- node.fire_event(event.to_s)
68
+ native.fire_event(event.to_s)
78
69
  end
79
70
 
80
- private
81
-
82
- def all_unfiltered(locator)
83
- noko_node = Nokogiri::HTML(driver.body).xpath(node.xpath).first
71
+ def find(locator)
72
+ noko_node = Nokogiri::HTML(driver.body).xpath(native.xpath).first
84
73
  all_nodes = noko_node.xpath(locator).map { |n| n.path }.join(' | ')
85
- driver.find(all_nodes)
74
+ if all_nodes.empty? then [] else driver.find(all_nodes) end
86
75
  end
87
76
 
77
+ protected
78
+
79
+ # a reference to the select node if this is an option node
80
+ def select_node
81
+ find('./ancestor::select').first
82
+ end
83
+
84
+
88
85
  end
89
86
 
90
- attr_reader :app, :rack_server
87
+ attr_reader :app, :rack_server, :options
91
88
 
92
- def initialize(app)
89
+ def initialize(app, options={})
93
90
  @app = app
91
+ @options = options
94
92
  @rack_server = Capybara::Server.new(@app)
95
93
  @rack_server.boot if Capybara.run_server
96
94
  end
@@ -143,7 +141,7 @@ class Capybara::Driver::Celerity < Capybara::Driver::Base
143
141
  @_browser
144
142
  end
145
143
 
146
- def cleanup!
144
+ def reset!
147
145
  browser.clear_cookies
148
146
  end
149
147
 
@@ -14,8 +14,9 @@ class Capybara::Driver::Culerity < Capybara::Driver::Celerity
14
14
 
15
15
  def browser
16
16
  unless @_browser
17
- @_browser = ::Culerity::RemoteBrowserProxy.new self.class.server, {:browser => :firefox, :log_level => :off}
17
+ @_browser = ::Culerity::RemoteBrowserProxy.new self.class.server, options
18
18
  at_exit do
19
+ @_browser.close
19
20
  @_browser.exit
20
21
  end
21
22
  end
@@ -0,0 +1,66 @@
1
+ module Capybara
2
+ module Driver
3
+ class Node
4
+ attr_reader :driver, :native
5
+
6
+ def initialize(driver, native)
7
+ @driver = driver
8
+ @native = native
9
+ end
10
+
11
+ def text
12
+ raise NotImplementedError
13
+ end
14
+
15
+ def [](name)
16
+ raise NotImplementedError
17
+ end
18
+
19
+ def value
20
+ raise NotImplementedError
21
+ end
22
+
23
+ def set(value)
24
+ raise NotImplementedError
25
+ end
26
+
27
+ def select_option
28
+ raise NotImplementedError
29
+ end
30
+
31
+ def unselect_option
32
+ raise NotImplementedError
33
+ end
34
+
35
+ def click
36
+ raise NotImplementedError
37
+ end
38
+
39
+ def drag_to(element)
40
+ raise NotImplementedError
41
+ end
42
+
43
+ def tag_name
44
+ raise NotImplementedError
45
+ end
46
+
47
+ def visible?
48
+ raise NotImplementedError
49
+ end
50
+
51
+ def path
52
+ raise NotSupportedByDriverError
53
+ end
54
+
55
+ def trigger(event)
56
+ raise NotSupportedByDriverError
57
+ end
58
+
59
+ def inspect
60
+ %(#<Capybara::Driver::Node tag="#{tag_name}" path="#{path}">)
61
+ rescue NotSupportedByDriverError
62
+ %(#<Capybara::Driver::Node tag="#{tag_name}">)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,117 +1,111 @@
1
1
  require 'rack/test'
2
+ require 'rack/utils'
2
3
  require 'mime/types'
3
4
  require 'nokogiri'
4
5
  require 'cgi'
5
6
 
6
7
  class Capybara::Driver::RackTest < Capybara::Driver::Base
7
- class Node < Capybara::Node
8
+ class Node < Capybara::Driver::Node
8
9
  def text
9
- node.text
10
+ native.text
10
11
  end
11
12
 
12
13
  def [](name)
13
14
  attr_name = name.to_s
14
15
  case
15
16
  when 'select' == tag_name && 'value' == attr_name
16
- if node['multiple'] == 'multiple'
17
- node.xpath(".//option[@selected='selected']").map { |option| option.content }
17
+ if native['multiple'] == 'multiple'
18
+ native.xpath(".//option[@selected='selected']").map { |option| option[:value] || option.content }
18
19
  else
19
- option = node.xpath(".//option[@selected='selected']").first || node.xpath(".//option").first
20
- option.content if option
20
+ option = native.xpath(".//option[@selected='selected']").first || native.xpath(".//option").first
21
+ option[:value] || option.content if option
21
22
  end
22
23
  when 'input' == tag_name && 'checkbox' == type && 'checked' == attr_name
23
- node[attr_name] == 'checked' ? true : false
24
+ native[attr_name] == 'checked' ? true : false
24
25
  else
25
- node[attr_name]
26
+ native[attr_name]
26
27
  end
27
28
  end
28
29
 
29
30
  def value
30
31
  if tag_name == 'textarea'
31
- node.content
32
+ native.content
32
33
  else
33
- super
34
+ self[:value]
34
35
  end
35
36
  end
36
37
 
37
38
  def set(value)
38
39
  if tag_name == 'input' and type == 'radio'
39
- driver.html.xpath("//input[@name=#{Capybara::XPath.escape(self[:name])}]").each { |node| node.remove_attribute("checked") }
40
- node['checked'] = 'checked'
40
+ other_radios_xpath = XPath.generate { |x| x.anywhere(:input)[x.attr(:name).equals(self[:name])] }.to_s
41
+ driver.html.xpath(other_radios_xpath).each { |node| node.remove_attribute("checked") }
42
+ native['checked'] = 'checked'
41
43
  elsif tag_name == 'input' and type == 'checkbox'
42
- if value && !node['checked']
43
- node['checked'] = 'checked'
44
- elsif !value && node['checked']
45
- node.remove_attribute('checked')
44
+ if value && !native['checked']
45
+ native['checked'] = 'checked'
46
+ elsif !value && native['checked']
47
+ native.remove_attribute('checked')
46
48
  end
47
49
  elsif tag_name == 'input'
48
- node['value'] = value.to_s
50
+ native['value'] = value.to_s
49
51
  elsif tag_name == "textarea"
50
- node.content = value.to_s
52
+ native.content = value.to_s
51
53
  end
52
54
  end
53
55
 
54
- def select(option)
55
- if node['multiple'] != 'multiple'
56
- node.xpath(".//option[@selected]").each { |node| node.remove_attribute("selected") }
57
- end
58
-
59
- if option_node = node.xpath(".//option[text()=#{Capybara::XPath.escape(option)}]").first ||
60
- node.xpath(".//option[contains(.,#{Capybara::XPath.escape(option)})]").first
61
- option_node["selected"] = 'selected'
62
- else
63
- options = node.xpath(".//option").map { |o| "'#{o.text}'" }.join(', ')
64
- raise Capybara::OptionNotFound, "No such option '#{option}' in this select box. Available options: #{options}"
56
+ def select_option
57
+ if select_node['multiple'] != 'multiple'
58
+ select_node.find(".//option[@selected]").each { |node| node.native.remove_attribute("selected") }
65
59
  end
60
+ native["selected"] = 'selected'
66
61
  end
67
62
 
68
- def unselect(option)
69
- if node['multiple'] != 'multiple'
70
- raise Capybara::UnselectNotAllowed, "Cannot unselect option '#{option}' from single select box."
71
- end
72
-
73
- if option_node = node.xpath(".//option[text()=#{Capybara::XPath.escape(option)}]").first ||
74
- node.xpath(".//option[contains(.,#{Capybara::XPath.escape(option)})]").first
75
- option_node.remove_attribute('selected')
76
- else
77
- options = node.xpath(".//option").map { |o| "'#{o.text}'" }.join(', ')
78
- raise Capybara::OptionNotFound, "No such option '#{option}' in this select box. Available options: #{options}"
63
+ def unselect_option
64
+ if select_node['multiple'] != 'multiple'
65
+ raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
79
66
  end
67
+ native.remove_attribute('selected')
80
68
  end
81
69
 
82
70
  def click
83
71
  if tag_name == 'a'
84
72
  method = self["data-method"] || :get
85
73
  driver.process(method, self[:href].to_s)
86
- elsif (tag_name == 'input' or tag_name == 'button') and %w(submit image).include?(type)
74
+ elsif (tag_name == 'input' and %w(submit image).include?(type)) or
75
+ ((tag_name == 'button') and type.nil? or type == "submit")
87
76
  Form.new(driver, form).submit(self)
88
77
  end
89
78
  end
90
79
 
91
80
  def tag_name
92
- node.node_name
81
+ native.node_name
93
82
  end
94
83
 
95
84
  def visible?
96
- node.xpath("./ancestor-or-self::*[contains(@style, 'display:none') or contains(@style, 'display: none')]").size == 0
85
+ native.xpath("./ancestor-or-self::*[contains(@style, 'display:none') or contains(@style, 'display: none')]").size == 0
97
86
  end
98
87
 
99
88
  def path
100
- node.path
89
+ native.path
90
+ end
91
+
92
+ def find(locator)
93
+ native.xpath(locator).map { |n| self.class.new(driver, n) }
101
94
  end
102
95
 
103
96
  private
104
97
 
105
- def all_unfiltered(locator)
106
- node.xpath(locator).map { |n| self.class.new(driver, n) }
98
+ # a reference to the select node if this is an option node
99
+ def select_node
100
+ find('./ancestor::select').first
107
101
  end
108
102
 
109
103
  def type
110
- node[:type]
104
+ native[:type]
111
105
  end
112
106
 
113
107
  def form
114
- node.ancestors('form').first
108
+ native.ancestors('form').first
115
109
  end
116
110
  end
117
111
 
@@ -119,16 +113,16 @@ class Capybara::Driver::RackTest < Capybara::Driver::Base
119
113
  def params(button)
120
114
  params = {}
121
115
 
122
- node.xpath(".//input[not(@type) or (@type!='radio' and @type!='checkbox' and @type!='submit' and @type!='image')]").map do |input|
116
+ native.xpath(".//input[not(@disabled) and (not(@type) or (@type!='radio' and @type!='checkbox' and @type!='submit' and @type!='image'))]").map do |input|
123
117
  merge_param!(params, input['name'].to_s, input['value'].to_s)
124
118
  end
125
- node.xpath(".//textarea").map do |textarea|
119
+ native.xpath(".//textarea[not(@disabled)]").map do |textarea|
126
120
  merge_param!(params, textarea['name'].to_s, textarea.text.to_s)
127
121
  end
128
- node.xpath(".//input[@type='radio' or @type='checkbox']").map do |input|
122
+ native.xpath(".//input[not(@disabled) and (@type='radio' or @type='checkbox')]").map do |input|
129
123
  merge_param!(params, input['name'].to_s, input['value'].to_s) if input['checked']
130
124
  end
131
- node.xpath(".//select").map do |select|
125
+ native.xpath(".//select[not(@disabled)]").map do |select|
132
126
  if select['multiple'] == 'multiple'
133
127
  options = select.xpath(".//option[@selected]")
134
128
  options.each do |option|
@@ -140,7 +134,7 @@ class Capybara::Driver::RackTest < Capybara::Driver::Base
140
134
  merge_param!(params, select['name'].to_s, (option['value'] || option.text).to_s) if option
141
135
  end
142
136
  end
143
- node.xpath(".//input[@type='file']").map do |input|
137
+ native.xpath(".//input[not(@disabled) and @type='file']").map do |input|
144
138
  unless input['value'].to_s.empty?
145
139
  if multipart?
146
140
  content_type = MIME::Types.type_for(input['value'].to_s).first.to_s
@@ -156,7 +150,7 @@ class Capybara::Driver::RackTest < Capybara::Driver::Base
156
150
  end
157
151
 
158
152
  def submit(button)
159
- driver.submit(method, node['action'].to_s, params(button))
153
+ driver.submit(method, native['action'].to_s, params(button))
160
154
  end
161
155
 
162
156
  def multipart?
@@ -170,16 +164,7 @@ class Capybara::Driver::RackTest < Capybara::Driver::Base
170
164
  end
171
165
 
172
166
  def merge_param!(params, key, value)
173
- collection = key.sub!(/\[\]$/, '')
174
- if collection
175
- if params[key]
176
- params[key] << value
177
- else
178
- params[key] = [value]
179
- end
180
- else
181
- params[key] = value
182
- end
167
+ Rack::Utils.normalize_params(params, key, value)
183
168
  end
184
169
  end
185
170
 
@@ -200,7 +185,7 @@ class Capybara::Driver::RackTest < Capybara::Driver::Base
200
185
 
201
186
  def process(method, path, attributes = {})
202
187
  return if path.gsub(/^#{request_path}/, '') =~ /^#/
203
- send(method, path, attributes, env)
188
+ send(method, to_binary(path), to_binary( attributes ), env)
204
189
  follow_redirects!
205
190
  end
206
191
 
@@ -216,9 +201,23 @@ class Capybara::Driver::RackTest < Capybara::Driver::Base
216
201
  response.status
217
202
  end
218
203
 
204
+ def to_binary(object)
205
+ return object unless Kernel.const_defined?(:Encoding)
206
+
207
+ if object.respond_to?(:force_encoding)
208
+ object.dup.force_encoding(Encoding::ASCII_8BIT)
209
+ elsif object.respond_to?(:each_pair) #Hash
210
+ {}.tap { |x| object.each_pair {|k,v| x[to_binary(k)] = to_binary(v) } }
211
+ elsif object.respond_to?(:each) #Array
212
+ object.map{|x| to_binary(x)}
213
+ else
214
+ object
215
+ end
216
+ end
217
+
219
218
  def submit(method, path, attributes)
220
219
  path = request_path if not path or path.empty?
221
- send(method, path, attributes, env)
220
+ send(method, to_binary(path), to_binary(attributes), env)
222
221
  follow_redirects!
223
222
  end
224
223
 
@@ -235,7 +234,7 @@ class Capybara::Driver::RackTest < Capybara::Driver::Base
235
234
  end
236
235
  alias_method :source, :body
237
236
 
238
- def cleanup!
237
+ def reset!
239
238
  clear_cookies
240
239
  end
241
240