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