capybara 2.15.4 → 2.16.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 +5 -5
- data/History.md +17 -1
- data/README.md +12 -8
- data/lib/capybara/config.rb +2 -1
- data/lib/capybara/driver/base.rb +4 -0
- data/lib/capybara/minitest/spec.rb +3 -3
- data/lib/capybara/node/actions.rb +8 -8
- data/lib/capybara/node/finders.rb +1 -1
- data/lib/capybara/queries/current_path_query.rb +11 -8
- data/lib/capybara/queries/selector_query.rb +2 -3
- data/lib/capybara/queries/text_query.rb +1 -1
- data/lib/capybara/selenium/driver.rb +28 -13
- data/lib/capybara/selenium/node.rb +9 -0
- data/lib/capybara/session.rb +18 -0
- data/lib/capybara/session/matchers.rb +15 -7
- data/lib/capybara/spec/public/test.js +8 -0
- data/lib/capybara/spec/session/accept_prompt_spec.rb +22 -0
- data/lib/capybara/spec/session/evaluate_async_script_spec.rb +22 -0
- data/lib/capybara/spec/session/evaluate_script_spec.rb +1 -1
- data/lib/capybara/spec/session/has_current_path_spec.rb +41 -4
- data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
- data/lib/capybara/spec/views/with_js.erb +4 -0
- data/lib/capybara/version.rb +1 -1
- data/spec/selenium_spec_marionette.rb +2 -2
- data/spec/shared_selenium_session.rb +24 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ee26f641aae1c298d4f5c3d7e8a98376c133c1e2d6ac90b3a3a274ae0dddd13f
|
4
|
+
data.tar.gz: 12a92103cb8b03439ba345d2433385d5d8ec698713c1fda8d8d496c1d7a5db4a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b63bffec8f36f684af468659992edc8368844838a7f92f0cacaca2cfb070c8d5d62baebc6308dc60bac7646a0e4a7221eca416014cbdc2a0f82ce189dedc1172
|
7
|
+
data.tar.gz: 26d2d8319225c85ef3ea5c1c546f321a92063adcf08a97ed1e530782e7af18aa9c7b2e8c59faaac2bf02c5a3d0d7d37d82a124dc1a0472605296745fe26f2ca8
|
data/History.md
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
# Version 2.16.0
|
2
|
+
Release date: 2017-11-13
|
3
|
+
|
4
|
+
### Added
|
5
|
+
|
6
|
+
* Attempt to move element into view when selenium doesn't correctly do it - See PR #1917 [Thomas Walpole]
|
7
|
+
* `current_path` matchers will now autodetect path vs url based on string to be matched. Deprecates
|
8
|
+
`:only_path` in favor of `:ignore_query` option [Thomas Walpole]
|
9
|
+
* Session#evaluate_async_script [Thomas Walpole]
|
10
|
+
|
11
|
+
### Fixed
|
12
|
+
|
13
|
+
* Default prompt value when using headless Chrome works correctly [Thomas Walpole]
|
14
|
+
* Support new modal error returned by selenium-webdriver 3.7 for W3C drivers [Thomas Walpole]
|
15
|
+
* Calling `respond_to?` on the object passed to `Capybara.configure` block - Issue #1935
|
16
|
+
|
1
17
|
# Version 2.15.4
|
2
18
|
Release date: 2017-10-07
|
3
19
|
|
@@ -235,7 +251,7 @@ Release date: 2016-09-19
|
|
235
251
|
|
236
252
|
Release date: 2016-08-25
|
237
253
|
|
238
|
-
###Fixed
|
254
|
+
### Fixed
|
239
255
|
|
240
256
|
* Fixed error message from have_text when text is not found but contains regex special characters [Ryunosuke Sato]
|
241
257
|
* Warn when :exact option is passed that has no effect [Thomas Walpole]
|
data/README.md
CHANGED
@@ -549,7 +549,7 @@ If you find yourself needing to use this a lot you may be better off adding a [c
|
|
549
549
|
```ruby
|
550
550
|
find_field('First Name'){ |el| el['data-xyz'] == '123' }
|
551
551
|
find("#img_loading"){ |img| img['complete'] == true }
|
552
|
-
|
552
|
+
```
|
553
553
|
|
554
554
|
**Note**: `find` will wait for an element to appear on the page, as explained in the
|
555
555
|
Ajax section. If the element does not appear it will raise an error.
|
@@ -894,7 +894,7 @@ To permanently switch the current session to a different session
|
|
894
894
|
|
895
895
|
```ruby
|
896
896
|
Capybara.session_name = "some other session"
|
897
|
-
|
897
|
+
```
|
898
898
|
|
899
899
|
### <a name="using-sessions-manually"></a>Using sessions manually
|
900
900
|
|
@@ -1053,7 +1053,9 @@ In normal mode most of Capybara's configuration options are global settings whic
|
|
1053
1053
|
if using multiple sessions and wanting to change a setting for only one of the sessions. To provide
|
1054
1054
|
support for this type of usage Capybara now provides a "threadsafe" mode which can be enabled by setting
|
1055
1055
|
|
1056
|
-
|
1056
|
+
```ruby
|
1057
|
+
Capybara.threadsafe = true
|
1058
|
+
```
|
1057
1059
|
|
1058
1060
|
This setting can only be changed before any sessions have been created. In "threadsafe" mode the following
|
1059
1061
|
behaviors of Capybara change
|
@@ -1063,11 +1065,13 @@ behaviors of Capybara change
|
|
1063
1065
|
`app`, `reuse_server`, `default_driver`, `javascript_driver`, and (obviously) `threadsafe`. Any drivers and servers
|
1064
1066
|
registered through `register_driver` and `register_server` are also global.
|
1065
1067
|
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1068
|
+
```ruby
|
1069
|
+
my_session = Capybara::Session.new(:driver, some_app) do |config|
|
1070
|
+
config.automatic_label_click = true # only set for my_session
|
1071
|
+
end
|
1072
|
+
my_session.config.default_max_wait_time = 10 # only set for my_session
|
1073
|
+
Capybara.default_max_wait_time = 2 # will not change the default_max_wait in my_session
|
1074
|
+
```
|
1071
1075
|
|
1072
1076
|
* `current_driver` and `session_name` are thread specific. This means that `using_session` and
|
1073
1077
|
`using_driver` also only affect the current thread.
|
data/lib/capybara/config.rb
CHANGED
@@ -108,6 +108,7 @@ module Capybara
|
|
108
108
|
end
|
109
109
|
end
|
110
110
|
|
111
|
+
# @api private
|
111
112
|
class ConfigureDeprecator
|
112
113
|
def initialize(config)
|
113
114
|
@config = config
|
@@ -125,7 +126,7 @@ module Capybara
|
|
125
126
|
end
|
126
127
|
|
127
128
|
def respond_to_missing?(m, include_private = false)
|
128
|
-
@config.
|
129
|
+
@config.respond_to?(m) || Capybara.respond_to?(m) || super
|
129
130
|
end
|
130
131
|
end
|
131
132
|
end
|
data/lib/capybara/driver/base.rb
CHANGED
@@ -42,6 +42,10 @@ class Capybara::Driver::Base
|
|
42
42
|
raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#evaluate_script'
|
43
43
|
end
|
44
44
|
|
45
|
+
def evaluate_async_script(script, *args)
|
46
|
+
raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#evaluate_script_asnyc'
|
47
|
+
end
|
48
|
+
|
45
49
|
def save_screenshot(path, options={})
|
46
50
|
raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#save_screenshot'
|
47
51
|
end
|
@@ -8,13 +8,13 @@ module Capybara
|
|
8
8
|
infect_an_assertion "refute_#{assertion}", "wont_have_#{assertion}", :reverse
|
9
9
|
end
|
10
10
|
|
11
|
-
(%w(selector xpath css link button field select table checked_field unchecked_field).
|
11
|
+
(%w(selector xpath css link button field select table checked_field unchecked_field).flat_map do |assertion|
|
12
12
|
[["assert_#{assertion}", "must_have_#{assertion}"],
|
13
13
|
["refute_#{assertion}", "wont_have_#{assertion}"]]
|
14
|
-
end
|
14
|
+
end + %w(selector xpath css).flat_map do |assertion|
|
15
15
|
[["assert_matches_#{assertion}", "must_match_#{assertion}"],
|
16
16
|
["refute_matches_#{assertion}", "wont_match_#{assertion}"]]
|
17
|
-
end
|
17
|
+
end).each do |(meth, new_name)|
|
18
18
|
self.class_eval <<-EOM, __FILE__, __LINE__ + 1
|
19
19
|
def #{new_name} *args, &block
|
20
20
|
::Minitest::Expectation.new(self, ::Minitest::Spec.current).#{new_name}(*args, &block)
|
@@ -5,8 +5,8 @@ module Capybara
|
|
5
5
|
|
6
6
|
##
|
7
7
|
#
|
8
|
-
# Finds a button or link
|
9
|
-
#
|
8
|
+
# Finds a button or link and clicks it. See {Capybara::Node::Actions#click_button} and
|
9
|
+
# {Capybara::Node::Actions#click_link} for what locator will match against for each type of element
|
10
10
|
# @!macro waiting_behavior
|
11
11
|
# If the driver is capable of executing JavaScript, +$0+ will wait for a set amount of time
|
12
12
|
# and continuously retry finding the element until either the element is found or the time
|
@@ -16,7 +16,7 @@ module Capybara
|
|
16
16
|
#
|
17
17
|
# @overload click_link_or_button([locator], options)
|
18
18
|
#
|
19
|
-
# @param [String] locator
|
19
|
+
# @param [String] locator See {Capybara::Node::Actions#click_button} and {Capybara::Node::Actions#click_link}
|
20
20
|
#
|
21
21
|
# @return [Capybara::Node::Element] The element clicked
|
22
22
|
#
|
@@ -80,7 +80,7 @@ module Capybara
|
|
80
80
|
# @option options [String] :id Match fields that match the id attribute
|
81
81
|
# @option options [String] :name Match fields that match the name attribute
|
82
82
|
# @option options [String] :placeholder Match fields that match the placeholder attribute
|
83
|
-
# @option options [String, Array<String>] :class Match
|
83
|
+
# @option options [String, Array<String>] :class Match fields that match the class(es) provided
|
84
84
|
#
|
85
85
|
# @return [Capybara::Node::Element] The element filled_in
|
86
86
|
def fill_in(locator, options={})
|
@@ -108,7 +108,7 @@ module Capybara
|
|
108
108
|
# @option options [String] :option Value of the radio_button to choose
|
109
109
|
# @option options [String] :id Match fields that match the id attribute
|
110
110
|
# @option options [String] :name Match fields that match the name attribute
|
111
|
-
# @option options [String, Array<String>] :class Match
|
111
|
+
# @option options [String, Array<String>] :class Match fields that match the class(es) provided
|
112
112
|
# @macro waiting_behavior
|
113
113
|
# @macro label_click
|
114
114
|
#
|
@@ -131,7 +131,7 @@ module Capybara
|
|
131
131
|
# @option options [String] :option Value of the checkbox to select
|
132
132
|
# @option options [String] id Match fields that match the id attribute
|
133
133
|
# @option options [String] name Match fields that match the name attribute
|
134
|
-
# @option options [String, Array<String>] :class Match
|
134
|
+
# @option options [String, Array<String>] :class Match fields that match the class(es) provided
|
135
135
|
# @macro label_click
|
136
136
|
# @macro waiting_behavior
|
137
137
|
#
|
@@ -154,7 +154,7 @@ module Capybara
|
|
154
154
|
# @option options [String] :option Value of the checkbox to deselect
|
155
155
|
# @option options [String] id Match fields that match the id attribute
|
156
156
|
# @option options [String] name Match fields that match the name attribute
|
157
|
-
# @option options [String, Array<String>] :class Match
|
157
|
+
# @option options [String, Array<String>] :class Match fields that match the class(es) provided
|
158
158
|
# @macro label_click
|
159
159
|
# @macro waiting_behavior
|
160
160
|
#
|
@@ -229,7 +229,7 @@ module Capybara
|
|
229
229
|
# @option options [Boolean] multiple Match field which allows multiple file selection
|
230
230
|
# @option options [String] id Match fields that match the id attribute
|
231
231
|
# @option options [String] name Match fields that match the name attribute
|
232
|
-
# @option options [String, Array<String>] :class Match
|
232
|
+
# @option options [String, Array<String>] :class Match fields that match the class(es) provided
|
233
233
|
# @option options [true, Hash] make_visible A Hash of CSS styles to change before attempting to attach the file, if `true` { opacity: 1, display: 'block', visibility: 'visible' } is used (may not be supported by all drivers)
|
234
234
|
#
|
235
235
|
# @return [Capybara::Node::Element] The file field element
|
@@ -171,7 +171,7 @@ module Capybara
|
|
171
171
|
# @option options [String] id Match buttons with the id provided
|
172
172
|
# @option options [String] title Match buttons with the title provided
|
173
173
|
# @option options [String] value Match buttons with the value provided
|
174
|
-
# @option options [String, Array<String>] class Match
|
174
|
+
# @option options [String, Array<String>] class Match buttons that match the class(es) provided
|
175
175
|
# @return [Capybara::Node::Element] The found element
|
176
176
|
#
|
177
177
|
def find_button(locator=nil, options={}, &optional_filter_block)
|
@@ -8,22 +8,25 @@ module Capybara
|
|
8
8
|
def initialize(expected_path, options = {})
|
9
9
|
super(options)
|
10
10
|
@expected_path = expected_path
|
11
|
+
warn "DEPRECATED: The :only_path option is deprecated in favor of the :ignore_query option" if options.has_key?(:only_path)
|
12
|
+
|
11
13
|
@options = {
|
12
|
-
url:
|
13
|
-
only_path: false
|
14
|
+
url: !@expected_path.is_a?(Regexp) && !::Addressable::URI.parse(@expected_path || "").hostname.nil?,
|
15
|
+
only_path: false,
|
16
|
+
ignore_query: false }.merge(options)
|
14
17
|
assert_valid_keys
|
15
18
|
end
|
16
19
|
|
17
20
|
def resolves_for?(session)
|
21
|
+
uri = ::Addressable::URI.parse(session.current_url)
|
22
|
+
uri.query = nil if uri && options[:ignore_query]
|
18
23
|
@actual_path = if options[:url]
|
19
|
-
|
24
|
+
uri.to_s
|
20
25
|
else
|
21
|
-
uri = ::Addressable::URI.parse(session.current_url)
|
22
|
-
|
23
26
|
if options[:only_path]
|
24
|
-
uri
|
27
|
+
uri && uri.path
|
25
28
|
else
|
26
|
-
uri
|
29
|
+
uri && uri.request_uri
|
27
30
|
end
|
28
31
|
end
|
29
32
|
|
@@ -50,7 +53,7 @@ module Capybara
|
|
50
53
|
end
|
51
54
|
|
52
55
|
def valid_keys
|
53
|
-
[:wait, :url, :only_path]
|
56
|
+
[:wait, :url, :only_path, :ignore_query]
|
54
57
|
end
|
55
58
|
|
56
59
|
def assert_valid_keys
|
@@ -17,7 +17,6 @@ module Capybara
|
|
17
17
|
if args[0].is_a?(Symbol)
|
18
18
|
@selector = Selector.all.fetch(args.shift) do |selector_type|
|
19
19
|
raise ArgumentError, "Unknown selector type (:#{selector_type})"
|
20
|
-
nil
|
21
20
|
end
|
22
21
|
@locator = args.shift
|
23
22
|
else
|
@@ -26,7 +25,7 @@ module Capybara
|
|
26
25
|
end
|
27
26
|
@selector ||= Selector.all[session_options.default_selector]
|
28
27
|
|
29
|
-
warn "Unused parameters passed to #{self.class.name} : #{args
|
28
|
+
warn "Unused parameters passed to #{self.class.name} : #{args}" unless args.empty?
|
30
29
|
|
31
30
|
# for compatibility with Capybara 2.0
|
32
31
|
if session_options.exact_options and @selector == Selector.all[:option]
|
@@ -235,7 +234,7 @@ module Capybara
|
|
235
234
|
|
236
235
|
def warn_exact_usage
|
237
236
|
if options.has_key?(:exact) && !supports_exact?
|
238
|
-
warn "The :exact option only has an effect on queries using the XPath#is method. Using it with the query \"#{expression
|
237
|
+
warn "The :exact option only has an effect on queries using the XPath#is method. Using it with the query \"#{expression}\" has no effect."
|
239
238
|
end
|
240
239
|
end
|
241
240
|
|
@@ -16,7 +16,7 @@ module Capybara
|
|
16
16
|
@expected_text = Capybara::Helpers.normalize_whitespace(@expected_text)
|
17
17
|
end
|
18
18
|
@search_regexp = Capybara::Helpers.to_regexp(@expected_text, nil, exact?)
|
19
|
-
warn "Unused parameters passed to #{self.class.name} : #{args
|
19
|
+
warn "Unused parameters passed to #{self.class.name} : #{args}" unless args.empty?
|
20
20
|
assert_valid_keys
|
21
21
|
end
|
22
22
|
|
@@ -24,7 +24,6 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
24
24
|
|
25
25
|
@w3c = ((defined?(Selenium::WebDriver::Remote::W3CCapabilities) && @browser.capabilities.is_a?(Selenium::WebDriver::Remote::W3CCapabilities)) ||
|
26
26
|
(defined?(Selenium::WebDriver::Remote::W3C::Capabilities) && @browser.capabilities.is_a?(Selenium::WebDriver::Remote::W3C::Capabilities)))
|
27
|
-
|
28
27
|
main = Process.pid
|
29
28
|
at_exit do
|
30
29
|
# Store the exit status of the test run since it goes away after calling the at_exit proc...
|
@@ -114,6 +113,12 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
114
113
|
unwrap_script_result(result)
|
115
114
|
end
|
116
115
|
|
116
|
+
def evaluate_async_script(script, *args)
|
117
|
+
browser.manage.timeouts.script_timeout = Capybara.default_max_wait_time
|
118
|
+
result = browser.execute_async_script(script, *args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg} )
|
119
|
+
unwrap_script_result(result)
|
120
|
+
end
|
121
|
+
|
117
122
|
def save_screenshot(path, _options={})
|
118
123
|
browser.save_screenshot(path)
|
119
124
|
end
|
@@ -164,7 +169,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
164
169
|
begin
|
165
170
|
@browser.switch_to.alert.accept
|
166
171
|
sleep 0.25 # allow time for the modal to be handled
|
167
|
-
rescue
|
172
|
+
rescue modal_error
|
168
173
|
# The alert is now gone - nothing to do
|
169
174
|
end
|
170
175
|
# try cleaning up the browser again
|
@@ -246,13 +251,15 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
246
251
|
def accept_modal(_type, options={})
|
247
252
|
if headless_chrome?
|
248
253
|
raise ArgumentError, "Block that triggers the system modal is missing" unless block_given?
|
249
|
-
insert_modal_handlers(true, options[:with]
|
254
|
+
insert_modal_handlers(true, options[:with])
|
250
255
|
yield
|
251
256
|
find_headless_modal(options)
|
252
257
|
else
|
253
258
|
yield if block_given?
|
254
259
|
modal = find_modal(options)
|
260
|
+
|
255
261
|
modal.send_keys options[:with] if options[:with]
|
262
|
+
|
256
263
|
message = modal.text
|
257
264
|
modal.accept
|
258
265
|
message
|
@@ -262,7 +269,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
262
269
|
def dismiss_modal(_type, options={})
|
263
270
|
if headless_chrome?
|
264
271
|
raise ArgumentError, "Block that triggers the system modal is missing" unless block_given?
|
265
|
-
insert_modal_handlers(false, options[:with]
|
272
|
+
insert_modal_handlers(false, options[:with])
|
266
273
|
yield
|
267
274
|
find_headless_modal(options)
|
268
275
|
else
|
@@ -342,6 +349,14 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
342
349
|
options[:browser].to_s
|
343
350
|
end
|
344
351
|
|
352
|
+
def modal_error
|
353
|
+
if defined?(Selenium::WebDriver::Error::NoSuchAlertError)
|
354
|
+
Selenium::WebDriver::Error::NoSuchAlertError
|
355
|
+
else
|
356
|
+
Selenium::WebDriver::Error::NoAlertPresentError
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
345
360
|
def find_window(locator)
|
346
361
|
handles = browser.window_handles
|
347
362
|
return locator if handles.include? locator
|
@@ -359,7 +374,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
359
374
|
raise Capybara::ElementNotFound, "Could not find a window identified by #{locator}"
|
360
375
|
end
|
361
376
|
|
362
|
-
def insert_modal_handlers(accept, response_text
|
377
|
+
def insert_modal_handlers(accept, response_text)
|
363
378
|
script = <<-JS
|
364
379
|
if (typeof window.capybara === 'undefined') {
|
365
380
|
window.capybara = {
|
@@ -391,13 +406,13 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
391
406
|
}
|
392
407
|
window.capybara.add_handler(modal_handler);
|
393
408
|
|
394
|
-
window.alert = window.confirm = function(str) {
|
395
|
-
window.capybara.handler_called(modal_handler, str);
|
409
|
+
window.alert = window.confirm = function(str = "") {
|
410
|
+
window.capybara.handler_called(modal_handler, str.toString());
|
396
411
|
return #{accept ? 'true' : 'false'};
|
397
|
-
}
|
398
|
-
window.prompt = function(str) {
|
399
|
-
window.capybara.handler_called(modal_handler, str);
|
400
|
-
return #{accept ? "'#{response_text}'" : 'null'};
|
412
|
+
}
|
413
|
+
window.prompt = function(str = "", default_text = "") {
|
414
|
+
window.capybara.handler_called(modal_handler, str.toString());
|
415
|
+
return #{accept ? (response_text.nil? ? "default_text" : "'#{response_text}'") : 'null'};
|
401
416
|
}
|
402
417
|
JS
|
403
418
|
execute_script script
|
@@ -420,7 +435,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
420
435
|
# Actual wait time may be longer than specified
|
421
436
|
wait = Selenium::WebDriver::Wait.new(
|
422
437
|
timeout: options.fetch(:wait, session_options.default_max_wait_time) || 0 ,
|
423
|
-
ignore:
|
438
|
+
ignore: modal_error)
|
424
439
|
begin
|
425
440
|
wait.until do
|
426
441
|
alert = @browser.switch_to.alert
|
@@ -437,7 +452,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
437
452
|
# Actual wait time may be longer than specified
|
438
453
|
wait = Selenium::WebDriver::Wait.new(
|
439
454
|
timeout: options.fetch(:wait, session_options.default_max_wait_time) || 0 ,
|
440
|
-
ignore:
|
455
|
+
ignore: modal_error)
|
441
456
|
begin
|
442
457
|
wait.until do
|
443
458
|
called, alert_text = evaluate_script('window.capybara && window.capybara.current_modal_status()')
|
@@ -100,6 +100,15 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
100
100
|
|
101
101
|
def click
|
102
102
|
native.click
|
103
|
+
rescue => e
|
104
|
+
if e.is_a?(::Selenium::WebDriver::Error::ElementClickInterceptedError) ||
|
105
|
+
e.message =~ /Other element would receive the click/
|
106
|
+
begin
|
107
|
+
driver.execute_script("arguments[0].scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'})", self)
|
108
|
+
rescue
|
109
|
+
end
|
110
|
+
end
|
111
|
+
raise e
|
103
112
|
end
|
104
113
|
|
105
114
|
def right_click
|
data/lib/capybara/session.rb
CHANGED
@@ -642,6 +642,24 @@ module Capybara
|
|
642
642
|
element_script_result(result)
|
643
643
|
end
|
644
644
|
|
645
|
+
##
|
646
|
+
#
|
647
|
+
# Evaluate the given JavaScript and obtain the result from a callback function which will be passed as the last argument to the script.
|
648
|
+
#
|
649
|
+
# @param [String] script A string of JavaScript to evaluate
|
650
|
+
# @return [Object] The result of the evaluated JavaScript (may be driver specific)
|
651
|
+
#
|
652
|
+
def evaluate_async_script(script, *args)
|
653
|
+
@touched = true
|
654
|
+
result = if args.empty?
|
655
|
+
driver.evaluate_async_script(script)
|
656
|
+
else
|
657
|
+
raise Capybara::NotSupportedByDriverError, "The current driver does not support evaluate_async_script arguments" if driver.method(:evaluate_async_script).arity == 1
|
658
|
+
driver.evaluate_async_script(script, *args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg} )
|
659
|
+
end
|
660
|
+
element_script_result(result)
|
661
|
+
end
|
662
|
+
|
645
663
|
##
|
646
664
|
#
|
647
665
|
# Execute the block, accepting a alert.
|
@@ -3,16 +3,18 @@ module Capybara
|
|
3
3
|
module SessionMatchers
|
4
4
|
##
|
5
5
|
# Asserts that the page has the given path.
|
6
|
-
# By default this will compare against the
|
6
|
+
# By default, if passed a full url this will compare against the full url,
|
7
|
+
# if passed a path only the path+query portion will be compared, if passed a regexp
|
8
|
+
# the comparison will depend on the :url option
|
7
9
|
#
|
8
10
|
# @!macro current_path_query_params
|
9
11
|
# @overload $0(string, options = {})
|
10
12
|
# @param string [String] The string that the current 'path' should equal
|
11
13
|
# @overload $0(regexp, options = {})
|
12
14
|
# @param regexp [Regexp] The regexp that the current 'path' should match to
|
13
|
-
# @option options [
|
14
|
-
# @option options [Boolean] :
|
15
|
-
# @option options [
|
15
|
+
# @option options [Boolean] :url (true if `string` ia a full url, otherwise false) Whether the compare should be done against the full current url or just the path
|
16
|
+
# @option options [Boolean] :ignore_query (false) Whether the query portion of the current url/path should be ignored
|
17
|
+
# @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time that Capybara will wait for the current url/path to eq/match given string/regexp argument
|
16
18
|
# @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
|
17
19
|
# @return [true]
|
18
20
|
#
|
@@ -22,7 +24,9 @@ module Capybara
|
|
22
24
|
|
23
25
|
##
|
24
26
|
# Asserts that the page doesn't have the given path.
|
25
|
-
# By default this will compare against the
|
27
|
+
# By default, if passed a full url this will compare against the full url,
|
28
|
+
# if passed a path only the path+query portion will be compared, if passed a regexp
|
29
|
+
# the comparison will depend on the :url option
|
26
30
|
#
|
27
31
|
# @macro current_path_query_params
|
28
32
|
# @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
|
@@ -34,7 +38,9 @@ module Capybara
|
|
34
38
|
|
35
39
|
##
|
36
40
|
# Checks if the page has the given path.
|
37
|
-
# By default this will compare against the
|
41
|
+
# By default, if passed a full url this will compare against the full url,
|
42
|
+
# if passed a path only the path+query portion will be compared, if passed a regexp
|
43
|
+
# the comparison will depend on the :url option
|
38
44
|
#
|
39
45
|
# @macro current_path_query_params
|
40
46
|
# @return [Boolean]
|
@@ -47,7 +53,9 @@ module Capybara
|
|
47
53
|
|
48
54
|
##
|
49
55
|
# Checks if the page doesn't have the given path.
|
50
|
-
# By default this will compare against the
|
56
|
+
# By default, if passed a full url this will compare against the full url,
|
57
|
+
# if passed a path only the path+query portion will be compared, if passed a regexp
|
58
|
+
# the comparison will depend on the :url option
|
51
59
|
#
|
52
60
|
# @macro current_path_query_params
|
53
61
|
# @return [Boolean]
|
@@ -109,6 +109,14 @@ $(function() {
|
|
109
109
|
$(this).attr('response', response);
|
110
110
|
}
|
111
111
|
});
|
112
|
+
$('#open-prompt-with-default').click(function() {
|
113
|
+
var response = prompt('Prompt opened', 'Default value!');
|
114
|
+
if(response === null) {
|
115
|
+
$(this).attr('response', 'dismissed');
|
116
|
+
} else {
|
117
|
+
$(this).attr('response', response);
|
118
|
+
}
|
119
|
+
});
|
112
120
|
$('#open-twice').click(function() {
|
113
121
|
if (confirm('Are you sure?')) {
|
114
122
|
if (!confirm('Are you really sure?')) {
|
@@ -11,6 +11,13 @@ Capybara::SpecHelper.spec '#accept_prompt', requires: [:modals] do
|
|
11
11
|
expect(@session).to have_xpath("//a[@id='open-prompt' and @response='']")
|
12
12
|
end
|
13
13
|
|
14
|
+
it "should accept the prompt with no message when there is a default" do
|
15
|
+
@session.accept_prompt do
|
16
|
+
@session.click_link('Open defaulted prompt')
|
17
|
+
end
|
18
|
+
expect(@session).to have_xpath("//a[@id='open-prompt-with-default' and @response='Default value!']")
|
19
|
+
end
|
20
|
+
|
14
21
|
it "should return the message presented" do
|
15
22
|
message = @session.accept_prompt do
|
16
23
|
@session.click_link('Open prompt')
|
@@ -25,6 +32,21 @@ Capybara::SpecHelper.spec '#accept_prompt', requires: [:modals] do
|
|
25
32
|
expect(@session).to have_xpath("//a[@id='open-prompt' and @response='the response']")
|
26
33
|
end
|
27
34
|
|
35
|
+
it "should accept the prompt with a response when there is a default" do
|
36
|
+
@session.accept_prompt with: 'the response' do
|
37
|
+
@session.click_link('Open defaulted prompt')
|
38
|
+
end
|
39
|
+
expect(@session).to have_xpath("//a[@id='open-prompt-with-default' and @response='the response']")
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should accept the prompt with a blank response when there is a default" do
|
43
|
+
pending "Geckodriver doesn't set a blank response currently" if @session.respond_to?(:mode) && @session.mode.to_s == "selenium_marionette"
|
44
|
+
@session.accept_prompt with: '' do
|
45
|
+
@session.click_link('Open defaulted prompt')
|
46
|
+
end
|
47
|
+
expect(@session).to have_xpath("//a[@id='open-prompt-with-default' and @response='']")
|
48
|
+
end
|
49
|
+
|
28
50
|
it "should accept the prompt if the message matches" do
|
29
51
|
@session.accept_prompt 'Prompt opened', with: 'matched' do
|
30
52
|
@session.click_link('Open prompt')
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
Capybara::SpecHelper.spec "#evaluate_async_script", requires: [:js] do
|
3
|
+
it "should evaluate the given script and return whatever it produces" do
|
4
|
+
@session.visit('/with_js')
|
5
|
+
expect(@session.evaluate_async_script("arguments[0](4)")).to eq(4)
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should support passing elements as arguments to the script", requires: [:js, :es_args] do
|
9
|
+
@session.visit('/with_js')
|
10
|
+
el = @session.find(:css, '#drag p')
|
11
|
+
result = @session.evaluate_async_script("arguments[2]([arguments[0].innerText, arguments[1]])", el, "Doodle Funk")
|
12
|
+
expect(result).to eq ["This is a draggable element.", "Doodle Funk"]
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should support returning elements after asynchronous operation", requires: [:js, :es_args] do
|
16
|
+
@session.visit('/with_js')
|
17
|
+
@session.find(:css, '#change') # ensure page has loaded and element is available
|
18
|
+
el = @session.evaluate_async_script("var cb = arguments[0]; setTimeout(function(){ cb(document.getElementById('change')) }, 100)")
|
19
|
+
expect(el).to be_instance_of(Capybara::Node::Element)
|
20
|
+
expect(el).to eq(@session.find(:css, '#change'))
|
21
|
+
end
|
22
|
+
end
|
@@ -20,7 +20,7 @@ Capybara::SpecHelper.spec "#evaluate_script", requires: [:js] do
|
|
20
20
|
|
21
21
|
it "should support returning elements", requires: [:js, :es_args] do
|
22
22
|
@session.visit('/with_js')
|
23
|
-
|
23
|
+
@session.find(:css, '#change') # ensure page has loaded and element is available
|
24
24
|
el = @session.evaluate_script("document.getElementById('change')")
|
25
25
|
expect(el).to be_instance_of(Capybara::Node::Element)
|
26
26
|
expect(el).to eq(@session.find(:css, '#change'))
|
@@ -37,20 +37,47 @@ Capybara::SpecHelper.spec '#has_current_path?' do
|
|
37
37
|
expect(@session).to have_current_path('/with_js?test=test')
|
38
38
|
end
|
39
39
|
|
40
|
-
it "should compare the full url" do
|
40
|
+
it "should compare the full url if url: true is used" do
|
41
41
|
expect(@session).to have_current_path(%r{\Ahttp://[^/]*/with_js\Z}, url: true)
|
42
|
+
domain_port = if @session.respond_to?(:server) && @session.server
|
43
|
+
"#{@session.server.host}:#{@session.server.port}"
|
44
|
+
else
|
45
|
+
"www.example.com"
|
46
|
+
end
|
47
|
+
expect(@session).to have_current_path("http://#{domain_port}/with_js", url: true)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should not compare the full url if url: true is not passed" do
|
51
|
+
expect(@session).to have_current_path(%r{^/with_js\Z})
|
52
|
+
expect(@session).to have_current_path('/with_js')
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should not compare the full url if url: false is passed" do
|
56
|
+
expect(@session).to have_current_path(%r{^/with_js\Z}, url: false)
|
57
|
+
expect(@session).to have_current_path('/with_js', url: false)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should default to full url if value is a url" do
|
61
|
+
url = @session.current_url
|
62
|
+
expect(url).to match /with_js$/
|
63
|
+
expect(@session).to have_current_path(url)
|
64
|
+
expect(@session).not_to have_current_path("http://www.not_example.com/with_js")
|
42
65
|
end
|
43
66
|
|
44
67
|
it "should ignore the query" do
|
45
68
|
@session.visit('/with_js?test=test')
|
46
69
|
expect(@session).to have_current_path('/with_js?test=test')
|
47
70
|
expect(@session).to have_current_path('/with_js', only_path: true)
|
71
|
+
expect(@session).to have_current_path('/with_js', ignore_query: true)
|
72
|
+
uri = ::Addressable::URI.parse(@session.current_url)
|
73
|
+
uri.query = nil
|
74
|
+
expect(@session).to have_current_path(uri.to_s, ignore_query: true)
|
48
75
|
end
|
49
76
|
|
50
77
|
it "should not allow url and only_path at the same time" do
|
51
78
|
expect {
|
52
79
|
expect(@session).to have_current_path('/with_js', url: true, only_path: true)
|
53
|
-
|
80
|
+
}.to raise_error ArgumentError
|
54
81
|
end
|
55
82
|
|
56
83
|
it "should not raise an exception if the current_url is nil" do
|
@@ -59,12 +86,17 @@ Capybara::SpecHelper.spec '#has_current_path?' do
|
|
59
86
|
# Without only_path option
|
60
87
|
expect {
|
61
88
|
expect(@session).to have_current_path(nil)
|
62
|
-
}.
|
89
|
+
}.not_to raise_exception
|
63
90
|
|
64
91
|
# With only_path option
|
65
92
|
expect {
|
66
93
|
expect(@session).to have_current_path(nil, only_path: true)
|
67
|
-
}.
|
94
|
+
}.not_to raise_exception
|
95
|
+
|
96
|
+
# With ignore_query option
|
97
|
+
expect {
|
98
|
+
expect(@session).to have_current_path(nil, ignore_query: true)
|
99
|
+
}.not_to raise_exception
|
68
100
|
end
|
69
101
|
end
|
70
102
|
|
@@ -105,5 +137,10 @@ Capybara::SpecHelper.spec '#has_no_current_path?' do
|
|
105
137
|
expect {
|
106
138
|
expect(@session).not_to have_current_path('/with_js', only_path: true)
|
107
139
|
}. not_to raise_exception
|
140
|
+
|
141
|
+
# With ignore_query option
|
142
|
+
expect {
|
143
|
+
expect(@session).not_to have_current_path('/with_js', ignore_query: true)
|
144
|
+
}. not_to raise_exception
|
108
145
|
end
|
109
146
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<style>
|
4
|
+
header { height: 45px; position: fixed; top: 0; background-color: red; width: 100%;}
|
5
|
+
footer { height: 45px; position: fixed; bottom: 0; background-color: red; width: 100%;}
|
6
|
+
#main { margin: 45px;}
|
7
|
+
#tall { display: block; height: 2000px;}
|
8
|
+
</style>
|
9
|
+
</head>
|
10
|
+
<body>
|
11
|
+
<header>My headers</header>
|
12
|
+
<div id="main">
|
13
|
+
<div id="tall">A tall block</div>
|
14
|
+
<a href="/">Go to root</a>
|
15
|
+
</div>
|
16
|
+
<footer>My footer</footer>
|
17
|
+
</body>
|
data/lib/capybara/version.rb
CHANGED
@@ -14,7 +14,7 @@ Capybara.register_driver :selenium_marionette do |app|
|
|
14
14
|
Capybara::Selenium::Driver.new(
|
15
15
|
app,
|
16
16
|
browser: :firefox,
|
17
|
-
desired_capabilities: {:
|
17
|
+
desired_capabilities: {marionette: true, 'moz:webdriverClick': true},
|
18
18
|
options: browser_options
|
19
19
|
# Get a trace level log from geckodriver
|
20
20
|
# :driver_opts => { args: ['-vv'] }
|
@@ -55,7 +55,7 @@ end
|
|
55
55
|
|
56
56
|
RSpec.describe Capybara::Selenium::Driver do
|
57
57
|
before do
|
58
|
-
@driver = Capybara::Selenium::Driver.new(TestApp, browser: :firefox)
|
58
|
+
@driver = Capybara::Selenium::Driver.new(TestApp, browser: :firefox, options: browser_options)
|
59
59
|
end
|
60
60
|
|
61
61
|
describe '#quit' do
|
@@ -61,7 +61,7 @@ RSpec.shared_examples "Capybara::Session" do |session, mode|
|
|
61
61
|
skip "Headless Chrome doesn't support blockless modal methods" if @session.driver.send(:headless_chrome?)
|
62
62
|
@session.click_link('Open alert')
|
63
63
|
@session.accept_alert
|
64
|
-
expect{@session.driver.browser.switch_to.alert}.to raise_error(
|
64
|
+
expect{@session.driver.browser.switch_to.alert}.to raise_error(@session.driver.send(:modal_error))
|
65
65
|
end
|
66
66
|
|
67
67
|
it "raises if block is missing" do
|
@@ -180,6 +180,17 @@ RSpec.shared_examples "Capybara::Session" do |session, mode|
|
|
180
180
|
}
|
181
181
|
})
|
182
182
|
end
|
183
|
+
|
184
|
+
describe "#evaluate_async_script" do
|
185
|
+
it "will timeout if the script takes too long" do
|
186
|
+
@session.visit('/with_js')
|
187
|
+
expect do
|
188
|
+
@session.using_wait_time(1) do
|
189
|
+
@session.evaluate_async_script("var cb = arguments[0]; setTimeout(function(){ cb(null) }, 3000)")
|
190
|
+
end
|
191
|
+
end.to raise_error Selenium::WebDriver::Error::ScriptTimeoutError
|
192
|
+
end
|
193
|
+
end
|
183
194
|
end
|
184
195
|
|
185
196
|
describe "Element#inspect" do
|
@@ -191,5 +202,17 @@ RSpec.shared_examples "Capybara::Session" do |session, mode|
|
|
191
202
|
expect(el.inspect).to eq "Obsolete #<Capybara::Node::Element>"
|
192
203
|
end
|
193
204
|
end
|
205
|
+
|
206
|
+
describe "Element#click" do
|
207
|
+
it "should handle fixed headers/footers" do
|
208
|
+
if mode == :selenium_marionette && !(ENV['TRAVIS_ALLOW_FAILURE']=='true')
|
209
|
+
pending "geckodriver/firefox/marionette just fail to click without reporting an error - no way for us to detect/catch"
|
210
|
+
end
|
211
|
+
@session.visit('/with_fixed_header_footer')
|
212
|
+
# @session.click_link('Go to root')
|
213
|
+
@session.find(:link, 'Go to root').click
|
214
|
+
expect(@session).to have_current_path('/')
|
215
|
+
end
|
216
|
+
end
|
194
217
|
end
|
195
218
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: capybara
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.16.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Walpole
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain:
|
12
12
|
- gem-public_cert.pem
|
13
|
-
date: 2017-
|
13
|
+
date: 2017-11-13 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: nokogiri
|
@@ -376,6 +376,7 @@ files:
|
|
376
376
|
- lib/capybara/spec/session/element/match_css_spec.rb
|
377
377
|
- lib/capybara/spec/session/element/match_xpath_spec.rb
|
378
378
|
- lib/capybara/spec/session/element/matches_selector_spec.rb
|
379
|
+
- lib/capybara/spec/session/evaluate_async_script_spec.rb
|
379
380
|
- lib/capybara/spec/session/evaluate_script_spec.rb
|
380
381
|
- lib/capybara/spec/session/execute_script_spec.rb
|
381
382
|
- lib/capybara/spec/session/fill_in_spec.rb
|
@@ -447,6 +448,7 @@ files:
|
|
447
448
|
- lib/capybara/spec/views/tables.erb
|
448
449
|
- lib/capybara/spec/views/with_base_tag.erb
|
449
450
|
- lib/capybara/spec/views/with_count.erb
|
451
|
+
- lib/capybara/spec/views/with_fixed_header_footer.erb
|
450
452
|
- lib/capybara/spec/views/with_hover.erb
|
451
453
|
- lib/capybara/spec/views/with_html.erb
|
452
454
|
- lib/capybara/spec/views/with_html_entities.erb
|
@@ -506,7 +508,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
506
508
|
version: '0'
|
507
509
|
requirements: []
|
508
510
|
rubyforge_project:
|
509
|
-
rubygems_version: 2.
|
511
|
+
rubygems_version: 2.7.0
|
510
512
|
signing_key:
|
511
513
|
specification_version: 4
|
512
514
|
summary: Capybara aims to simplify the process of integration testing Rack applications,
|