capybara 2.4.4 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +32 -5
- data/README.md +69 -8
- data/lib/capybara.rb +50 -29
- data/lib/capybara/driver/base.rb +4 -0
- data/lib/capybara/driver/node.rb +4 -0
- data/lib/capybara/helpers.rb +17 -5
- data/lib/capybara/node/actions.rb +16 -11
- data/lib/capybara/node/base.rb +7 -7
- data/lib/capybara/node/document_matchers.rb +1 -1
- data/lib/capybara/node/element.rb +82 -7
- data/lib/capybara/node/finders.rb +62 -22
- data/lib/capybara/node/matchers.rb +3 -3
- data/lib/capybara/node/simple.rb +6 -1
- data/lib/capybara/queries/base_query.rb +1 -1
- data/lib/capybara/queries/current_path_query.rb +58 -0
- data/lib/capybara/queries/text_query.rb +2 -11
- data/lib/capybara/rack_test/browser.rb +7 -2
- data/lib/capybara/rack_test/driver.rb +4 -0
- data/lib/capybara/rack_test/form.rb +2 -1
- data/lib/capybara/rack_test/node.rb +1 -0
- data/lib/capybara/result.rb +2 -2
- data/lib/capybara/rspec.rb +1 -0
- data/lib/capybara/rspec/features.rb +1 -1
- data/lib/capybara/rspec/matchers.rb +42 -3
- data/lib/capybara/selector.rb +7 -2
- data/lib/capybara/selenium/driver.rb +26 -12
- data/lib/capybara/selenium/node.rb +42 -6
- data/lib/capybara/server.rb +1 -1
- data/lib/capybara/session.rb +78 -50
- data/lib/capybara/session/matchers.rb +69 -0
- data/lib/capybara/spec/public/test.js +8 -0
- data/lib/capybara/spec/session/all_spec.rb +5 -0
- data/lib/capybara/spec/session/assert_current_path.rb +59 -0
- data/lib/capybara/spec/session/assert_text.rb +1 -1
- data/lib/capybara/spec/session/attach_file_spec.rb +2 -2
- data/lib/capybara/spec/session/body_spec.rb +2 -0
- data/lib/capybara/spec/session/click_button_spec.rb +17 -8
- data/lib/capybara/spec/session/click_link_spec.rb +32 -1
- data/lib/capybara/spec/session/current_url_spec.rb +5 -0
- data/lib/capybara/spec/session/fill_in_spec.rb +1 -1
- data/lib/capybara/spec/session/find_field_spec.rb +17 -0
- data/lib/capybara/spec/session/find_spec.rb +14 -5
- data/lib/capybara/spec/session/first_spec.rb +24 -0
- data/lib/capybara/spec/session/has_current_path_spec.rb +68 -0
- data/lib/capybara/spec/session/has_link_spec.rb +3 -0
- data/lib/capybara/spec/session/has_text_spec.rb +7 -0
- data/lib/capybara/spec/session/node_spec.rb +45 -6
- data/lib/capybara/spec/session/reset_session_spec.rb +18 -1
- data/lib/capybara/spec/session/save_and_open_page_spec.rb +19 -0
- data/lib/capybara/spec/session/save_page_spec.rb +12 -3
- data/lib/capybara/spec/session/save_screenshot_spec.rb +23 -0
- data/lib/capybara/spec/session/select_spec.rb +12 -0
- data/lib/capybara/spec/session/title_spec.rb +2 -2
- data/lib/capybara/spec/session/window/become_closed_spec.rb +4 -4
- data/lib/capybara/spec/session/window/switch_to_window_spec.rb +8 -0
- data/lib/capybara/spec/session/window/window_opened_by_spec.rb +14 -8
- data/lib/capybara/spec/session/window/window_spec.rb +24 -4
- data/lib/capybara/spec/spec_helper.rb +3 -1
- data/lib/capybara/spec/test_app.rb +10 -1
- data/lib/capybara/spec/views/form.erb +7 -1
- data/lib/capybara/spec/views/path.erb +12 -0
- data/lib/capybara/spec/views/with_html.erb +2 -0
- data/lib/capybara/spec/views/with_js.erb +9 -1
- data/lib/capybara/spec/views/with_title.erb +4 -1
- data/lib/capybara/spec/views/with_windows.erb +2 -2
- data/lib/capybara/version.rb +1 -1
- data/spec/basic_node_spec.rb +1 -0
- data/spec/capybara_spec.rb +12 -3
- data/spec/dsl_spec.rb +18 -6
- data/spec/rack_test_spec.rb +6 -5
- data/spec/rspec/matchers_spec.rb +62 -16
- data/spec/rspec/views_spec.rb +7 -0
- data/spec/selenium_spec.rb +38 -3
- data/spec/selenium_spec_chrome.rb +3 -7
- metadata +13 -4
@@ -0,0 +1,58 @@
|
|
1
|
+
module Capybara
|
2
|
+
# @api private
|
3
|
+
module Queries
|
4
|
+
class CurrentPathQuery < BaseQuery
|
5
|
+
def initialize(expected_path, options = {})
|
6
|
+
@expected_path = expected_path
|
7
|
+
@options = {
|
8
|
+
url: false,
|
9
|
+
only_path: false }.merge(options)
|
10
|
+
assert_valid_keys
|
11
|
+
end
|
12
|
+
|
13
|
+
def resolves_for?(session)
|
14
|
+
@actual_path = if options[:url]
|
15
|
+
session.current_url
|
16
|
+
else
|
17
|
+
if options[:only_path]
|
18
|
+
URI.parse(session.current_url).path
|
19
|
+
else
|
20
|
+
URI.parse(session.current_url).request_uri
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
if @expected_path.is_a? Regexp
|
25
|
+
@actual_path.match(@expected_path)
|
26
|
+
else
|
27
|
+
@expected_path == @actual_path
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def failure_message
|
32
|
+
failure_message_helper
|
33
|
+
end
|
34
|
+
|
35
|
+
def negative_failure_message
|
36
|
+
failure_message_helper(' not')
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def failure_message_helper(negated = '')
|
42
|
+
verb = (@expected_path.is_a?(Regexp))? 'match' : 'equal'
|
43
|
+
"expected #{@actual_path.inspect}#{negated} to #{verb} #{@expected_path.inspect}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def valid_keys
|
47
|
+
[:wait, :url, :only_path]
|
48
|
+
end
|
49
|
+
|
50
|
+
def assert_valid_keys
|
51
|
+
super
|
52
|
+
if options[:url] && options[:only_path]
|
53
|
+
raise ArgumentError, "the :url and :only_path options cannot both be true"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -3,7 +3,7 @@ module Capybara
|
|
3
3
|
module Queries
|
4
4
|
class TextQuery < BaseQuery
|
5
5
|
def initialize(*args)
|
6
|
-
@type = args.
|
6
|
+
@type = (args.first.is_a?(Symbol) || args.first.nil?) ? args.shift : nil
|
7
7
|
@expected_text, @options = args
|
8
8
|
unless @expected_text.is_a?(Regexp)
|
9
9
|
@expected_text = Capybara::Helpers.normalize_whitespace(@expected_text)
|
@@ -11,15 +11,6 @@ module Capybara
|
|
11
11
|
@search_regexp = Capybara::Helpers.to_regexp(@expected_text)
|
12
12
|
@options ||= {}
|
13
13
|
assert_valid_keys
|
14
|
-
|
15
|
-
# this is needed to not break existing tests that may use keys supported by `Query` but not supported by `TextQuery`
|
16
|
-
# can be removed in next minor version (> 2.4)
|
17
|
-
invalid_keys = @options.keys - (COUNT_KEYS + [:wait])
|
18
|
-
unless invalid_keys.empty?
|
19
|
-
invalid_names = invalid_keys.map(&:inspect).join(", ")
|
20
|
-
valid_names = valid_keys.map(&:inspect).join(", ")
|
21
|
-
warn "invalid keys #{invalid_names}, should be one of #{valid_names}"
|
22
|
-
end
|
23
14
|
end
|
24
15
|
|
25
16
|
def resolve_for(node)
|
@@ -49,7 +40,7 @@ module Capybara
|
|
49
40
|
private
|
50
41
|
|
51
42
|
def valid_keys
|
52
|
-
|
43
|
+
COUNT_KEYS + [:wait]
|
53
44
|
end
|
54
45
|
end
|
55
46
|
end
|
@@ -27,7 +27,7 @@ class Capybara::RackTest::Browser
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def follow(method, path, attributes = {})
|
30
|
-
return if path.gsub(/^#{request_path}/, '').start_with?('#')
|
30
|
+
return if path.gsub(/^#{Regexp.escape(request_path)}/, '').start_with?('#') || path.downcase.start_with?('javascript:')
|
31
31
|
process_and_follow_redirects(method, path, attributes, {'HTTP_REFERER' => current_url})
|
32
32
|
end
|
33
33
|
|
@@ -96,7 +96,12 @@ class Capybara::RackTest::Browser
|
|
96
96
|
end
|
97
97
|
|
98
98
|
def title
|
99
|
-
dom.
|
99
|
+
if dom.respond_to? :title
|
100
|
+
dom.title
|
101
|
+
else
|
102
|
+
#old versions of nokogiri don't have #title - remove in 3.0
|
103
|
+
dom.xpath('/html/head/title | /html/title').first.text
|
104
|
+
end
|
100
105
|
end
|
101
106
|
|
102
107
|
protected
|
@@ -86,6 +86,10 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
|
|
86
86
|
@browser = nil
|
87
87
|
end
|
88
88
|
|
89
|
+
def browser_initialized?
|
90
|
+
!@browser.nil?
|
91
|
+
end
|
92
|
+
|
89
93
|
def get(*args, &block); browser.get(*args, &block); end
|
90
94
|
def post(*args, &block); browser.post(*args, &block); end
|
91
95
|
def put(*args, &block); browser.put(*args, &block); end
|
@@ -72,7 +72,8 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
|
|
72
72
|
|
73
73
|
def submit(button)
|
74
74
|
action = (button && button['formaction']) || native['action']
|
75
|
-
|
75
|
+
requset_method = (button && button['formmethod']) || method
|
76
|
+
driver.submit(requset_method, action.to_s, params(button))
|
76
77
|
end
|
77
78
|
|
78
79
|
def multipart?
|
@@ -36,6 +36,7 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def select_option
|
39
|
+
return if disabled?
|
39
40
|
if select_node['multiple'] != 'multiple'
|
40
41
|
select_node.find_xpath(".//option[@selected]").each { |node| node.native.remove_attribute("selected") }
|
41
42
|
end
|
data/lib/capybara/result.rb
CHANGED
@@ -3,7 +3,7 @@ require 'forwardable'
|
|
3
3
|
module Capybara
|
4
4
|
|
5
5
|
##
|
6
|
-
# A {Capybara::Result} represents a collection of {Capybara::Element} on the page. It is possible to interact with this
|
6
|
+
# A {Capybara::Result} represents a collection of {Capybara::Node::Element} on the page. It is possible to interact with this
|
7
7
|
# collection similar to an Array because it implements Enumerable and offers the following Array methods through delegation:
|
8
8
|
#
|
9
9
|
# * []
|
@@ -16,7 +16,7 @@ module Capybara
|
|
16
16
|
# * last()
|
17
17
|
# * empty?()
|
18
18
|
#
|
19
|
-
# @see Capybara::Element
|
19
|
+
# @see Capybara::Node::Element
|
20
20
|
#
|
21
21
|
class Result
|
22
22
|
include Enumerable
|
data/lib/capybara/rspec.rb
CHANGED
@@ -7,6 +7,7 @@ require 'capybara/rspec/features'
|
|
7
7
|
RSpec.configure do |config|
|
8
8
|
config.include Capybara::DSL, :type => :feature
|
9
9
|
config.include Capybara::RSpecMatchers, :type => :feature
|
10
|
+
config.include Capybara::RSpecMatchers, :type => :view
|
10
11
|
|
11
12
|
# A work-around to support accessing the current example that works in both
|
12
13
|
# RSpec 2 and RSpec 3.
|
@@ -11,7 +11,7 @@ if RSpec::Core::Version::STRING.to_f >= 3.0
|
|
11
11
|
config.alias_example_group_to :feature, :capybara_feature => true, :type => :feature
|
12
12
|
config.alias_example_to :scenario
|
13
13
|
config.alias_example_to :xscenario, :skip => "Temporarily disabled with xscenario"
|
14
|
-
|
14
|
+
config.alias_example_to :fscenario, :focus => true
|
15
15
|
end
|
16
16
|
else
|
17
17
|
module Capybara
|
@@ -123,6 +123,41 @@ module Capybara
|
|
123
123
|
alias_method :failure_message_for_should_not, :failure_message_when_negated
|
124
124
|
end
|
125
125
|
|
126
|
+
class HaveCurrentPath < Matcher
|
127
|
+
attr_reader :current_path
|
128
|
+
|
129
|
+
attr_reader :failure_message, :failure_message_when_negated
|
130
|
+
|
131
|
+
def initialize(*args)
|
132
|
+
@args = args
|
133
|
+
|
134
|
+
# are set just for backwards compatability
|
135
|
+
@current_path = args.first
|
136
|
+
end
|
137
|
+
|
138
|
+
def matches?(actual)
|
139
|
+
wrap(actual).assert_current_path(*@args)
|
140
|
+
rescue Capybara::ExpectationNotMet => e
|
141
|
+
@failure_message = e.message
|
142
|
+
return false
|
143
|
+
end
|
144
|
+
|
145
|
+
def does_not_match?(actual)
|
146
|
+
wrap(actual).assert_no_current_path(*@args)
|
147
|
+
rescue Capybara::ExpectationNotMet => e
|
148
|
+
@failure_message_when_negated = e.message
|
149
|
+
return false
|
150
|
+
end
|
151
|
+
|
152
|
+
def description
|
153
|
+
"have current path #{current_path.inspect}"
|
154
|
+
end
|
155
|
+
|
156
|
+
# RSpec 2 compatibility:
|
157
|
+
alias_method :failure_message_for_should, :failure_message
|
158
|
+
alias_method :failure_message_for_should_not, :failure_message_when_negated
|
159
|
+
end
|
160
|
+
|
126
161
|
class BecomeClosed
|
127
162
|
def initialize(options)
|
128
163
|
@wait_time = Capybara::Query.new(options).wait
|
@@ -130,9 +165,9 @@ module Capybara
|
|
130
165
|
|
131
166
|
def matches?(window)
|
132
167
|
@window = window
|
133
|
-
start_time =
|
168
|
+
start_time = Capybara::Helpers.monotonic_time
|
134
169
|
while window.exists?
|
135
|
-
return false if (
|
170
|
+
return false if (Capybara::Helpers.monotonic_time - start_time) > @wait_time
|
136
171
|
sleep 0.05
|
137
172
|
end
|
138
173
|
true
|
@@ -172,6 +207,10 @@ module Capybara
|
|
172
207
|
HaveTitle.new(title, options)
|
173
208
|
end
|
174
209
|
|
210
|
+
def have_current_path(path, options = {})
|
211
|
+
HaveCurrentPath.new(path, options)
|
212
|
+
end
|
213
|
+
|
175
214
|
def have_link(locator, options={})
|
176
215
|
HaveSelector.new(:link, locator, options)
|
177
216
|
end
|
@@ -205,7 +244,7 @@ module Capybara
|
|
205
244
|
# @example
|
206
245
|
# expect(window).to become_closed(wait: 0.8)
|
207
246
|
# @param options [Hash] optional param
|
208
|
-
# @option options [Numeric] :wait (Capybara.
|
247
|
+
# @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum wait time
|
209
248
|
def become_closed(options = {})
|
210
249
|
BecomeClosed.new(options)
|
211
250
|
end
|
data/lib/capybara/selector.rb
CHANGED
@@ -116,6 +116,7 @@ Capybara.add_selector(:field) do
|
|
116
116
|
filter(:checked, boolean: true) { |node, value| not(value ^ node.checked?) }
|
117
117
|
filter(:unchecked, boolean: true) { |node, value| (value ^ node.checked?) }
|
118
118
|
filter(:disabled, default: false, boolean: true) { |node, value| not(value ^ node.disabled?) }
|
119
|
+
filter(:readonly, boolean: true) { |node, value| not(value ^ node[:readonly]) }
|
119
120
|
filter(:with) { |node, with| node.value == with.to_s }
|
120
121
|
filter(:type) do |node, type|
|
121
122
|
if ['textarea', 'select'].include?(type)
|
@@ -150,7 +151,11 @@ end
|
|
150
151
|
Capybara.add_selector(:link) do
|
151
152
|
xpath { |locator| XPath::HTML.link(locator) }
|
152
153
|
filter(:href) do |node, href|
|
153
|
-
|
154
|
+
if href.is_a? Regexp
|
155
|
+
node[:href].match href
|
156
|
+
else
|
157
|
+
node.first(:xpath, XPath.axis(:self)[XPath.attr(:href).equals(href.to_s)], minimum: 0)
|
158
|
+
end
|
154
159
|
end
|
155
160
|
describe { |options| " with href #{options[:href].inspect}" if options[:href] }
|
156
161
|
end
|
@@ -210,7 +215,7 @@ Capybara.add_selector(:select) do
|
|
210
215
|
actual = node.all(:xpath, './/option').map { |option| option.text }
|
211
216
|
options.sort == actual.sort
|
212
217
|
end
|
213
|
-
filter(:with_options) { |node, options| options.all? { |option| node.first(:option, option) } }
|
218
|
+
filter(:with_options) { |node, options| options.all? { |option| node.first(:option, option, minimum: 0) } }
|
214
219
|
filter(:selected) do |node, selected|
|
215
220
|
actual = node.all(:xpath, './/option').select { |option| option.selected? }.map { |option| option.text }
|
216
221
|
[selected].flatten.sort == actual.sort
|
@@ -116,7 +116,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
116
116
|
|
117
117
|
##
|
118
118
|
#
|
119
|
-
# Webdriver supports frame name, id, index(zero-based) or {Capybara::Element} to find iframe
|
119
|
+
# Webdriver supports frame name, id, index(zero-based) or {Capybara::Node::Element} to find iframe
|
120
120
|
#
|
121
121
|
# @overload within_frame(index)
|
122
122
|
# @param [Integer] index index of a frame
|
@@ -126,17 +126,24 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
126
126
|
# @param [Capybara::Node::Base] a_node frame element
|
127
127
|
#
|
128
128
|
def within_frame(frame_handle)
|
129
|
-
@frame_handles[browser.window_handle] ||= []
|
130
129
|
frame_handle = frame_handle.native if frame_handle.is_a?(Capybara::Node::Base)
|
131
|
-
|
132
|
-
|
130
|
+
if !browser.switch_to.respond_to?(:parent_frame)
|
131
|
+
# Selenium Webdriver < 2.43 doesnt support moving back to the parent
|
132
|
+
@frame_handles[browser.window_handle] ||= []
|
133
|
+
@frame_handles[browser.window_handle] << frame_handle
|
134
|
+
end
|
135
|
+
browser.switch_to.frame(frame_handle)
|
133
136
|
yield
|
134
137
|
ensure
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
138
|
+
if browser.switch_to.respond_to?(:parent_frame)
|
139
|
+
browser.switch_to.parent_frame
|
140
|
+
else
|
141
|
+
# There doesnt appear to be any way in Selenium Webdriver < 2.43 to move back to a parent frame
|
142
|
+
# other than going back to the root and then reiterating down
|
143
|
+
@frame_handles[browser.window_handle].pop
|
144
|
+
browser.switch_to.default_content
|
145
|
+
@frame_handles[browser.window_handle].each { |fh| browser.switch_to.frame(fh) }
|
146
|
+
end
|
140
147
|
end
|
141
148
|
|
142
149
|
def current_window_handle
|
@@ -230,13 +237,20 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
230
237
|
end
|
231
238
|
|
232
239
|
def invalid_element_errors
|
233
|
-
[Selenium::WebDriver::Error::StaleElementReferenceError,
|
240
|
+
[Selenium::WebDriver::Error::StaleElementReferenceError,
|
241
|
+
Selenium::WebDriver::Error::UnhandledError,
|
242
|
+
Selenium::WebDriver::Error::ElementNotVisibleError,
|
243
|
+
Selenium::WebDriver::Error::InvalidSelectorError] # Work around a race condition that can occur with chromedriver and #go_back/#go_forward
|
234
244
|
end
|
235
245
|
|
236
246
|
def no_such_window_error
|
237
247
|
Selenium::WebDriver::Error::NoSuchWindowError
|
238
248
|
end
|
239
249
|
|
250
|
+
def browser_initialized?
|
251
|
+
!@browser.nil?
|
252
|
+
end
|
253
|
+
|
240
254
|
private
|
241
255
|
|
242
256
|
def within_given_window(handle)
|
@@ -255,10 +269,10 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
255
269
|
# Selenium has its own built in wait (2 seconds)for a modal to show up, so this wait is really the minimum time
|
256
270
|
# Actual wait time may be longer than specified
|
257
271
|
wait = Selenium::WebDriver::Wait.new(
|
258
|
-
timeout: (options[:wait] || Capybara.
|
272
|
+
timeout: (options[:wait] || Capybara.default_max_wait_time),
|
259
273
|
ignore: Selenium::WebDriver::Error::NoAlertPresentError)
|
260
274
|
begin
|
261
|
-
|
275
|
+
wait.until do
|
262
276
|
alert = @browser.switch_to.alert
|
263
277
|
regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text].to_s)
|
264
278
|
alert.text.match(regexp) ? alert : nil
|
@@ -23,7 +23,7 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
def set(value)
|
26
|
+
def set(value, fill_options={})
|
27
27
|
tag_name = self.tag_name
|
28
28
|
type = self[:type]
|
29
29
|
if (Array === value) && !self[:multiple]
|
@@ -42,9 +42,17 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
42
42
|
elsif value.to_s.empty?
|
43
43
|
native.clear
|
44
44
|
else
|
45
|
-
|
46
|
-
|
47
|
-
|
45
|
+
if fill_options[:clear] == :backspace
|
46
|
+
# Clear field by sending the correct number of backspace keys.
|
47
|
+
backspaces = [:backspace] * self.value.to_s.length
|
48
|
+
native.send_keys(*(backspaces + [value.to_s]))
|
49
|
+
else
|
50
|
+
# Clear field by JavaScript assignment of the value property.
|
51
|
+
# Script can change a readonly element which user input cannot, so
|
52
|
+
# don't execute if readonly.
|
53
|
+
driver.browser.execute_script "arguments[0].value = ''", native
|
54
|
+
native.send_keys(value.to_s)
|
55
|
+
end
|
48
56
|
end
|
49
57
|
elsif native.attribute('isContentEditable')
|
50
58
|
#ensure we are focused on the element
|
@@ -72,15 +80,19 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
72
80
|
def click
|
73
81
|
native.click
|
74
82
|
end
|
75
|
-
|
83
|
+
|
76
84
|
def right_click
|
77
85
|
driver.browser.action.context_click(native).perform
|
78
86
|
end
|
79
|
-
|
87
|
+
|
80
88
|
def double_click
|
81
89
|
driver.browser.action.double_click(native).perform
|
82
90
|
end
|
83
91
|
|
92
|
+
def send_keys(*args)
|
93
|
+
native.send_keys(*args)
|
94
|
+
end
|
95
|
+
|
84
96
|
def hover
|
85
97
|
driver.browser.action.move_to(native).perform
|
86
98
|
end
|
@@ -121,6 +133,30 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
121
133
|
native == other.native
|
122
134
|
end
|
123
135
|
|
136
|
+
def path
|
137
|
+
path = find_xpath('ancestor::*').reverse
|
138
|
+
path.unshift self
|
139
|
+
|
140
|
+
result = []
|
141
|
+
while node = path.shift
|
142
|
+
parent = path.first
|
143
|
+
|
144
|
+
if parent
|
145
|
+
siblings = parent.find_xpath(node.tag_name)
|
146
|
+
if siblings.size == 1
|
147
|
+
result.unshift node.tag_name
|
148
|
+
else
|
149
|
+
index = siblings.index(node)
|
150
|
+
result.unshift "#{node.tag_name}[#{index+1}]"
|
151
|
+
end
|
152
|
+
else
|
153
|
+
result.unshift node.tag_name
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
'/' + result.join('/')
|
158
|
+
end
|
159
|
+
|
124
160
|
private
|
125
161
|
|
126
162
|
# a reference to the select node if this is an option node
|