capybara 2.4.4 → 2.5.0
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.
- 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
|