capybara 0.4.0 → 0.4.1.rc

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