capybara 0.4.0 → 0.4.1.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 (46) hide show
  1. data/History.txt +35 -0
  2. data/README.rdoc +60 -19
  3. data/lib/capybara.rb +81 -5
  4. data/lib/capybara/driver/base.rb +1 -6
  5. data/lib/capybara/driver/celerity_driver.rb +19 -9
  6. data/lib/capybara/driver/node.rb +8 -0
  7. data/lib/capybara/driver/rack_test_driver.rb +42 -29
  8. data/lib/capybara/driver/selenium_driver.rb +11 -3
  9. data/lib/capybara/dsl.rb +11 -0
  10. data/lib/capybara/node/actions.rb +4 -14
  11. data/lib/capybara/node/base.rb +47 -0
  12. data/lib/capybara/node/document.rb +17 -0
  13. data/lib/capybara/node/element.rb +178 -0
  14. data/lib/capybara/node/finders.rb +78 -27
  15. data/lib/capybara/node/matchers.rb +40 -11
  16. data/lib/capybara/node/simple.rb +116 -0
  17. data/lib/capybara/rspec.rb +18 -0
  18. data/lib/capybara/server.rb +5 -10
  19. data/lib/capybara/session.rb +7 -16
  20. data/lib/capybara/spec/driver.rb +16 -1
  21. data/lib/capybara/spec/session.rb +1 -0
  22. data/lib/capybara/spec/session/all_spec.rb +5 -0
  23. data/lib/capybara/spec/session/attach_file_spec.rb +6 -0
  24. data/lib/capybara/spec/session/click_link_or_button_spec.rb +2 -3
  25. data/lib/capybara/spec/session/find_spec.rb +0 -6
  26. data/lib/capybara/spec/session/first_spec.rb +72 -0
  27. data/lib/capybara/spec/session/has_css_spec.rb +107 -1
  28. data/lib/capybara/spec/session/has_field_spec.rb +60 -0
  29. data/lib/capybara/spec/session/has_select_spec.rb +40 -0
  30. data/lib/capybara/spec/session/javascript.rb +4 -13
  31. data/lib/capybara/spec/session/within_spec.rb +10 -3
  32. data/lib/capybara/spec/test_app.rb +20 -1
  33. data/lib/capybara/spec/views/form.erb +27 -0
  34. data/lib/capybara/spec/views/with_html.erb +21 -5
  35. data/lib/capybara/util/save_and_open_page.rb +8 -5
  36. data/lib/capybara/version.rb +1 -1
  37. data/spec/basic_node_spec.rb +77 -0
  38. data/spec/capybara_spec.rb +18 -0
  39. data/spec/dsl_spec.rb +26 -0
  40. data/spec/rspec_spec.rb +47 -0
  41. data/spec/save_and_open_page_spec.rb +83 -7
  42. data/spec/server_spec.rb +20 -0
  43. data/spec/session/rack_test_session_spec.rb +10 -0
  44. data/spec/string_spec.rb +77 -0
  45. metadata +306 -295
  46. data/lib/capybara/node.rb +0 -221
@@ -43,7 +43,7 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
43
43
  if select_node['multiple'] != 'multiple' and select_node['multiple'] != 'true'
44
44
  raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
45
45
  end
46
- native.clear
46
+ native.toggle if selected?
47
47
  end
48
48
 
49
49
  def click
@@ -59,9 +59,17 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
59
59
  end
60
60
 
61
61
  def visible?
62
- native.displayed? and native.displayed? != "false"
62
+ displayed = native.displayed?
63
+ displayed and displayed != "false"
63
64
  end
64
65
 
66
+ def selected?
67
+ selected = native.selected?
68
+ selected and selected != "false"
69
+ end
70
+
71
+ alias :checked? :selected?
72
+
65
73
  def find(locator)
66
74
  native.find_elements(:xpath, locator).map { |n| self.class.new(driver, n) }
67
75
  end
@@ -83,7 +91,7 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
83
91
 
84
92
  def browser
85
93
  unless @browser
86
- @browser = Selenium::WebDriver.for(options.delete(:browser) || :firefox, options)
94
+ @browser = Selenium::WebDriver.for(options[:browser] || :firefox, options)
87
95
  at_exit do
88
96
  @browser.quit
89
97
  end
data/lib/capybara/dsl.rb CHANGED
@@ -39,6 +39,17 @@ module Capybara
39
39
  @current_driver = nil
40
40
  end
41
41
 
42
+ ##
43
+ #
44
+ # Yield a block using a specific driver
45
+ #
46
+ def using_driver(driver)
47
+ Capybara.current_driver = driver
48
+ yield
49
+ ensure
50
+ Capybara.use_default_driver
51
+ end
52
+
42
53
  ##
