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
@@ -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