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
@@ -1,16 +1,16 @@
1
1
  require 'selenium-webdriver'
2
2
 
3
3
  class Capybara::Driver::Selenium < Capybara::Driver::Base
4
- class Node < Capybara::Node
4
+ class Node < Capybara::Driver::Node
5
5
  def text
6
- node.text
6
+ native.text
7
7
  end
8
8
 
9
9
  def [](name)
10
10
  if name == :value
11
- node.value
11
+ native.value
12
12
  else
13
- node.attribute(name.to_s)
13
+ native.attribute(name.to_s)
14
14
  end
15
15
  rescue Selenium::WebDriver::Error::WebDriverError
16
16
  nil
@@ -18,65 +18,59 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
18
18
 
19
19
  def value
20
20
  if tag_name == "select" and self[:multiple]
21
- node.find_elements(:xpath, ".//option").select { |n| n.selected? }.map { |n| n.text }
21
+ native.find_elements(:xpath, ".//option").select { |n| n.selected? }.map { |n| n.value || n.text }
22
22
  else
23
- super
23
+ self[:value]
24
24
  end
25
25
  end
26
26
 
27
27
  def set(value)
28
28
  if tag_name == 'input' and type == 'radio'
29
- node.click
29
+ native.click
30
30
  elsif tag_name == 'input' and type == 'checkbox'
31
- node.click if node.attribute('checked') != value
31
+ native.click if value ^ native.attribute('checked').to_s.eql?("true")
32
32
  elsif tag_name == 'textarea' or tag_name == 'input'
33
- node.clear
34
- node.send_keys(value.to_s)
33
+ native.clear
34
+ native.send_keys(value.to_s)
35
35
  end
36
36
  end
37
37
 
38
- def select(option)
39
- option_node = node.find_element(:xpath, ".//option[normalize-space(text())=#{Capybara::XPath.escape(option)}]") || node.find_element(:xpath, ".//option[contains(.,#{Capybara::XPath.escape(option)})]")
40
- option_node.select
41
- rescue
42
- options = node.find_elements(:xpath, "//option").map { |o| "'#{o.text}'" }.join(', ')
43
- raise Capybara::OptionNotFound, "No such option '#{option}' in this select box. Available options: #{options}"
38
+ def select_option
39
+ native.select
44
40
  end
45
41
 
46
- def unselect(option)
47
- if node['multiple'] != 'multiple'
48
- raise Capybara::UnselectNotAllowed, "Cannot unselect option '#{option}' from single select box."
49
- end
50
-
51
- begin
52
- option_node = node.find_element(:xpath, ".//option[normalize-space(text())=#{Capybara::XPath.escape(option)}]") || node.find_element(:xpath, ".//option[contains(.,#{Capybara::XPath.escape(option)})]")
53
- option_node.clear
54
- rescue
55
- options = node.find_elements(:xpath, "//option").map { |o| "'#{o.text}'" }.join(', ')
56
- raise Capybara::OptionNotFound, "No such option '#{option}' in this select box. Available options: #{options}"
42
+ def unselect_option
43
+ if select_node['multiple'] != 'multiple' and select_node['multiple'] != 'true'
44
+ raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
57
45
  end
46
+ native.clear
58
47
  end
59
48
 
60
49
  def click
61
- node.click
50
+ native.click
62
51
  end
63
52
 
64
53
  def drag_to(element)
65
- node.drag_and_drop_on(element.node)
54
+ native.drag_and_drop_on(element.native)
66
55
  end
67
56
 
68
57
  def tag_name
69
- node.tag_name
58
+ native.tag_name
70
59
  end
71
60
 
72
61
  def visible?
73
- node.displayed? and node.displayed? != "false"
62
+ native.displayed? and native.displayed? != "false"
63
+ end
64
+
65
+ def find(locator)
66
+ native.find_elements(:xpath, locator).map { |n| self.class.new(driver, n) }
74
67
  end
75
-
68
+
76
69
  private
