capybara 3.5.1 → 3.6.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 +9 -0
- data/README.md +14 -2
- data/lib/capybara.rb +2 -0
- data/lib/capybara/driver/base.rb +7 -1
- data/lib/capybara/node/document.rb +2 -2
- data/lib/capybara/node/document_matchers.rb +2 -6
- data/lib/capybara/node/element.rb +3 -2
- data/lib/capybara/node/matchers.rb +20 -27
- data/lib/capybara/node/simple.rb +3 -2
- data/lib/capybara/queries/text_query.rb +9 -11
- data/lib/capybara/rack_test/driver.rb +0 -1
- data/lib/capybara/selenium/driver.rb +0 -1
- data/lib/capybara/selenium/node.rb +4 -5
- data/lib/capybara/selenium/nodes/chrome_node.rb +29 -0
- data/lib/capybara/selenium/nodes/marionette_node.rb +78 -2
- data/lib/capybara/session/config.rb +2 -1
- data/lib/capybara/session/matchers.rb +9 -6
- data/lib/capybara/spec/public/test.js +10 -0
- data/lib/capybara/spec/session/has_css_spec.rb +20 -0
- data/lib/capybara/spec/session/has_title_spec.rb +5 -0
- data/lib/capybara/spec/session/node_spec.rb +2 -2
- data/lib/capybara/spec/spec_helper.rb +1 -0
- data/lib/capybara/spec/views/with_js.erb +13 -0
- data/lib/capybara/version.rb +1 -1
- data/spec/selenium_spec_firefox_remote.rb +0 -4
- data/spec/selenium_spec_marionette.rb +0 -4
- data/spec/shared_selenium_session.rb +30 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f61329ea428004635c5d6768afcdc61fcc22a2dd3a4cb0f14bde2d3b05102cb
|
4
|
+
data.tar.gz: d9d3de632455c5a3a8eecbef2c2fccc7b8d301bb589dcd7a5a21c719e639a5f5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79e61c8494174a8006f158dc0b26d143a14525e4c0e77b4a1b5148f7f46c79207d4d5d89d0f5ef6f3ed3a8374bf2b54e915160770c6c254147c9aea52da48bf1
|
7
|
+
data.tar.gz: b329ae727a2c0d47903f9c992e8acd9e7e26e3bb280751fb8f681cb67c29650ad11d002ad9bfae627c1e767a00483e97a74667e564d4cec0bae2c529afc27452
|
data/History.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
# Version 3.6.0
|
2
|
+
Release date: 2018-08-14
|
3
|
+
|
4
|
+
### Added
|
5
|
+
|
6
|
+
* Workaround geckodriver/firefox send_keys issues as much as possible using the Selenium actions API
|
7
|
+
* Workaround lack of HTML5 native drag and drop events when using Selenium driver with Chrome and FF >= 62
|
8
|
+
* `Capybara.predicates_wait` option which sets whether or not Capybaras matcher predicate methods (`has_css?`, `has_selector?`, `has_text?`, etc.) default to using waiting/retrying behavior (defaults to true)
|
9
|
+
|
1
10
|
# Version 3.5.1
|
2
11
|
Release date: 2018-08-03
|
3
12
|
|
data/README.md
CHANGED
@@ -7,7 +7,7 @@
|
|
7
7
|
[![SemVer](https://api.dependabot.com/badges/compatibility_score?dependency-name=capybara&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=capybara&package-manager=bundler&version-scheme=semver)
|
8
8
|
|
9
9
|
**Note** You are viewing the README for the development version of Capybara. If you are using the current release version
|
10
|
-
you can find the README at https://github.com/teamcapybara/capybara/blob/3.
|
10
|
+
you can find the README at https://github.com/teamcapybara/capybara/blob/3.6_stable/README.md
|
11
11
|
|
12
12
|
|
13
13
|
Capybara helps you test web applications by simulating how a real user would
|
@@ -389,9 +389,20 @@ See the section on adding and configuring drivers.
|
|
389
389
|
|
390
390
|
Capybara supports [Selenium 3.5+
|
391
391
|
(Webdriver)](https://www.seleniumhq.org/projects/webdriver/).
|
392
|
-
In order to use Selenium, you'll need to install the`selenium-webdriver` gem,
|
392
|
+
In order to use Selenium, you'll need to install the `selenium-webdriver` gem,
|
393
393
|
and add it to your Gemfile if you're using bundler.
|
394
394
|
|
395
|
+
Capybara pre-registers a number of named drives that use Selenium - they are:
|
396
|
+
|
397
|
+
* :selenium => Selenium driving Firefox
|
398
|
+
* :selenium_chrome => Selenium driving Chrome
|
399
|
+
* :selenium_chrome_headless => Selenium driving Chrome in a headless configuration
|
400
|
+
|
401
|
+
These should work (with relevant software installation) in a local desktop configuration but you may
|
402
|
+
need to customize them if using in a CI environment where additional options may need to be passed
|
403
|
+
to the browsers. See the section on adding and configuring drivers.
|
404
|
+
|
405
|
+
|
395
406
|
**Note**: drivers which run the server in a different thread may not share the
|
396
407
|
same transaction as your tests, causing data not to be shared between your test
|
397
408
|
and test server, see "Transactions and database setup" below.
|
@@ -1000,6 +1011,7 @@ end
|
|
1000
1011
|
However, it's also possible to give this configuration a different name.
|
1001
1012
|
|
1002
1013
|
```ruby
|
1014
|
+
# Note: Capybara registers this by default
|
1003
1015
|
Capybara.register_driver :selenium_chrome do |app|
|
1004
1016
|
Capybara::Selenium::Driver.new(app, :browser => :chrome)
|
1005
1017
|
end
|
data/lib/capybara.rb
CHANGED
@@ -84,6 +84,7 @@ module Capybara
|
|
84
84
|
# [server = Symbol] The name of the registered server to use when running the app under test (Default: :webrick)
|
85
85
|
# [default_set_options = Hash] The default options passed to Node::set (Default: {})
|
86
86
|
# [test_id = Symbol/String/nil] Optional attribute to match locator aginst with builtin selectors along with id (Default: nil)
|
87
|
+
# [predicates_wait = Boolean] Whether Capybaras predicate matchers use waiting behavior by default (Default: true)
|
87
88
|
#
|
88
89
|
# === DSL Options
|
89
90
|
#
|
@@ -484,6 +485,7 @@ Capybara.configure do |config|
|
|
484
485
|
config.reuse_server = true
|
485
486
|
config.default_set_options = {}
|
486
487
|
config.test_id = nil
|
488
|
+
config.predicates_wait = true
|
487
489
|
end
|
488
490
|
|
489
491
|
Capybara.register_driver :rack_test do |app|
|
data/lib/capybara/driver/base.rb
CHANGED
@@ -20,8 +20,8 @@ module Capybara
|
|
20
20
|
#
|
21
21
|
# @return [String] The text of the document
|
22
22
|
#
|
23
|
-
def text(type = nil)
|
24
|
-
find(:xpath, '/html').text(type)
|
23
|
+
def text(type = nil, normalize_ws: false)
|
24
|
+
find(:xpath, '/html').text(type, normalize_ws: normalize_ws)
|
25
25
|
end
|
26
26
|
|
27
27
|
##
|
@@ -38,9 +38,7 @@ module Capybara
|
|
38
38
|
# @return [Boolean]
|
39
39
|
#
|
40
40
|
def has_title?(title, **options)
|
41
|
-
assert_title(title, options)
|
42
|
-
rescue Capybara::ExpectationNotMet
|
43
|
-
false
|
41
|
+
make_predicate(options) { assert_title(title, options) }
|
44
42
|
end
|
45
43
|
|
46
44
|
##
|
@@ -50,9 +48,7 @@ module Capybara
|
|
50
48
|
# @return [Boolean]
|
51
49
|
#
|
52
50
|
def has_no_title?(title, **options)
|
53
|
-
assert_no_title(title, options)
|
54
|
-
rescue Capybara::ExpectationNotMet
|
55
|
-
false
|
51
|
+
make_predicate(options) { assert_no_title(title, options) }
|
56
52
|
end
|
57
53
|
|
58
54
|
private
|
@@ -53,9 +53,10 @@ module Capybara
|
|
53
53
|
# @param type [:all, :visible] Whether to return only visible or all text
|
54
54
|
# @return [String] The text of the element
|
55
55
|
#
|
56
|
-
def text(type = nil)
|
56
|
+
def text(type = nil, normalize_ws: false)
|
57
57
|
type ||= :all unless session_options.ignore_hidden_elements || session_options.visible_text_only
|
58
|
-
synchronize { type == :all ? base.all_text : base.visible_text }
|
58
|
+
t = synchronize { type == :all ? base.all_text : base.visible_text }
|
59
|
+
normalize_ws ? t.gsub(/[[:space:]]+/, ' ').strip : t
|
59
60
|
end
|
60
61
|
|
61
62
|
##
|
@@ -36,10 +36,8 @@ module Capybara
|
|
36
36
|
# @option args [Range] :between (nil) Range of times that should contain number of times text occurs
|
37
37
|
# @return [Boolean] If the expression exists
|
38
38
|
#
|
39
|
-
def has_selector?(*args, &optional_filter_block)
|
40
|
-
assert_selector(*args, &optional_filter_block)
|
41
|
-
rescue Capybara::ExpectationNotMet
|
42
|
-
false
|
39
|
+
def has_selector?(*args, **options, &optional_filter_block)
|
40
|
+
make_predicate(options) { assert_selector(*args, options, &optional_filter_block) }
|
43
41
|
end
|
44
42
|
|
45
43
|
##
|
@@ -50,10 +48,8 @@ module Capybara
|
|
50
48
|
# @param (see Capybara::Node::Finders#has_selector?)
|
51
49
|
# @return [Boolean]
|
52
50
|
#
|
53
|
-
def has_no_selector?(*args, &optional_filter_block)
|
54
|
-
assert_no_selector(*args, &optional_filter_block)
|
55
|
-
rescue Capybara::ExpectationNotMet
|
56
|
-
false
|
51
|
+
def has_no_selector?(*args, **options, &optional_filter_block)
|
52
|
+
make_predicate(options) { assert_no_selector(*args, options, &optional_filter_block) }
|
57
53
|
end
|
58
54
|
|
59
55
|
##
|
@@ -66,9 +62,7 @@ module Capybara
|
|
66
62
|
# @return [Boolean] If the styles match
|
67
63
|
#
|
68
64
|
def has_style?(styles, **options)
|
69
|
-
assert_style(styles,
|
70
|
-
rescue Capybara::ExpectationNotMet
|
71
|
-
false
|
65
|
+
make_predicate(options) { assert_style(styles, options) }
|
72
66
|
end
|
73
67
|
|
74
68
|
##
|
@@ -554,10 +548,8 @@ module Capybara
|
|
554
548
|
# @param (see Capybara::Node::Finders#has_selector?)
|
555
549
|
# @return [Boolean]
|
556
550
|
#
|
557
|
-
def matches_selector?(*args, &optional_filter_block)
|
558
|
-
assert_matches_selector(*args, &optional_filter_block)
|
559
|
-
rescue Capybara::ExpectationNotMet
|
560
|
-
false
|
551
|
+
def matches_selector?(*args, **options, &optional_filter_block)
|
552
|
+
make_predicate(options) { assert_matches_selector(*args, options, &optional_filter_block) }
|
561
553
|
end
|
562
554
|
|
563
555
|
##
|
@@ -590,10 +582,8 @@ module Capybara
|
|
590
582
|
# @param (see Capybara::Node::Finders#has_selector?)
|
591
583
|
# @return [Boolean]
|
592
584
|
#
|
593
|
-
def not_matches_selector?(*args, &optional_filter_block)
|
594
|
-
assert_not_matches_selector(*args, &optional_filter_block)
|
595
|
-
rescue Capybara::ExpectationNotMet
|
596
|
-
false
|
585
|
+
def not_matches_selector?(*args, **options, &optional_filter_block)
|
586
|
+
make_predicate(options) { assert_not_matches_selector(*args, options, &optional_filter_block) }
|
597
587
|
end
|
598
588
|
|
599
589
|
##
|
@@ -683,10 +673,8 @@ module Capybara
|
|
683
673
|
# @macro text_query_params
|
684
674
|
# @return [Boolean] Whether it exists
|
685
675
|
#
|
686
|
-
def has_text?(*args)
|
687
|
-
assert_text(*args)
|
688
|
-
rescue Capybara::ExpectationNotMet
|
689
|
-
false
|
676
|
+
def has_text?(*args, **options)
|
677
|
+
make_predicate(options) { assert_text(*args, options) }
|
690
678
|
end
|
691
679
|
alias_method :has_content?, :has_text?
|
692
680
|
|
@@ -697,10 +685,8 @@ module Capybara
|
|
697
685
|
# @macro text_query_params
|
698
686
|
# @return [Boolean] Whether it doesn't exist
|
699
687
|
#
|
700
|
-
def has_no_text?(*args)
|
701
|
-
assert_no_text(*args)
|
702
|
-
rescue Capybara::ExpectationNotMet
|
703
|
-
false
|
688
|
+
def has_no_text?(*args, **options)
|
689
|
+
make_predicate(options) { assert_no_text(*args, options) }
|
704
690
|
end
|
705
691
|
alias_method :has_no_content?, :has_no_text?
|
706
692
|
|
@@ -745,6 +731,13 @@ module Capybara
|
|
745
731
|
query_options[:session_options] = session_options
|
746
732
|
query_args.push(query_options)
|
747
733
|
end
|
734
|
+
|
735
|
+
def make_predicate(options)
|
736
|
+
options[:wait] = 0 unless options.key?(:wait) || session_options.predicates_wait
|
737
|
+
yield
|
738
|
+
rescue Capybara::ExpectationNotMet
|
739
|
+
false
|
740
|
+
end
|
748
741
|
end
|
749
742
|
end
|
750
743
|
end
|
data/lib/capybara/node/simple.rb
CHANGED
@@ -5,12 +5,7 @@ module Capybara
|
|
5
5
|
module Queries
|
6
6
|
class TextQuery < BaseQuery
|
7
7
|
def initialize(type = nil, expected_text, session_options:, **options) # rubocop:disable Style/OptionalArguments
|
8
|
-
@type =
|
9
|
-
Capybara.ignore_hidden_elements || Capybara.visible_text_only ? :visible : :all
|
10
|
-
else
|
11
|
-
type
|
12
|
-
end
|
13
|
-
|
8
|
+
@type = type.nil? ? default_type : type
|
14
9
|
@expected_text = expected_text.is_a?(Regexp) ? expected_text : expected_text.to_s
|
15
10
|
@options = options
|
16
11
|
super(@options)
|
@@ -23,8 +18,7 @@ module Capybara
|
|
23
18
|
|
24
19
|
def resolve_for(node)
|
25
20
|
@node = node
|
26
|
-
@actual_text = text
|
27
|
-
@actual_text.gsub!(/[[:space:]]+/, ' ').strip! if options[:normalize_ws]
|
21
|
+
@actual_text = text
|
28
22
|
@count = @actual_text.scan(@search_regexp).size
|
29
23
|
end
|
30
24
|
|
@@ -74,7 +68,7 @@ module Capybara
|
|
74
68
|
end
|
75
69
|
|
76
70
|
def invisible_message
|
77
|
-
invisible_text = text(
|
71
|
+
invisible_text = text(query_type: :all)
|
78
72
|
invisible_count = invisible_text.scan(@search_regexp).size
|
79
73
|
return if invisible_count == @count
|
80
74
|
"it was found #{invisible_count} #{Capybara::Helpers.declension('time', 'times', invisible_count)} including non-visible text"
|
@@ -95,8 +89,12 @@ module Capybara
|
|
95
89
|
!@expected_text.is_a?(Regexp)
|
96
90
|
end
|
97
91
|
|
98
|
-
def text(node, query_type)
|
99
|
-
node.text(query_type)
|
92
|
+
def text(node: @node, query_type: @type)
|
93
|
+
node.text(query_type, normalize_ws: options[:normalize_ws])
|
94
|
+
end
|
95
|
+
|
96
|
+
def default_type
|
97
|
+
Capybara.ignore_hidden_elements || Capybara.visible_text_only ? :visible : :all
|
100
98
|
end
|
101
99
|
end
|
102
100
|
end
|
@@ -16,7 +16,6 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
|
|
16
16
|
|
17
17
|
def initialize(app, **options)
|
18
18
|
raise ArgumentError, 'rack-test requires a rack application, but none was given' unless app
|
19
|
-
@session = nil
|
20
19
|
@app = app
|
21
20
|
@options = DEFAULT_OPTIONS.merge(options)
|
22
21
|
end
|
@@ -47,7 +47,6 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
47
47
|
# Array => an array of keys to send before the value being set, e.g. [[:command, 'a'], :backspace]
|
48
48
|
def set(value, **options)
|
49
49
|
raise ArgumentError, "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}" if value.is_a?(Array) && !multiple?
|
50
|
-
|
51
50
|
case tag_name
|
52
51
|
when 'input'
|
53
52
|
case self[:type]
|
@@ -212,17 +211,17 @@ private
|
|
212
211
|
elsif clear == :backspace
|
213
212
|
# Clear field by sending the correct number of backspace keys.
|
214
213
|
backspaces = [:backspace] * self.value.to_s.length
|
215
|
-
|
214
|
+
send_keys(*([:end] + backspaces + [value.to_s]))
|
216
215
|
elsif clear == :none
|
217
|
-
|
216
|
+
send_keys(value.to_s)
|
218
217
|
elsif clear.is_a? Array
|
219
|
-
|
218
|
+
send_keys(*clear, value.to_s)
|
220
219
|
else
|
221
220
|
# Clear field by JavaScript assignment of the value property.
|
222
221
|
# Script can change a readonly element which user input cannot, so
|
223
222
|
# don't execute if readonly.
|
224
223
|
driver.execute_script "arguments[0].value = ''", self
|
225
|
-
|
224
|
+
send_keys(value.to_s)
|
226
225
|
end
|
227
226
|
end
|
228
227
|
|
@@ -10,9 +10,38 @@ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
|
|
10
10
|
raise
|
11
11
|
end
|
12
12
|
|
13
|
+
def drag_to(element)
|
14
|
+
return super unless self[:draggable] == 'true'
|
15
|
+
|
16
|
+
scroll_if_needed { driver.browser.action.click_and_hold(native).perform }
|
17
|
+
driver.execute_script HTML5_DRAG_DROP_SCRIPT, self, element
|
18
|
+
end
|
19
|
+
|
13
20
|
private
|
14
21
|
|
15
22
|
def bridge
|
16
23
|
driver.browser.send(:bridge)
|
17
24
|
end
|
25
|
+
|
26
|
+
HTML5_DRAG_DROP_SCRIPT = <<~JS
|
27
|
+
var source = arguments[0];
|
28
|
+
var target = arguments[1];
|
29
|
+
|
30
|
+
var dt = new DataTransfer();
|
31
|
+
var opts = { cancelable: true, bubbles: true, dataTransfer: dt };
|
32
|
+
|
33
|
+
var dragEvent = new DragEvent('dragstart', opts);
|
34
|
+
source.dispatchEvent(dragEvent);
|
35
|
+
target.scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'});
|
36
|
+
var dragOverEvent = new DragEvent('dragover', opts);
|
37
|
+
target.dispatchEvent(dragOverEvent);
|
38
|
+
var dragLeaveEvent = new DragEvent('dragleave', opts);
|
39
|
+
target.dispatchEvent(dragLeaveEvent);
|
40
|
+
if (dragOverEvent.defaultPrevented) {
|
41
|
+
var dropEvent = new DragEvent('drop', opts);
|
42
|
+
target.dispatchEvent(dropEvent);
|
43
|
+
}
|
44
|
+
var dragEndEvent = new DragEvent('dragend', opts);
|
45
|
+
source.dispatchEvent(dragEndEvent);
|
46
|
+
JS
|
18
47
|
end
|
@@ -14,7 +14,7 @@ class Capybara::Selenium::MarionetteNode < Capybara::Selenium::Node
|
|
14
14
|
|
15
15
|
def disabled?
|
16
16
|
# Not sure exactly what version of FF fixed the below issue, but it is definitely fixed in 61+
|
17
|
-
return super unless
|
17
|
+
return super unless browser_version < 61.0
|
18
18
|
|
19
19
|
return true if super
|
20
20
|
# workaround for selenium-webdriver/geckodriver reporting elements as enabled when they are nested in disabling elements
|
@@ -27,7 +27,7 @@ class Capybara::Selenium::MarionetteNode < Capybara::Selenium::Node
|
|
27
27
|
|
28
28
|
def set_file(value) # rubocop:disable Naming/AccessorMethodName
|
29
29
|
native.clear # By default files are appended so we have to clear here
|
30
|
-
return super if
|
30
|
+
return super if browser_version >= 62.0
|
31
31
|
|
32
32
|
# Workaround lack of support for multiple upload by uploading one at a time
|
33
33
|
path_names = value.to_s.empty? ? [] : value
|
@@ -42,8 +42,58 @@ class Capybara::Selenium::MarionetteNode < Capybara::Selenium::Node
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
+
def send_keys(*args)
|
46
|
+
# https://github.com/mozilla/geckodriver/issues/846
|
47
|
+
return super(*args.map { |arg| arg == :space ? ' ' : arg }) if args.none? { |s| s.is_a? Array }
|
48
|
+
|
49
|
+
native.click
|
50
|
+
actions = driver.browser.action
|
51
|
+
args.each do |keys|
|
52
|
+
_send_keys(keys, actions)
|
53
|
+
end
|
54
|
+
actions.perform
|
55
|
+
end
|
56
|
+
|
57
|
+
def drag_to(element)
|
58
|
+
return super unless (browser_version >= 62.0) && (self[:draggable] == 'true')
|
59
|
+
|
60
|
+
scroll_if_needed { driver.browser.action.click_and_hold(native).perform }
|
61
|
+
driver.execute_script HTML5_DRAG_DROP_SCRIPT, self, element
|
62
|
+
end
|
63
|
+
|
45
64
|
private
|
46
65
|
|
66
|
+
def _send_keys(keys, actions, down_keys = nil)
|
67
|
+
case keys
|
68
|
+
when String
|
69
|
+
keys = keys.upcase if down_keys&.include?(:shift) # https://bugzilla.mozilla.org/show_bug.cgi?id=1405370
|
70
|
+
actions.send_keys(keys)
|
71
|
+
when :space
|
72
|
+
actions.send_keys(' ') # https://github.com/mozilla/geckodriver/issues/846
|
73
|
+
when :control, :left_control, :right_control,
|
74
|
+
:alt, :left_alt, :right_alt,
|
75
|
+
:shift, :left_shift, :right_shift,
|
76
|
+
:meta, :left_meta, :right_meta,
|
77
|
+
:command
|
78
|
+
if down_keys.nil?
|
79
|
+
actions.send_keys(keys)
|
80
|
+
else
|
81
|
+
down_keys << keys
|
82
|
+
actions.key_down(keys)
|
83
|
+
end
|
84
|
+
when Symbol
|
85
|
+
actions.send_keys(keys)
|
86
|
+
when Array
|
87
|
+
local_down_keys = []
|
88
|
+
keys.each do |sub_keys|
|
89
|
+
_send_keys(sub_keys, actions, local_down_keys)
|
90
|
+
end
|
91
|
+
local_down_keys.each { |key| actions.key_up(key) }
|
92
|
+
else
|
93
|
+
raise ArgumentError, 'Unknown keys type'
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
47
97
|
def bridge
|
48
98
|
driver.browser.send(:bridge)
|
49
99
|
end
|
@@ -56,4 +106,30 @@ private
|
|
56
106
|
result = bridge.http.call(:post, "session/#{bridge.session_id}/file", file: Selenium::WebDriver::Zipper.zip_file(local_file))
|
57
107
|
result['value']
|
58
108
|
end
|
109
|
+
|
110
|
+
def browser_version
|
111
|
+
driver.browser.capabilities[:browser_version].to_f
|
112
|
+
end
|
113
|
+
|
114
|
+
HTML5_DRAG_DROP_SCRIPT = <<~JS
|
115
|
+
var source = arguments[0];
|
116
|
+
var target = arguments[1];
|
117
|
+
|
118
|
+
var dt = new DataTransfer();
|
119
|
+
var opts = { cancelable: true, bubbles: true, dataTransfer: dt };
|
120
|
+
|
121
|
+
var dragEvent = new DragEvent('dragstart', opts);
|
122
|
+
source.dispatchEvent(dragEvent);
|
123
|
+
target.scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'});
|
124
|
+
var dragOverEvent = new DragEvent('dragover', opts);
|
125
|
+
target.dispatchEvent(dragOverEvent);
|
126
|
+
var dragLeaveEvent = new DragEvent('dragleave', opts);
|
127
|
+
target.dispatchEvent(dragLeaveEvent);
|
128
|
+
if (dragOverEvent.defaultPrevented) {
|
129
|
+
var dropEvent = new DragEvent('drop', opts);
|
130
|
+
target.dispatchEvent(dropEvent);
|
131
|
+
}
|
132
|
+
var dragEndEvent = new DragEvent('dragend', opts);
|
133
|
+
source.dispatchEvent(dragEndEvent);
|
134
|
+
JS
|
59
135
|
end
|
@@ -7,7 +7,8 @@ module Capybara
|
|
7
7
|
OPTIONS = %i[always_include_port run_server default_selector default_max_wait_time ignore_hidden_elements
|
8
8
|
automatic_reload match exact exact_text raise_server_errors visible_text_only
|
9
9
|
automatic_label_click enable_aria_label save_path asset_host default_host app_host
|
10
|
-
server_host server_port server_errors default_set_options disable_animation test_id
|
10
|
+
server_host server_port server_errors default_set_options disable_animation test_id
|
11
|
+
predicates_wait].freeze
|
11
12
|
|
12
13
|
attr_accessor(*OPTIONS)
|
13
14
|
|
@@ -47,9 +47,7 @@ module Capybara
|
|
47
47
|
# @return [Boolean]
|
48
48
|
#
|
49
49
|
def has_current_path?(path, **options)
|
50
|
-
assert_current_path(path, options)
|
51
|
-
rescue Capybara::ExpectationNotMet
|
52
|
-
false
|
50
|
+
make_predicate(options) { assert_current_path(path, options) }
|
53
51
|
end
|
54
52
|
|
55
53
|
##
|
@@ -62,9 +60,7 @@ module Capybara
|
|
62
60
|
# @return [Boolean]
|
63
61
|
#
|
64
62
|
def has_no_current_path?(path, **options)
|
65
|
-
assert_no_current_path(path, options)
|
66
|
-
rescue Capybara::ExpectationNotMet
|
67
|
-
false
|
63
|
+
make_predicate(options) { assert_no_current_path(path, options) }
|
68
64
|
end
|
69
65
|
|
70
66
|
private
|
@@ -76,5 +72,12 @@ module Capybara
|
|
76
72
|
end
|
77
73
|
true
|
78
74
|
end
|
75
|
+
|
76
|
+
def make_predicate(options)
|
77
|
+
options[:wait] = 0 unless options.key?(:wait) || config.predicates_wait
|
78
|
+
yield
|
79
|
+
rescue Capybara::ExpectationNotMet
|
80
|
+
false
|
81
|
+
end
|
79
82
|
end
|
80
83
|
end
|
@@ -8,6 +8,16 @@ $(function() {
|
|
8
8
|
$(this).html('Dropped!');
|
9
9
|
}
|
10
10
|
});
|
11
|
+
$('#drag_html5, #drag_html5_scroll').on('dragstart', function(ev){
|
12
|
+
ev.originalEvent.dataTransfer.setData("text", ev.target.id);
|
13
|
+
});
|
14
|
+
$('#drop_html5, #drop_html5_scroll').on('dragover', function(ev){
|
15
|
+
if ($(this).hasClass('drop')) { ev.preventDefault(); }
|
16
|
+
});
|
17
|
+
$('#drop_html5, #drop_html5_scroll').on('drop', function(ev){
|
18
|
+
ev.preventDefault();
|
19
|
+
$(this).html('HTML5 Dropped ' + ev.originalEvent.dataTransfer.getData("text"));
|
20
|
+
});
|
11
21
|
$('#clickable').click(function(e) {
|
12
22
|
var link = $(this);
|
13
23
|
setTimeout(function() {
|
@@ -30,6 +30,26 @@ Capybara::SpecHelper.spec '#has_css?' do
|
|
30
30
|
expect(@session).to have_css("input[type='submit'][value='New Here']")
|
31
31
|
end
|
32
32
|
|
33
|
+
context 'with predicates_wait == true' do
|
34
|
+
it 'should wait for content to appear', requires: [:js] do
|
35
|
+
Capybara.predicates_wait = true
|
36
|
+
Capybara.default_max_wait_time = 2
|
37
|
+
@session.visit('/with_js')
|
38
|
+
@session.click_link('Click me')
|
39
|
+
expect(@session.has_css?("input[type='submit'][value='New Here']")).to be true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'with predicates_wait == false' do
|
44
|
+
it 'should not wait for content to appear', requires: [:js] do
|
45
|
+
Capybara.predicates_wait = false
|
46
|
+
Capybara.default_max_wait_time = 2
|
47
|
+
@session.visit('/with_js')
|
48
|
+
@session.click_link('Click me')
|
49
|
+
expect(@session.has_css?("input[type='submit'][value='New Here']")).to be false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
33
53
|
context 'with between' do
|
34
54
|
it 'should be true if the content occurs within the range given' do
|
35
55
|
expect(@session).to have_css('p', between: 1..4)
|
@@ -7,6 +7,7 @@ Capybara::SpecHelper.spec '#has_title?' do
|
|
7
7
|
|
8
8
|
it 'should be true if the page has the given title' do
|
9
9
|
expect(@session).to have_title('with_js')
|
10
|
+
expect(@session.has_title?('with_js')).to be true
|
10
11
|
end
|
11
12
|
|
12
13
|
it 'should allow regexp matches' do
|
@@ -21,6 +22,7 @@ Capybara::SpecHelper.spec '#has_title?' do
|
|
21
22
|
|
22
23
|
it 'should be false if the page has not the given title' do
|
23
24
|
expect(@session).not_to have_title('monkey')
|
25
|
+
expect(@session.has_title?('monkey')).to be false
|
24
26
|
end
|
25
27
|
|
26
28
|
it 'should default to exact: false matching' do
|
@@ -31,6 +33,8 @@ Capybara::SpecHelper.spec '#has_title?' do
|
|
31
33
|
it 'should match exactly if exact: true option passed' do
|
32
34
|
expect(@session).to have_title('with_js', exact: true)
|
33
35
|
expect(@session).not_to have_title('with_', exact: true)
|
36
|
+
expect(@session.has_title?('with_js', exact: true)).to be true
|
37
|
+
expect(@session.has_title?('with_', exact: true)).to be false
|
34
38
|
end
|
35
39
|
|
36
40
|
it 'should match partial if exact: false option passed' do
|
@@ -62,5 +66,6 @@ Capybara::SpecHelper.spec '#has_no_title?' do
|
|
62
66
|
|
63
67
|
it 'should be true if the page has not the given title' do
|
64
68
|
expect(@session).to have_no_title('monkey')
|
69
|
+
expect(@session.has_no_title?('monkey')).to be true
|
65
70
|
end
|
66
71
|
end
|
@@ -306,7 +306,7 @@ Capybara::SpecHelper.spec 'node' do
|
|
306
306
|
element = @session.find('//div[@id="drag"]')
|
307
307
|
target = @session.find('//div[@id="drop"]')
|
308
308
|
element.drag_to(target)
|
309
|
-
expect(@session.
|
309
|
+
expect(@session).to have_xpath('//div[contains(., "Dropped!")]')
|
310
310
|
end
|
311
311
|
|
312
312
|
it 'should drag and drop if scrolling is needed' do
|
@@ -314,7 +314,7 @@ Capybara::SpecHelper.spec 'node' do
|
|
314
314
|
element = @session.find('//div[@id="drag_scroll"]')
|
315
315
|
target = @session.find('//div[@id="drop_scroll"]')
|
316
316
|
element.drag_to(target)
|
317
|
-
expect(@session.
|
317
|
+
expect(@session).to have_xpath('//div[contains(., "Dropped!")]')
|
318
318
|
end
|
319
319
|
end
|
320
320
|
|
@@ -19,6 +19,16 @@
|
|
19
19
|
<p>It should be dropped here.</p>
|
20
20
|
</div>
|
21
21
|
|
22
|
+
<div id="drop_html5_scroll" class="drop">
|
23
|
+
<p>It should be dropped here.</p>
|
24
|
+
</div>
|
25
|
+
<div id="drag_html5" draggable="true">
|
26
|
+
<p>This is an HTML5 draggable element.</p>
|
27
|
+
</div>
|
28
|
+
<div id="drop_html5" class="drop">
|
29
|
+
<p>It should be dropped here.</p>
|
30
|
+
</div>
|
31
|
+
|
22
32
|
<p><a href="#" id="clickable">Click me</a></p>
|
23
33
|
<p><a href="#" id="slow-click">Slowly</a></p>
|
24
34
|
|
@@ -135,6 +145,9 @@
|
|
135
145
|
<div id="drop_scroll">
|
136
146
|
<p>It should be dropped here.</p>
|
137
147
|
</div>
|
148
|
+
<div id="drag_html5_scroll" draggable="true">
|
149
|
+
<p>This is an HTML5 draggable element.</p>
|
150
|
+
</div>
|
138
151
|
|
139
152
|
<script type="text/javascript">
|
140
153
|
// a javascript comment
|
data/lib/capybara/version.rb
CHANGED
@@ -60,10 +60,6 @@ skipped_tests << :windows if ENV['TRAVIS'] && (ENV['SKIP_WINDOW'] || ENV['HEADLE
|
|
60
60
|
|
61
61
|
Capybara::SpecHelper.run_specs TestSessions::RemoteFirefox, FIREFOX_REMOTE_DRIVER.to_s, capybara_skip: skipped_tests do |example|
|
62
62
|
case example.metadata[:full_description]
|
63
|
-
when 'Capybara::Session selenium_firefox_remote node #send_keys should generate key events',
|
64
|
-
'Capybara::Session selenium_firefox_remote node #send_keys should allow for multiple simultaneous keys',
|
65
|
-
'Capybara::Session selenium_firefox_remote node #send_keys should send special characters'
|
66
|
-
pending "selenium-webdriver/geckodriver doesn't support complex sets of characters"
|
67
63
|
when 'Capybara::Session selenium_firefox_remote node #click should allow multiple modifiers'
|
68
64
|
pending "Firefox doesn't generate an event for shift+control+click" if marionette_gte?(62, @session)
|
69
65
|
when /^Capybara::Session selenium node #double_click/
|
@@ -50,10 +50,6 @@ $stdout.puts `#{Selenium::WebDriver::Firefox.driver_path} --version` if ENV['CI'
|
|
50
50
|
|
51
51
|
Capybara::SpecHelper.run_specs TestSessions::SeleniumMarionette, 'selenium', capybara_skip: skipped_tests do |example|
|
52
52
|
case example.metadata[:full_description]
|
53
|
-
when 'Capybara::Session selenium node #send_keys should generate key events',
|
54
|
-
'Capybara::Session selenium node #send_keys should allow for multiple simultaneous keys',
|
55
|
-
'Capybara::Session selenium node #send_keys should send special characters'
|
56
|
-
pending "selenium-webdriver/geckodriver doesn't support complex sets of characters"
|
57
53
|
when 'Capybara::Session selenium node #click should allow multiple modifiers'
|
58
54
|
pending "Firefox doesn't generate an event for shift+control+click" if marionette_gte?(62, @session)
|
59
55
|
when /^Capybara::Session selenium node #double_click/
|
@@ -185,7 +185,6 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
|
|
185
185
|
|
186
186
|
context '#fill_in with { clear: Array } fill_options' do
|
187
187
|
it 'should pass the array through to the element' do
|
188
|
-
pending "selenium-webdriver/geckodriver doesn't support complex sets of characters" if marionette?(session)
|
189
188
|
# this is mainly for use with [[:control, 'a'], :backspace] - however since that is platform dependant I'm testing with something less useful
|
190
189
|
session.visit('/form')
|
191
190
|
session.fill_in('form_first_name',
|
@@ -286,6 +285,36 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
|
|
286
285
|
end
|
287
286
|
end
|
288
287
|
|
288
|
+
describe 'Element#drag_to' do
|
289
|
+
it 'should HTML5 drag and drop an object' do
|
290
|
+
pending "Firefox < 62 doesn't support a DataTransfer constuctor" if marionette_lt?(62.0, session)
|
291
|
+
session.visit('/with_js')
|
292
|
+
element = session.find('//div[@id="drag_html5"]')
|
293
|
+
target = session.find('//div[@id="drop_html5"]')
|
294
|
+
element.drag_to(target)
|
295
|
+
expect(session).to have_xpath('//div[contains(., "HTML5 Dropped drag_html5")]')
|
296
|
+
end
|
297
|
+
|
298
|
+
it 'should not HTML5 drag and drop on a non HTML5 drop element' do
|
299
|
+
session.visit('/with_js')
|
300
|
+
element = session.find('//div[@id="drag_html5"]')
|
301
|
+
target = session.find('//div[@id="drop_html5"]')
|
302
|
+
target.execute_script("$(this).removeClass('drop');")
|
303
|
+
element.drag_to(target)
|
304
|
+
sleep 1
|
305
|
+
expect(session).not_to have_xpath('//div[contains(., "HTML5 Dropped drag_html5")]')
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'should HTML6 drag and drop when scrolling needed' do
|
309
|
+
pending "Firefox < 62 doesn't support a DataTransfer constuctor" if marionette_lt?(62.0, session)
|
310
|
+
session.visit('/with_js')
|
311
|
+
element = session.find('//div[@id="drag_html5_scroll"]')
|
312
|
+
target = session.find('//div[@id="drop_html5_scroll"]')
|
313
|
+
element.drag_to(target)
|
314
|
+
expect(session).to have_xpath('//div[contains(., "HTML5 Dropped drag_html5_scroll")]')
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
289
318
|
context 'Windows' do
|
290
319
|
it "can't close the primary window" do
|
291
320
|
expect do
|
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: 3.
|
4
|
+
version: 3.6.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: 2018-08-
|
13
|
+
date: 2018-08-14 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: addressable
|
@@ -503,7 +503,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
503
503
|
version: '0'
|
504
504
|
requirements: []
|
505
505
|
rubyforge_project:
|
506
|
-
rubygems_version: 2.7.
|
506
|
+
rubygems_version: 2.7.7
|
507
507
|
signing_key:
|
508
508
|
specification_version: 4
|
509
509
|
summary: Capybara aims to simplify the process of integration testing Rack applications,
|