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.
- data/History.txt +35 -0
- data/README.rdoc +60 -19
- data/lib/capybara.rb +81 -5
- data/lib/capybara/driver/base.rb +1 -6
- data/lib/capybara/driver/celerity_driver.rb +19 -9
- data/lib/capybara/driver/node.rb +8 -0
- data/lib/capybara/driver/rack_test_driver.rb +42 -29
- data/lib/capybara/driver/selenium_driver.rb +11 -3
- data/lib/capybara/dsl.rb +11 -0
- data/lib/capybara/node/actions.rb +4 -14
- data/lib/capybara/node/base.rb +47 -0
- data/lib/capybara/node/document.rb +17 -0
- data/lib/capybara/node/element.rb +178 -0
- data/lib/capybara/node/finders.rb +78 -27
- data/lib/capybara/node/matchers.rb +40 -11
- data/lib/capybara/node/simple.rb +116 -0
- data/lib/capybara/rspec.rb +18 -0
- data/lib/capybara/server.rb +5 -10
- data/lib/capybara/session.rb +7 -16
- data/lib/capybara/spec/driver.rb +16 -1
- data/lib/capybara/spec/session.rb +1 -0
- data/lib/capybara/spec/session/all_spec.rb +5 -0
- data/lib/capybara/spec/session/attach_file_spec.rb +6 -0
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +2 -3
- data/lib/capybara/spec/session/find_spec.rb +0 -6
- data/lib/capybara/spec/session/first_spec.rb +72 -0
- data/lib/capybara/spec/session/has_css_spec.rb +107 -1
- data/lib/capybara/spec/session/has_field_spec.rb +60 -0
- data/lib/capybara/spec/session/has_select_spec.rb +40 -0
- data/lib/capybara/spec/session/javascript.rb +4 -13
- data/lib/capybara/spec/session/within_spec.rb +10 -3
- data/lib/capybara/spec/test_app.rb +20 -1
- data/lib/capybara/spec/views/form.erb +27 -0
- data/lib/capybara/spec/views/with_html.erb +21 -5
- data/lib/capybara/util/save_and_open_page.rb +8 -5
- data/lib/capybara/version.rb +1 -1
- data/spec/basic_node_spec.rb +77 -0
- data/spec/capybara_spec.rb +18 -0
- data/spec/dsl_spec.rb +26 -0
- data/spec/rspec_spec.rb +47 -0
- data/spec/save_and_open_page_spec.rb +83 -7
- data/spec/server_spec.rb +20 -0
- data/spec/session/rack_test_session_spec.rb +10 -0
- data/spec/string_spec.rb +77 -0
- metadata +306 -295
- 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.
|
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
|
-
|
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
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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 =
|
118
|
+
options = extract_normalized_options(args)
|
124
119
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|
-
|
133
|
-
|
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
|
-
|
136
|
-
|
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
|
-
|
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
|
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
|