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.
- data/History.txt +43 -1
- data/README.rdoc +168 -98
- data/lib/capybara.rb +77 -15
- data/lib/capybara/driver/base.rb +21 -16
- data/lib/capybara/driver/celerity_driver.rb +39 -41
- data/lib/capybara/driver/culerity_driver.rb +2 -1
- data/lib/capybara/driver/node.rb +66 -0
- data/lib/capybara/driver/rack_test_driver.rb +66 -67
- data/lib/capybara/driver/selenium_driver.rb +43 -47
- data/lib/capybara/dsl.rb +44 -6
- data/lib/capybara/node.rb +185 -24
- data/lib/capybara/node/actions.rb +170 -0
- data/lib/capybara/node/finders.rb +150 -0
- data/lib/capybara/node/matchers.rb +360 -0
- data/lib/capybara/rails.rb +1 -0
- data/lib/capybara/selector.rb +52 -0
- data/lib/capybara/server.rb +68 -87
- data/lib/capybara/session.rb +221 -207
- data/lib/capybara/spec/driver.rb +45 -35
- data/lib/capybara/spec/public/test.js +1 -1
- data/lib/capybara/spec/session.rb +28 -53
- data/lib/capybara/spec/session/all_spec.rb +7 -3
- data/lib/capybara/spec/session/check_spec.rb +50 -52
- data/lib/capybara/spec/session/click_button_spec.rb +9 -0
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +37 -0
- data/lib/capybara/spec/session/current_url_spec.rb +7 -0
- data/lib/capybara/spec/session/find_button_spec.rb +4 -2
- data/lib/capybara/spec/session/find_by_id_spec.rb +4 -2
- data/lib/capybara/spec/session/find_field_spec.rb +7 -3
- data/lib/capybara/spec/session/find_link_spec.rb +5 -3
- data/lib/capybara/spec/session/find_spec.rb +71 -6
- data/lib/capybara/spec/session/has_field_spec.rb +1 -1
- data/lib/capybara/spec/session/has_selector_spec.rb +129 -0
- data/lib/capybara/spec/session/has_xpath_spec.rb +4 -4
- data/lib/capybara/spec/session/javascript.rb +25 -5
- data/lib/capybara/spec/session/select_spec.rb +16 -2
- data/lib/capybara/spec/session/unselect_spec.rb +8 -1
- data/lib/capybara/spec/session/within_spec.rb +5 -5
- data/lib/capybara/spec/views/form.erb +65 -1
- data/lib/capybara/spec/views/popup_one.erb +8 -0
- data/lib/capybara/spec/views/popup_two.erb +8 -0
- data/lib/capybara/spec/views/with_html.erb +5 -0
- data/lib/capybara/spec/views/within_popups.erb +25 -0
- data/lib/capybara/{save_and_open_page.rb → util/save_and_open_page.rb} +3 -3
- data/lib/capybara/util/timeout.rb +27 -0
- data/lib/capybara/version.rb +1 -1
- data/spec/capybara_spec.rb +18 -8
- data/spec/driver/celerity_driver_spec.rb +10 -14
- data/spec/driver/culerity_driver_spec.rb +4 -3
- data/spec/driver/rack_test_driver_spec.rb +39 -2
- data/spec/driver/remote_culerity_driver_spec.rb +5 -7
- data/spec/driver/remote_selenium_driver_spec.rb +7 -10
- data/spec/driver/selenium_driver_spec.rb +3 -2
- data/spec/dsl_spec.rb +5 -14
- data/spec/save_and_open_page_spec.rb +19 -19
- data/spec/server_spec.rb +22 -10
- data/spec/session/celerity_session_spec.rb +17 -21
- data/spec/session/culerity_session_spec.rb +3 -3
- data/spec/session/rack_test_session_spec.rb +2 -2
- data/spec/session/selenium_session_spec.rb +2 -2
- data/spec/spec_helper.rb +27 -6
- data/spec/{wait_until_spec.rb → timeout_spec.rb} +14 -14
- metadata +88 -46
- data/lib/capybara/searchable.rb +0 -54
- data/lib/capybara/spec/session/click_spec.rb +0 -24
- data/lib/capybara/spec/session/locate_spec.rb +0 -65
- data/lib/capybara/wait_until.rb +0 -28
- data/lib/capybara/xpath.rb +0 -179
- data/spec/searchable_spec.rb +0 -66
- 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
|
-
|
6
|
+
native.text
|
7
7
|
end
|
8
8
|
|
9
9
|
def [](name)
|
10
10
|
if name == :value
|
11
|
-
|
11
|
+
native.value
|
12
12
|
else
|
13
|
-
|
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
|
-
|
21
|
+
native.find_elements(:xpath, ".//option").select { |n| n.selected? }.map { |n| n.value || n.text }
|
22
22
|
else
|
23
|
-
|
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
|
-
|
29
|
+
native.click
|
30
30
|
elsif tag_name == 'input' and type == 'checkbox'
|
31
|
-
|
31
|
+
native.click if value ^ native.attribute('checked').to_s.eql?("true")
|
32
32
|
elsif tag_name == 'textarea' or tag_name == 'input'
|
33
|
-
|
34
|
-
|
33
|
+
native.clear
|
34
|
+
native.send_keys(value.to_s)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
def
|
39
|
-
|
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
|
47
|
-
if
|
48
|
-
raise Capybara::UnselectNotAllowed, "Cannot unselect option
|
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
|
-
|
50
|
+
native.click
|
62
51
|
end
|
63
52
|
|
64
53
|
def drag_to(element)
|
65
|
-
|
54
|
+
native.drag_and_drop_on(element.native)
|
66
55
|
end
|
67
56
|
|
68
57
|
def tag_name
|
69
|
-
|
58
|
+
native.tag_name
|
70
59
|
end
|
71
60
|
|
72
61
|
def visible?
|
73
|
-
|
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
|
-
|
79
|
-
|
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
|
91
|
-
unless @
|
92
|
-
@
|
84
|
+
def browser
|
85
|
+
unless @browser
|
86
|
+
@browser = Selenium::WebDriver.for(options.delete(:browser) || :firefox, options)
|
93
87
|
at_exit do
|
94
|
-
@
|
88
|
+
@browser.quit
|
95
89
|
end
|
96
90
|
end
|
97
|
-
@
|
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
|
137
|
-
|
138
|
-
|
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)
|
data/lib/capybara/dsl.rb
CHANGED
@@ -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
|
-
|
31
|
-
|
32
|
-
|
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.
|
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
|
data/lib/capybara/node.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
89
|
+
base.text
|
14
90
|
end
|
15
91
|
|
16
|
-
|
17
|
-
|
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
|
-
|
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
|
-
|
120
|
+
base.set(value)
|
26
121
|
end
|
27
122
|
|
28
|
-
|
29
|
-
|
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
|
-
|
33
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|