77
70
 
78
- def all_unfiltered(locator)
79
- node.find_elements(:xpath, locator).map { |n| self.class.new(driver, n) }
71
+ # a reference to the select node if this is an option node
72
+ def select_node
73
+ find('./ancestor::select').first
80
74
  end
81
75
 
82
76
  def type
@@ -85,20 +79,21 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
85
79
 
86
80
  end
87
81
 
88
- attr_reader :app, :rack_server
82
+ attr_reader :app, :rack_server, :options
89
83
 
90
- def self.driver
91
- unless @driver
92
- @driver = Selenium::WebDriver.for :firefox
84
+ def browser
85
+ unless @browser
86
+ @browser = Selenium::WebDriver.for(options.delete(:browser) || :firefox, options)
93
87
  at_exit do
94
- @driver.quit
88
+ @browser.quit
95
89
  end
96
90
  end
97
- @driver
91
+ @browser
98
92
  end
99
93
 
100
- def initialize(app)
94
+ def initialize(app, options={})
101
95
  @app = app
96
+ @options = options
102
97
  @rack_server = Capybara::Server.new(@app)
103
98
  @rack_server.boot if Capybara.run_server
104
99
  end
@@ -133,12 +128,9 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
133
128
  browser.execute_script "return #{script}"
134
129
  end
135
130
 
136
- def browser
137
- self.class.driver
138
- end
139
-
140
- def cleanup!
141
- browser.manage.delete_all_cookies
131
+ def reset!
132
+ # Use instance variable directly so we avoid starting the browser just to reset the session
133
+ @browser.manage.delete_all_cookies if @browser
142
134
  end
143
135
 
144
136
  def within_frame(frame_id)
@@ -148,6 +140,10 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
148
140
  browser.switch_to.window old_window
149
141
  end
150
142
 
143
+ def within_window(handle, &blk)
144
+ browser.switch_to.window(handle, &blk)
145
+ end
146
+
151
147
  private
152
148
 
153
149
  def url(path)
@@ -6,35 +6,58 @@ module Capybara
6
6
 
7
7
  attr_accessor :app
8
8
 
9
+ ##
10
+ #
11
+ # @return [Symbol] The name of the driver to use by default
12
+ #
9
13
  def default_driver
10
14
  @default_driver || :rack_test
11
15
  end
12
16
 
17
+ ##
18
+ #
19
+ # @return [Symbol] The name of the driver currently in use
20
+ #
13
21
  def current_driver
14
22
  @current_driver || default_driver
15
23
  end
16
24
  alias_method :mode, :current_driver
17
25
 
26
+ ##
27
+ #
28
+ # @return [Symbol] The name of the driver used when JavaScript is needed
29
+ #
18
30
  def javascript_driver
19
31
  @javascript_driver || :selenium
20
32
  end
21
33
 
34
+ ##
35
+ #
36
+ # Use the default driver as the current driver
37
+ #
22
38
  def use_default_driver
23
39
  @current_driver = nil
24
40
  end
25
41
 
42
+ ##
43
+ #
44
+ # The current Capybara::Session base on what is set as Capybara.app and Capybara.current_driver
45
+ #
46
+ # @return [Capybara::Session] The currently used session
47
+ #
26
48
  def current_session
27
49
  session_pool["#{current_driver}#{app.object_id}"] ||= Capybara::Session.new(current_driver, app)
28
50
  end
29
51
 
30
- def current_session?
31
- session_pool.has_key?("#{current_driver}#{app.object_id}")
32
- end
33
-
52
+ ##
53
+ #
54
+ # Reset sessions, cleaning out the pool of sessions. This will remove any session information such
55
+ # as cookies.
56
+ #
34
57
  def reset_sessions!
35
- session_pool.each { |mode, session| session.cleanup! }
36
- @session_pool = nil
58
+ session_pool.each { |mode, session| session.reset! }
37
59
  end
