capybara 0.3.9 → 0.4.0.rc

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