43
54
  #
44
55
  # The current Capybara::Session base on what is set as Capybara.app and Capybara.current_driver
@@ -1,5 +1,5 @@
1
1
  module Capybara
2
- class Node
2
+ module Node
3
3
  module Actions
4
4
 
5
5
  ##
@@ -13,6 +13,7 @@ module Capybara
13
13
  msg = "no link or button '#{locator}' found"
14
14
  find(:xpath, XPath::HTML.link_or_button(locator), :message => msg).click
15
15
  end
16
+ alias_method :click_on, :click_link_or_button
16
17
 
17
18
  ##
18
19
  #
@@ -101,9 +102,10 @@ module Capybara
101
102
  # box is a multiple select, +select+ can be called multiple times to select more than
102
103
  # one option. The select box can be found via its name, id or label text.
103
104
  #
104
- # page.uncheck('German')
105
+ # page.select 'March', :from => 'Month'
105
106
  #
106
107
  # @param [String] locator Which check box to uncheck
108
+ # @param [Hash{:from => String}] The id, name or label of the select box
107
109
  #
108
110
  def select(value, options={})
109
111
  if options.has_key?(:from)
@@ -153,18 +155,6 @@ module Capybara
153
155
  msg = "cannot attach file, no file field with id, name, or label '#{locator}' found"
154
156
  find(:xpath, XPath::HTML.file_field(locator), :message => msg).set(path)
155
157
  end
156
-
157
- ##
158
- #
159
- # Drag one element to another
160
- #
161
- # @deprecated Use Capybara::Element#drag_to instead.
162
- #
163
- def drag(source_locator, target_locator)
164
- source = find(:xpath, source_locator, :message => "drag source '#{source_locator}' not found on page")
165
- target = find(:xpath, target_locator, :message => "drag target '#{target_locator}' not found on page")
166
- source.drag_to(target)
167
- end
168
158
  end
169
159
  end
170
160
  end