60
+ alias_method :reset!, :reset_sessions!
38
61
 
39
62
  private
40
63
 
@@ -45,6 +68,21 @@ module Capybara
45
68
 
46
69
  extend(self)
47
70
 
71
+ ##
72
+ #
73
+ # Shortcut to accessing the current session. This is useful when Capybara is included in a
74
+ # class or module.
75
+ #
76
+ # class MyClass
77
+ # include Capybara
78
+ #
79
+ # def has_header?
80
+ # page.has_css?('h1')
81
+ # end
82
+ # end
83
+ #
84
+ # @return [Capybara::Session] The current session object
85
+ #
48
86
  def page
49
87
  Capybara.current_session
50
88
  end
@@ -1,60 +1,221 @@
1
+ require 'capybara/node/finders'
2
+ require 'capybara/node/actions'
3
+ require 'capybara/node/matchers'
4
+
1
5
  module Capybara
6
+
7
+ ##
8
+ #
9
+ # A {Capybara::Node} represents either an element on a page through the subclass
10
+ # {Capybara::Element} or a document through {Capybara::Document}.
11
+ #
12
+ # Both types of Node share the same methods, used for interacting with the
13
+ # elements on the page. These methods are divided into three categories,
14
+ # finders, actions and matchers. These are found in the modules
15
+ # {Capybara::Node::Finders}, {Capybara::Node::Actions} and {Capybara::Node::Matchers}
16
+ # respectively.
17
+ #
18
+ # A {Capybara::Session} exposes all methods from {Capybara::Document} directly:
19
+ #
20
+ # session = Capybara::Session.new(:rack_test, my_app)
21
+ # session.visit('/')
22
+ # session.fill_in('Foo', :with => 'Bar') # from Capybara::Node::Actions
23
+ # bar = session.find('#bar') # from Capybara::Node::Finders
24
+ # bar.select('Baz', :from => 'Quox') # from Capybara::Node::Actions
25
+ # session.has_css?('#foobar') # from Capybara::Node::Matchers
26
+ #
2
27
  class Node
3
- include Searchable
28
+ attr_reader :session, :base
29
+
30
+ include Capybara::Node::Finders
31
+ include Capybara::Node::Actions
32
+ include Capybara::Node::Matchers
33
+
34
+ def initialize(session, base)
35
+ @session = session
36
+ @base = base
37
+ end
38
+
39
+ protected
4
40
 
5
- attr_reader :driver, :node
41
+ def driver
42
+ session.driver
43
+ end
44
+ end
45
+
46
+ ##
47
+ #
48
+ # A {Capybara::Element} represents a single element on the page. It is possible
49
+ # to interact with the contents of this element the same as with a document:
50
+ #
51
+ # session = Capybara::Session.new(:rack_test, my_app)
52
+ #
53
+ # bar = session.find('#bar') # from Capybara::Node::Finders
54
+ # bar.select('Baz', :from => 'Quox') # from Capybara::Node::Actions
55
+ #
56
+ # {Capybara::Element} also has access to HTML attributes and other properties of the
57
+ # element:
58
+ #
59
+ # bar.value
60
+ # bar.text
61
+ # bar[:title]
62
+ #
63
+ # @see Capybara::Node
64
+ #
65
+ class Element < Node
66
+
67
+ ##
68
+ #
69
+ # @return [Object] The native element from the driver, this allows access to driver specific methods
70
+ #
71
+ def native
72
+ base.native
73
+ end
6
74
 
7
- def initialize(driver, node)
8
- @driver = driver
9
- @node = node
75
+ ##
76
+ #
77
+ # @deprecated node is deprecated, please use {Capybara::Element#native} instead
78
+ #
79
+ def node
80
+ Capybara.deprecate("node", "native")
81
+ native
10
82
  end
11
83
 
84
+ ##
85
+ #
86
+ # @return [String] The text of the element
87
+ #
12
88
  def text
13
- raise NotImplementedError
89
+ base.text
14
90
  end
15
91
 
16
- def [](name)
17
- raise NotImplementedError
92
+ ##
93
+ #
94
+ # Retrieve the given attribute
95
+ #
96
+ # element[:title] # => HTML title attribute
97
+ #
98
+ # @param [Symbol] attribute The attribute to retrieve
99
+ # @return [String] The value of the attribute
100
+ #
101
+ def [](attribute)
102
+ base[attribute]
18
103
  end
19
104
 
105
+ ##
106
+ #
107
+ # @return [String] The value of the form element
108
+ #
20
109
  def value
21
- self[:value]
110
+ base.value
22
111
  end
23
112
 
113
+ ##
114
+ #
115
+ # Set the value of the form element to the given value.
116
+ #
117
+ # @param [String] value The new value
118
+ #
24
119
  def set(value)
25
- raise NotImplementedError
120
+ base.set(value)
26
121
  end
27
122
 
28
- def select(option)
29
- raise NotImplementedError
123
+ ##
124
+ #
125
+ # Select this node if is an option element inside a select tag
126
+ #
127
+ def select_option
128
+ base.select_option
30
129
  end
31
130
 
32
- def unselect(option)
33
- raise NotImplementedError
131
+ ##
132
+ #
133
+ # Unselect this node if is an option element inside a multiple select tag
134
+ #
135
+ def unselect_option
136
+ base.unselect_option
34
137
  end
35
138
 
139
+ ##
140
+ #
141
+ # Click the Element
142
+ #
36
143
  def click
37
- raise NotImplementedError
38
- end
39
-
40
- def drag_to(element)
41
- raise NotImplementedError
144
+ base.click
42
145
  end
43
146
 
147
+ ##
148
+ #
149
+ # @return [String] The tag name of the element
150
+ #
44
151
  def tag_name
45
- raise NotImplementedError
152
+ base.tag_name
46
153
  end
47
154
 
155
+ ##
156
+ #
157
+ # Whether or not the element is visible. Not all drivers support CSS, so
158
+ # the result may be inaccurate.
159
+ #
160
+ # @return [Boolean] Whether the element is visible
161
+ #
48
162
  def visible?
49
- raise NotImplementedError
163
+ base.visible?
50
164
  end
51
165
 
166
+ ##
167
+ #
168
+ # An XPath expression describing where on the page the element can be found
169
+ #
170
+ # @return [String] An XPath expression
171
+ #
52
172
  def path
53
- raise NotSupportedByDriverError
173
+ base.path
54
174
  end
55
-
175
+
176
+ ##
177
+ #
178
+ # Trigger any event on the current element, for example mouseover or focus
179
+ # events. Does not work in Selenium.
180
+ #
181
+ # @param [String] event The name of the event to trigger
182
+ #
56
183
  def trigger(event)
57
- raise NotSupportedByDriverError
184
+ base.trigger(event)
185
+ end
186
+
187
+ ##
188
+ #
189
+ # Drag the element to the given other element.
190
+ #
191
+ # source = page.find('#foo')
192
+ # target = page.find('#bar')
193
+ # source.drag_to(target)
194
+ #
195
+ # @param [Capybara::Element] node The element to drag to
196
+ #
197
+ def drag_to(node)
198
+ base.drag_to(node.base)
199
+ end
200
+
201
+ def inspect
202
+ %(#<Capybara::Element tag="#{tag_name}" path="#{path}">)
203
+ rescue NotSupportedByDriverError
204
+ %(#<Capybara::Element tag="#{tag_name}">)
205
+ end
206
+
207
+ end
208
+
209
+ ##
210
+ #
211
+ # A {Capybara::Document} represents an HTML document. Any operation
212
+ # performed on it will be performed on the entire document.
213
+ #
214
+ # @see Capybara::Node
215
+ #
216
+ class Document < Node
217
+ def inspect
218
+ %(#<Capybara::Document>)
58
219
  end
59
220
  end
60
221
  end