@@ -0,0 +1,47 @@
1
+ module Capybara
2
+ module Node
3
+
4
+ ##
5
+ #
6
+ # A {Capybara::Node::Base} represents either an element on a page through the subclass
7
+ # {Capybara::Node::Element} or a document through {Capybara::Node::Document}.
8
+ #
9
+ # Both types of Node share the same methods, used for interacting with the
10
+ # elements on the page. These methods are divided into three categories,
11
+ # finders, actions and matchers. These are found in the modules
12
+ # {Capybara::Node::Finders}, {Capybara::Node::Actions} and {Capybara::Node::Matchers}
13
+ # respectively.
14
+ #
15
+ # A {Capybara::Session} exposes all methods from {Capybara::Node::Document} directly:
16
+ #
17
+ # session = Capybara::Session.new(:rack_test, my_app)
18
+ # session.visit('/')
19
+ # session.fill_in('Foo', :with => 'Bar') # from Capybara::Node::Actions
20
+ # bar = session.find('#bar') # from Capybara::Node::Finders
21
+ # bar.select('Baz', :from => 'Quox') # from Capybara::Node::Actions
22
+ # session.has_css?('#foobar') # from Capybara::Node::Matchers
23
+ #
24
+ class Base
25
+ attr_reader :session, :base
26
+
27
+ include Capybara::Node::Finders
28
+ include Capybara::Node::Actions
29
+ include Capybara::Node::Matchers
30
+
31
+ def initialize(session, base)
32
+ @session = session
33
+ @base = base
34
+ end
35
+
36
+ protected
37
+
38
+ def wait?
39
+ driver.wait?
40
+ end
41
+
42
+ def driver
43
+ session.driver
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,17 @@
1
+ module Capybara
2
+ module Node
3
+
4
+ ##
5
+ #
6
+ # A {Capybara::Document} represents an HTML document. Any operation
7
+ # performed on it will be performed on the entire document.
8
+ #
9
+ # @see Capybara::Node
10
+ #
11
+ class Document < Base
12
+ def inspect
13
+ %(#<Capybara::Document>)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,178 @@
1
+ module Capybara
2
+ module Node
3
+
4
+ ##
5
+ #
6
+ # A {Capybara::Element} represents a single element on the page. It is possible
7
+ # to interact with the contents of this element the same as with a document:
8
+ #
9
+ # session = Capybara::Session.new(:rack_test, my_app)
10
+ #
11
+ # bar = session.find('#bar') # from Capybara::Node::Finders
12
+ # bar.select('Baz', :from => 'Quox') # from Capybara::Node::Actions
13
+ #
14
+ # {Capybara::Element} also has access to HTML attributes and other properties of the
15
+ # element:
16
+ #
17
+ # bar.value
18
+ # bar.text
19
+ # bar[:title]
20
+ #
21
+ # @see Capybara::Node
22
+ #
23
+ class Element < Base
24
+
25
+ ##
26
+ #
27
+ # @return [Object] The native element from the driver, this allows access to driver specific methods
28
+ #
29
+ def native
30
+ base.native
31
+ end
32
+
33
+ ##
34
+ #
35
+ # @return [String] The text of the element
36
+ #
37
+ def text
38
+ base.text
39
+ end
40
+
41
+ ##
42
+ #
43
+ # Retrieve the given attribute
44
+ #
45
+ # element[:title] # => HTML title attribute
46
+ #
47
+ # @param [Symbol] attribute The attribute to retrieve
48
+ # @return [String] The value of the attribute
49
+ #
50
+ def [](attribute)
51
+ base[attribute]
52
+ end
53
+
54
+ ##
55
+ #
56
+ # @return [String] The value of the form element
57
+ #
58
+ def value
59
+ base.value
60
+ end
61
+
62
+ ##
63
+ #
64
+ # Set the value of the form element to the given value.
65
+ #
66
+ # @param [String] value The new value
67
+ #
68
+ def set(value)
69
+ base.set(value)
70
+ end
71
+
72
+ ##
73
+ #
74
+ # Select this node if is an option element inside a select tag
75
+ #
76
+ def select_option
77
+ base.select_option
78
+ end
79
+
80
+ ##
81
+ #
82
+ # Unselect this node if is an option element inside a multiple select tag
83
+ #
84
+ def unselect_option
85
+ base.unselect_option
86
+ end
87
+
88
+ ##
89
+ #
90
+ # Click the Element
91
+ #
92
+ def click
93
+ base.click
94
+ end
95
+
96
+ ##
97
+ #
98
+ # @return [String] The tag name of the element
99
+ #
100
+ def tag_name
101
+ base.tag_name
102
+ end
103
+
104
+ ##
105
+ #
106
+ # Whether or not the element is visible. Not all drivers support CSS, so
107
+ # the result may be inaccurate.
108
+ #
109
+ # @return [Boolean] Whether the element is visible
110
+ #
111
+ def visible?
112
+ base.visible?
113
+ end
114
+
115
+ ##
116
+ #
117
+ # Whether or not the element is checked.
118
+ #
119
+ # @return [Boolean] Whether the element is checked
120
+ #
121
+ def checked?
122
+ base.checked?
123
+ end
124
+
125
+ ##
126
+ #
127
+ # Whether or not the element is selected.
128
+ #
129
+ # @return [Boolean] Whether the element is selected
130
+ #
131
+ def selected?
132
+ base.selected?
133
+ end
134
+
135
+ ##
136
+ #
137
+ # An XPath expression describing where on the page the element can be found
138
+ #
139
+ # @return [String] An XPath expression
140
+ #
141
+ def path
142
+ base.path
143
+ end
144
+
145
+ ##
146
+ #
147
+ # Trigger any event on the current element, for example mouseover or focus
148
+ # events. Does not work in Selenium.
149
+ #
150
+ # @param [String] event The name of the event to trigger
151
+ #
152
+ def trigger(event)
153
+ base.trigger(event)
154
+ end
155
+
156
+ ##
157
+ #
158
+ # Drag the element to the given other element.
159
+ #
160
+ # source = page.find('#foo')
161
+ # target = page.find('#bar')
162
+ # source.drag_to(target)
163
+ #
164
+ # @param [Capybara::Element] node The element to drag to
165
+ #
166
+ def drag_to(node)
167
+ base.drag_to(node.base)
168
+ end
169
+
170
+ def inspect
171
+ %(#<Capybara::Element tag="#{tag_name}" path="#{path}">)
172
+ rescue NotSupportedByDriverError
173
+ %(#<Capybara::Element tag="#{tag_name}">)
174
+ end
175
+
176
+ end
177
+ end
178
+ end
@@ -1,5 +1,5 @@
1
1
  module Capybara
2
- class Node
2
+ module Node
3
3
  module Finders
4
4
 
5
5
  ##
@@ -24,22 +24,17 @@ module Capybara
24
24
  # @raise [Capybara::ElementNotFound] If the element can't be found before time expires
25
25
  #
26
26
  def find(*args)
27
- node = wait_conditionally_until { all(*args).first }
28
- ensure
29
- options = if args.last.is_a?(Hash) then args.last else {} end
30
- raise Capybara::ElementNotFound, options[:message] || "Unable to find '#{args[1] || args[0]}'" unless node
27
+ begin
28
+ node = wait_conditionally_until { first(*args) }
29
+ rescue TimeoutError
30
+ end
31
+ unless node
32
+ options = if args.last.is_a?(Hash) then args.last else {} end
33
+ raise Capybara::ElementNotFound, options[:message] || "Unable to find '#{args[1] || args[0]}'"
34
+ end
31
35
  return node
32
36
  end
33
37
 
34
- ##
35
- #
36
- # @deprecated {#find} now behaves like locate used to. Use {#find} instead.
37
- #
38
- def locate(*args)
39
- Capybara.deprecate("locate", "find")
40
- find(*args)
41
- end
42
-
43
38
  ##
44
39
  #
45
40
  # Find a form field on the page. The field can be found by its name, id or label text.
@@ -120,31 +115,87 @@ module Capybara
120
115
  # @return [Capybara::Element] The found elements
121
116
  #
122
117
  def all(*args)
123
- options = if args.last.is_a?(Hash) then args.pop else {} end
118
+ options = extract_normalized_options(args)
124
119
 
125
- results = Capybara::Selector.normalize(*args).map do |path|
126
- base.find(path)
127
- end.flatten
128
-
129
- if text = options[:text]
130
- text = Regexp.escape(text) unless text.kind_of?(Regexp)
120
+ Capybara::Selector.normalize(*args).
121
+ map { |path| find_in_base(path) }.flatten.
122
+ select { |node| matches_options(node, options) }.
123
+ map { |node| convert_element(node) }
124
+ end
131
125
 
132
- results = results.select { |node| node.text.match(text) }
133
- end
126
+ ##
127
+ #
128
+ # Find the first element on the page matching the given selector
129
+ # and options, or nil if no element matches.
130
+ #
131
+ # When only the first matching element is needed, this method can
132
+ # be faster than all(*args).first.
133
+ #
134
+ # @param [:css, :xpath, String] kind_or_locator Either the kind of selector or the selector itself
135
+ # @param [String] locator The selector
136
+ # @param [Hash{Symbol => Object}] options Additional options; see {all}
137
+ # @return Capybara::Element The found element
138
+ #
139
+ def first(*args)
140
+ options = extract_normalized_options(args)
134
141
 
135
- if options[:visible] or Capybara.ignore_hidden_elements
136
- results = results.select { |node| node.visible? }
142
+ Capybara::Selector.normalize(*args).each do |path|
143
+ find_in_base(path).each do |node|
144
+ if matches_options(node, options)
145
+ return convert_element(node)
146
+ end
147
+ end
137
148
  end
138
149
 
139
- results.map { |n| Capybara::Element.new(session, n) }
150
+ nil
140
151
  end
141
152
 
142
153
  protected
143
154
 
155
+ def find_in_base(xpath)
156
+ base.find(xpath)
157
+ end
158
+
159
+ def convert_element(element)
160
+ Capybara::Node::Element.new(session, element)
161
+ end
162
+
144
163
  def wait_conditionally_until
145
- if driver.wait? then session.wait_until { yield } else yield end
164
+ if wait? then session.wait_until { yield } else yield end
146
165
  end
147
166
 
167
+ def extract_normalized_options(args)
168
+ options = if args.last.is_a?(Hash) then args.pop.dup else {} end
169
+
170
+ if text = options[:text]
171
+ options[:text] = Regexp.escape(text) unless text.kind_of?(Regexp)
172
+ end
173
+
174
+ if !options.has_key?(:visible)
175
+ options[:visible] = Capybara.ignore_hidden_elements
176
+ end
177
+
178
+ if selected = options[:selected]
179
+ options[:selected] = [selected].flatten
180
+ end
181
+
182
+ options
183
+ end
184
+
185
+ def matches_options(node, options)
186
+ return false if options[:text] and not node.text.match(options[:text])
187
+ return false if options[:visible] and not node.visible?
188
+ return false if options[:with] and not node.value == options[:with]
189
+ return false if options[:checked] and not node.checked?
190
+ return false if options[:unchecked] and node.checked?
191
+ return false if options[:selected] and not has_selected_options?(node, options[:selected])
192
+ true
193
+ end
194
+
195
+ def has_selected_options?(node, expected)
196
+ actual = node.find('.//option').select { |option| option.selected? }.map { |option| option.text }
197
+ (expected - actual).empty?
198
+ end
148
199
  end
149
200
  end
150
201
  end