capybara 3.18.0 → 3.19.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 +16 -0
- data/README.md +14 -44
- data/lib/capybara/node/actions.rb +2 -2
- data/lib/capybara/node/element.rb +3 -5
- data/lib/capybara/queries/selector_query.rb +30 -11
- data/lib/capybara/rack_test/node.rb +1 -1
- data/lib/capybara/result.rb +2 -0
- data/lib/capybara/rspec/matcher_proxies.rb +2 -0
- data/lib/capybara/rspec/matchers/base.rb +2 -2
- data/lib/capybara/rspec/matchers/count_sugar.rb +36 -0
- data/lib/capybara/rspec/matchers/have_selector.rb +3 -0
- data/lib/capybara/rspec/matchers/have_text.rb +3 -0
- data/lib/capybara/selector.rb +196 -599
- data/lib/capybara/selector/css.rb +2 -0
- data/lib/capybara/selector/definition.rb +276 -0
- data/lib/capybara/selector/definition/button.rb +46 -0
- data/lib/capybara/selector/definition/checkbox.rb +23 -0
- data/lib/capybara/selector/definition/css.rb +5 -0
- data/lib/capybara/selector/definition/datalist_input.rb +35 -0
- data/lib/capybara/selector/definition/datalist_option.rb +25 -0
- data/lib/capybara/selector/definition/element.rb +27 -0
- data/lib/capybara/selector/definition/field.rb +40 -0
- data/lib/capybara/selector/definition/fieldset.rb +14 -0
- data/lib/capybara/selector/definition/file_field.rb +13 -0
- data/lib/capybara/selector/definition/fillable_field.rb +33 -0
- data/lib/capybara/selector/definition/frame.rb +17 -0
- data/lib/capybara/selector/definition/id.rb +6 -0
- data/lib/capybara/selector/definition/label.rb +43 -0
- data/lib/capybara/selector/definition/link.rb +45 -0
- data/lib/capybara/selector/definition/link_or_button.rb +16 -0
- data/lib/capybara/selector/definition/option.rb +27 -0
- data/lib/capybara/selector/definition/radio_button.rb +24 -0
- data/lib/capybara/selector/definition/select.rb +62 -0
- data/lib/capybara/selector/definition/table.rb +106 -0
- data/lib/capybara/selector/definition/table_row.rb +21 -0
- data/lib/capybara/selector/definition/xpath.rb +5 -0
- data/lib/capybara/selector/filters/base.rb +4 -0
- data/lib/capybara/selector/filters/locator_filter.rb +12 -2
- data/lib/capybara/selector/selector.rb +40 -452
- data/lib/capybara/selenium/driver.rb +4 -10
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +3 -9
- data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +8 -0
- data/lib/capybara/selenium/extensions/find.rb +1 -1
- data/lib/capybara/selenium/logger_suppressor.rb +5 -0
- data/lib/capybara/selenium/node.rb +19 -13
- data/lib/capybara/selenium/nodes/chrome_node.rb +30 -0
- data/lib/capybara/selenium/nodes/firefox_node.rb +14 -12
- data/lib/capybara/selenium/nodes/ie_node.rb +11 -0
- data/lib/capybara/selenium/nodes/safari_node.rb +7 -12
- data/lib/capybara/server/checker.rb +7 -3
- data/lib/capybara/session.rb +2 -2
- data/lib/capybara/spec/session/all_spec.rb +1 -1
- data/lib/capybara/spec/session/find_spec.rb +1 -1
- data/lib/capybara/spec/session/first_spec.rb +1 -1
- data/lib/capybara/spec/session/has_css_spec.rb +7 -0
- data/lib/capybara/spec/session/has_text_spec.rb +6 -0
- data/lib/capybara/spec/session/save_screenshot_spec.rb +11 -0
- data/lib/capybara/spec/session/select_spec.rb +0 -5
- data/lib/capybara/spec/test_app.rb +8 -3
- data/lib/capybara/version.rb +1 -1
- data/lib/capybara/window.rb +1 -1
- data/spec/minitest_spec_spec.rb +1 -0
- data/spec/selector_spec.rb +12 -6
- data/spec/selenium_spec_firefox.rb +0 -3
- data/spec/selenium_spec_firefox_remote.rb +0 -3
- data/spec/selenium_spec_ie.rb +3 -1
- data/spec/server_spec.rb +1 -1
- data/spec/shared_selenium_session.rb +1 -1
- data/spec/spec_helper.rb +9 -2
- metadata +54 -2
@@ -226,10 +226,10 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
226
226
|
@browser&.quit
|
227
227
|
rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED # rubocop:disable Lint/HandleExceptions
|
228
228
|
# Browser must have already gone
|
229
|
-
rescue Selenium::WebDriver::Error::UnknownError =>
|
230
|
-
unless silenced_unknown_error_message?(
|
229
|
+
rescue Selenium::WebDriver::Error::UnknownError => e
|
230
|
+
unless silenced_unknown_error_message?(e.message) # Most likely already gone
|
231
231
|
# probably already gone but not sure - so warn
|
232
|
-
warn "Ignoring Selenium UnknownError during driver quit: #{
|
232
|
+
warn "Ignoring Selenium UnknownError during driver quit: #{e.message}"
|
233
233
|
end
|
234
234
|
ensure
|
235
235
|
@browser = nil
|
@@ -283,13 +283,7 @@ private
|
|
283
283
|
end
|
284
284
|
|
285
285
|
def clear_browser_state_errors
|
286
|
-
@clear_browser_state_errors ||= [Selenium::WebDriver::Error::UnknownError]
|
287
|
-
unless selenium_4?
|
288
|
-
::Selenium::WebDriver.logger.suppress_deprecations do
|
289
|
-
errors << Selenium::WebDriver::Error::UnhandledError
|
290
|
-
end
|
291
|
-
end
|
292
|
-
end
|
286
|
+
@clear_browser_state_errors ||= [Selenium::WebDriver::Error::UnknownError]
|
293
287
|
end
|
294
288
|
|
295
289
|
def unhandled_alert_errors
|
@@ -18,8 +18,8 @@ module Capybara::Selenium::Driver::ChromeDriver
|
|
18
18
|
|
19
19
|
def resize_window_to(handle, width, height)
|
20
20
|
super
|
21
|
-
rescue Selenium::WebDriver::Error::UnknownError =>
|
22
|
-
raise unless
|
21
|
+
rescue Selenium::WebDriver::Error::UnknownError => e
|
22
|
+
raise unless e.message.match?(/failed to change window state/)
|
23
23
|
|
24
24
|
# Chromedriver doesn't wait long enough for state to change when coming out of fullscreen
|
25
25
|
# and raises unnecessary error. Wait a bit and try again.
|
@@ -53,13 +53,7 @@ private
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def cdp_unsupported_errors
|
56
|
-
@cdp_unsupported_errors ||= [Selenium::WebDriver::Error::WebDriverError]
|
57
|
-
unless selenium_4?
|
58
|
-
::Selenium::WebDriver.logger.suppress_deprecations do
|
59
|
-
errors << Selenium::WebDriver::Error::UnhandledError
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
56
|
+
@cdp_unsupported_errors ||= [Selenium::WebDriver::Error::WebDriverError]
|
63
57
|
end
|
64
58
|
|
65
59
|
def execute_cdp(cmd, params = {})
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'capybara/selenium/nodes/ie_node'
|
4
|
+
|
3
5
|
module Capybara::Selenium::Driver::InternetExplorerDriver
|
4
6
|
def switch_to_frame(frame)
|
5
7
|
return super unless frame == :parent
|
@@ -10,6 +12,12 @@ module Capybara::Selenium::Driver::InternetExplorerDriver
|
|
10
12
|
browser.switch_to.default_content
|
11
13
|
handles.tap(&:pop).each { |fh| browser.switch_to.frame(fh) }
|
12
14
|
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def build_node(native_node, initial_cache = {})
|
19
|
+
::Capybara::Selenium::IENode.new(self, native_node, initial_cache)
|
20
|
+
end
|
13
21
|
end
|
14
22
|
|
15
23
|
module Capybara::Selenium
|
@@ -87,7 +87,7 @@ module Capybara
|
|
87
87
|
end
|
88
88
|
|
89
89
|
def is_displayed_atom # rubocop:disable Naming/PredicateName
|
90
|
-
@@is_displayed_atom ||= begin
|
90
|
+
@@is_displayed_atom ||= begin # rubocop:disable Style/ClassVars
|
91
91
|
browser.send(:bridge).send(:read_atom, 'isDisplayed')
|
92
92
|
rescue StandardError
|
93
93
|
# If the atom doesn't exist or other error
|
@@ -56,10 +56,12 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
56
56
|
def set(value, **options)
|
57
57
|
raise ArgumentError, "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}" if value.is_a?(Array) && !multiple?
|
58
58
|
|
59
|
-
tag_name, type = attrs(:tagName, :type)
|
60
|
-
|
59
|
+
tag_name, type = attrs(:tagName, :type).map { |val| val&.downcase }
|
60
|
+
@tag_name ||= tag_name
|
61
|
+
|
62
|
+
case tag_name
|
61
63
|
when 'input'
|
62
|
-
case type
|
64
|
+
case type
|
63
65
|
when 'radio'
|
64
66
|
click
|
65
67
|
when 'checkbox'
|
@@ -78,7 +80,7 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
78
80
|
when 'textarea'
|
79
81
|
set_text(value, options)
|
80
82
|
else
|
81
|
-
set_content_editable(value)
|
83
|
+
set_content_editable(value)
|
82
84
|
end
|
83
85
|
end
|
84
86
|
|
@@ -136,7 +138,7 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
136
138
|
end
|
137
139
|
|
138
140
|
def tag_name
|
139
|
-
native.tag_name.downcase
|
141
|
+
@tag_name ||= native.tag_name.downcase
|
140
142
|
end
|
141
143
|
|
142
144
|
def visible?; boolean_attr(native.displayed?); end
|
@@ -291,20 +293,24 @@ private
|
|
291
293
|
# Ensure we are focused on the element
|
292
294
|
click
|
293
295
|
|
294
|
-
driver.execute_script <<-JS, self
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
296
|
+
editable = driver.execute_script <<-JS, self
|
297
|
+
if (arguments[0].isContentEditable) {
|
298
|
+
var range = document.createRange();
|
299
|
+
var sel = window.getSelection();
|
300
|
+
arguments[0].focus();
|
301
|
+
range.selectNodeContents(arguments[0]);
|
302
|
+
sel.removeAllRanges();
|
303
|
+
sel.addRange(range);
|
304
|
+
return true;
|
305
|
+
}
|
306
|
+
return false;
|
301
307
|
JS
|
302
308
|
|
303
309
|
# The action api has a speed problem but both chrome and firefox 58 raise errors
|
304
310
|
# if we use the faster direct send_keys. For now just send_keys to the element
|
305
311
|
# we've already focused.
|
306
312
|
# native.send_keys(value.to_s)
|
307
|
-
browser_action.send_keys(value.to_s).perform
|
313
|
+
browser_action.send_keys(value.to_s).perform if editable
|
308
314
|
end
|
309
315
|
|
310
316
|
def action_with_modifiers(click_options)
|
@@ -13,6 +13,14 @@ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def set_file(value) # rubocop:disable Naming/AccessorMethodName
|
16
|
+
# In Chrome 75+ files are appended (due to WebDriver spec - why?) so we have to clear here if its multiple and already set
|
17
|
+
if browser_version >= 75.0
|
18
|
+
driver.execute_script(<<~JS, self)
|
19
|
+
if (arguments[0].multiple && (arguments[0].files.length > 0)){
|
20
|
+
arguments[0].value = null;
|
21
|
+
}
|
22
|
+
JS
|
23
|
+
end
|
16
24
|
super(value)
|
17
25
|
rescue *file_errors => e
|
18
26
|
raise ArgumentError, "Selenium < 3.14 with remote Chrome doesn't support multiple file upload" if e.message.match?(/File not found : .+\n.+/m)
|
@@ -35,6 +43,18 @@ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
|
|
35
43
|
raise
|
36
44
|
end
|
37
45
|
|
46
|
+
def disabled?
|
47
|
+
driver.evaluate_script("arguments[0].matches(':disabled, select:disabled *')", self)
|
48
|
+
end
|
49
|
+
|
50
|
+
def select_option
|
51
|
+
# To optimize to only one check and then click
|
52
|
+
selected_or_disabled = driver.evaluate_script(<<~JS, self)
|
53
|
+
arguments[0].matches(':disabled, select:disabled *, :checked')
|
54
|
+
JS
|
55
|
+
click unless selected_or_disabled
|
56
|
+
end
|
57
|
+
|
38
58
|
private
|
39
59
|
|
40
60
|
def file_errors
|
@@ -46,4 +66,14 @@ private
|
|
46
66
|
def bridge
|
47
67
|
driver.browser.send(:bridge)
|
48
68
|
end
|
69
|
+
|
70
|
+
def w3c?
|
71
|
+
(defined?(Selenium::WebDriver::VERSION) && (Selenium::WebDriver::VERSION.to_f >= 4)) ||
|
72
|
+
driver.browser.capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities)
|
73
|
+
end
|
74
|
+
|
75
|
+
def browser_version
|
76
|
+
caps = driver.browser.capabilities
|
77
|
+
(caps[:browser_version] || caps[:version]).to_f
|
78
|
+
end
|
49
79
|
end
|
@@ -18,22 +18,16 @@ class Capybara::Selenium::FirefoxNode < Capybara::Selenium::Node
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def disabled?
|
21
|
-
|
22
|
-
return super unless browser_version < 61.0
|
23
|
-
|
24
|
-
return true if super
|
25
|
-
|
26
|
-
# workaround for selenium-webdriver/geckodriver reporting elements as enabled when they are nested in disabling elements
|
27
|
-
if %w[option optgroup].include? tag_name
|
28
|
-
find_xpath('parent::*[self::optgroup or self::select]')[0].disabled?
|
29
|
-
else
|
30
|
-
!find_xpath(DISABLED_BY_FIELDSET_XPATH).empty?
|
31
|
-
end
|
21
|
+
driver.evaluate_script("arguments[0].matches(':disabled, select:disabled *')", self)
|
32
22
|
end
|
33
23
|
|
34
24
|
def set_file(value) # rubocop:disable Naming/AccessorMethodName
|
35
25
|
# By default files are appended so we have to clear here if its multiple and already set
|
36
|
-
|
26
|
+
driver.execute_script(<<~JS, self)
|
27
|
+
if (arguments[0].multiple && (arguments[0].files.length > 0)){
|
28
|
+
arguments[0].value = null;
|
29
|
+
}
|
30
|
+
JS
|
37
31
|
return super if browser_version >= 62.0
|
38
32
|
|
39
33
|
# Workaround lack of support for multiple upload by uploading one at a time
|
@@ -65,6 +59,14 @@ class Capybara::Selenium::FirefoxNode < Capybara::Selenium::Node
|
|
65
59
|
scroll_if_needed { browser_action.move_to(native, 0, 0).move_to(native).perform }
|
66
60
|
end
|
67
61
|
|
62
|
+
def select_option
|
63
|
+
# To optimize to only one check and then click
|
64
|
+
selected_or_disabled = driver.evaluate_script(<<~JS, self)
|
65
|
+
arguments[0].matches(':disabled, select:disabled *, :checked')
|
66
|
+
JS
|
67
|
+
click unless selected_or_disabled
|
68
|
+
end
|
69
|
+
|
68
70
|
private
|
69
71
|
|
70
72
|
def click_with_options(click_options)
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'capybara/selenium/extensions/html5_drag'
|
4
|
+
|
5
|
+
class Capybara::Selenium::IENode < Capybara::Selenium::Node
|
6
|
+
def disabled?
|
7
|
+
# TODO: Doesn't work for a bunch of cases - need to get IE running to see if it can be done like this
|
8
|
+
# driver.evaluate_script("arguments[0].msMatchesSelector(':disabled, select:disabled *')", self)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
end
|
@@ -19,8 +19,12 @@ class Capybara::Selenium::SafariNode < Capybara::Selenium::Node
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def select_option
|
22
|
-
|
23
|
-
|
22
|
+
# To optimize to only one check and then click
|
23
|
+
selected_or_disabled = driver.execute_script(<<~JS, self)
|
24
|
+
arguments[0].closest('select').scrollIntoView();
|
25
|
+
return arguments[0].matches(':disabled, select:disabled *, :checked');
|
26
|
+
JS
|
27
|
+
click unless selected_or_disabled
|
24
28
|
end
|
25
29
|
|
26
30
|
def unselect_option
|
@@ -40,16 +44,7 @@ class Capybara::Selenium::SafariNode < Capybara::Selenium::Node
|
|
40
44
|
end
|
41
45
|
|
42
46
|
def disabled?
|
43
|
-
|
44
|
-
|
45
|
-
# workaround for safaridriver reporting elements as enabled when they are nested in disabling elements
|
46
|
-
if %w[option optgroup].include? tag_name
|
47
|
-
return true if self[:disabled] == 'true'
|
48
|
-
|
49
|
-
find_xpath('parent::*[self::optgroup or self::select]')[0].disabled?
|
50
|
-
else
|
51
|
-
!find_xpath(DISABLED_BY_FIELDSET_XPATH).empty?
|
52
|
-
end
|
47
|
+
driver.evaluate_script("arguments[0].matches(':disabled, select:disabled *')", self)
|
53
48
|
end
|
54
49
|
|
55
50
|
def set_file(value) # rubocop:disable Naming/AccessorMethodName
|
@@ -12,7 +12,7 @@ module Capybara
|
|
12
12
|
|
13
13
|
def request(&block)
|
14
14
|
ssl? ? https_request(&block) : http_request(&block)
|
15
|
-
rescue *TRY_HTTPS_ERRORS #
|
15
|
+
rescue *TRY_HTTPS_ERRORS # _rubocop:disable Naming/RescuedExceptionsVariableName
|
16
16
|
res = https_request(&block)
|
17
17
|
@ssl = true
|
18
18
|
res
|
@@ -25,11 +25,15 @@ module Capybara
|
|
25
25
|
private
|
26
26
|
|
27
27
|
def http_request(&block)
|
28
|
-
|
28
|
+
make_request(read_timeout: 2, &block)
|
29
29
|
end
|
30
30
|
|
31
31
|
def https_request(&block)
|
32
|
-
|
32
|
+
make_request(ssl_options, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def make_request(**options, &block)
|
36
|
+
Net::HTTP.start(@host, @port, options.merge(max_retries: 0), &block)
|
33
37
|
end
|
34
38
|
|
35
39
|
def ssl_options
|
data/lib/capybara/session.rb
CHANGED
@@ -77,7 +77,7 @@ module Capybara
|
|
77
77
|
def initialize(mode, app = nil)
|
78
78
|
raise TypeError, 'The second parameter to Session::new should be a rack app if passed.' if app && !app.respond_to?(:call)
|
79
79
|
|
80
|
-
@@instance_created = true
|
80
|
+
@@instance_created = true # rubocop:disable Style/ClassVars
|
81
81
|
@mode = mode
|
82
82
|
@app = app
|
83
83
|
if block_given?
|
@@ -807,7 +807,7 @@ module Capybara
|
|
807
807
|
|
808
808
|
private
|
809
809
|
|
810
|
-
@@instance_created = false
|
810
|
+
@@instance_created = false # rubocop:disable Style/ClassVars
|
811
811
|
|
812
812
|
def driver_args(args)
|
813
813
|
args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg }
|
@@ -30,7 +30,7 @@ Capybara::SpecHelper.spec '#all' do
|
|
30
30
|
|
31
31
|
it 'should accept an XPath instance', :exact_false do
|
32
32
|
@session.visit('/form')
|
33
|
-
@xpath = Capybara::Selector
|
33
|
+
@xpath = Capybara::Selector.new(:fillable_field, config: {}, format: :xpath).call('Name')
|
34
34
|
expect(@xpath).to be_a(::XPath::Union)
|
35
35
|
@result = @session.all(@xpath).map(&:value)
|
36
36
|
expect(@result).to include('Smith', 'John', 'John Smith')
|
@@ -235,7 +235,7 @@ Capybara::SpecHelper.spec '#find' do
|
|
235
235
|
|
236
236
|
it 'should accept an XPath instance' do
|
237
237
|
@session.visit('/form')
|
238
|
-
@xpath = Capybara::Selector
|
238
|
+
@xpath = Capybara::Selector.new(:fillable_field, config: {}, format: :xpath).call('First Name')
|
239
239
|
expect(@xpath).to be_a(::XPath::Union)
|
240
240
|
expect(@session.find(@xpath).value).to eq('John')
|
241
241
|
end
|
@@ -24,7 +24,7 @@ Capybara::SpecHelper.spec '#first' do
|
|
24
24
|
|
25
25
|
it 'should accept an XPath instance' do
|
26
26
|
@session.visit('/form')
|
27
|
-
@xpath = Capybara::Selector
|
27
|
+
@xpath = Capybara::Selector.new(:fillable_field, config: {}, format: :xpath).call('First Name')
|
28
28
|
expect(@xpath).to be_a(::XPath::Union)
|
29
29
|
expect(@session.first(@xpath).value).to eq('John')
|
30
30
|
end
|
@@ -146,7 +146,9 @@ Capybara::SpecHelper.spec '#has_css?' do
|
|
146
146
|
context 'with count' do
|
147
147
|
it 'should be true if the content occurs the given number of times' do
|
148
148
|
expect(@session).to have_css('p', count: 3)
|
149
|
+
expect(@session).to have_css('p').exactly(3).times
|
149
150
|
expect(@session).to have_css('p a#foo', count: 1)
|
151
|
+
expect(@session).to have_css('p a#foo').once
|
150
152
|
expect(@session).to have_css('p a.doesnotexist', count: 0)
|
151
153
|
expect(@session).to have_css('li', class: /guitar|drummer/, count: 4)
|
152
154
|
expect(@session).to have_css('li', id: /john|paul/, class: /guitar|drummer/, count: 2)
|
@@ -161,6 +163,7 @@ Capybara::SpecHelper.spec '#has_css?' do
|
|
161
163
|
|
162
164
|
it 'should be false if the content occurs a different number of times than the given' do
|
163
165
|
expect(@session).not_to have_css('p', count: 6)
|
166
|
+
expect(@session).not_to have_css('p').exactly(5).times
|
164
167
|
expect(@session).not_to have_css('p a#foo', count: 2)
|
165
168
|
expect(@session).not_to have_css('p a.doesnotexist', count: 1)
|
166
169
|
end
|
@@ -175,6 +178,7 @@ Capybara::SpecHelper.spec '#has_css?' do
|
|
175
178
|
it 'should be true when content occurs same or fewer times than given' do
|
176
179
|
expect(@session).to have_css('h2.head', maximum: 5) # edge case
|
177
180
|
expect(@session).to have_css('h2', maximum: 10)
|
181
|
+
expect(@session).to have_css('h2').at_most(10).times
|
178
182
|
expect(@session).to have_css('p a.doesnotexist', maximum: 1)
|
179
183
|
expect(@session).to have_css('p a.doesnotexist', maximum: 0)
|
180
184
|
end
|
@@ -182,6 +186,7 @@ Capybara::SpecHelper.spec '#has_css?' do
|
|
182
186
|
it 'should be false when content occurs more times than given' do
|
183
187
|
expect(@session).not_to have_css('h2.head', maximum: 4) # edge case
|
184
188
|
expect(@session).not_to have_css('h2', maximum: 3)
|
189
|
+
expect(@session).not_to have_css('h2').at_most(3).times
|
185
190
|
expect(@session).not_to have_css('p', maximum: 1)
|
186
191
|
end
|
187
192
|
|
@@ -195,12 +200,14 @@ Capybara::SpecHelper.spec '#has_css?' do
|
|
195
200
|
it 'should be true when content occurs same or more times than given' do
|
196
201
|
expect(@session).to have_css('h2.head', minimum: 5) # edge case
|
197
202
|
expect(@session).to have_css('h2', minimum: 3)
|
203
|
+
expect(@session).to have_css('h2').at_least(2).times
|
198
204
|
expect(@session).to have_css('p a.doesnotexist', minimum: 0)
|
199
205
|
end
|
200
206
|
|
201
207
|
it 'should be false when content occurs fewer times than given' do
|
202
208
|
expect(@session).not_to have_css('h2.head', minimum: 6) # edge case
|
203
209
|
expect(@session).not_to have_css('h2', minimum: 8)
|
210
|
+
expect(@session).not_to have_css('h2').at_least(8).times
|
204
211
|
expect(@session).not_to have_css('p', minimum: 10)
|
205
212
|
expect(@session).not_to have_css('p a.doesnotexist', minimum: 1)
|
206
213
|
end
|
@@ -166,12 +166,14 @@ Capybara::SpecHelper.spec '#has_text?' do
|
|
166
166
|
it 'should be true if the text occurs the given number of times' do
|
167
167
|
@session.visit('/with_count')
|
168
168
|
expect(@session).to have_text('count', count: 2)
|
169
|
+
expect(@session).to have_text('count').exactly(2).times
|
169
170
|
end
|
170
171
|
|
171
172
|
it 'should be false if the text occurs a different number of times than the given' do
|
172
173
|
@session.visit('/with_count')
|
173
174
|
expect(@session).not_to have_text('count', count: 0)
|
174
175
|
expect(@session).not_to have_text('count', count: 1)
|
176
|
+
expect(@session).not_to have_text('count').once
|
175
177
|
expect(@session).not_to have_text(/count/, count: 3)
|
176
178
|
end
|
177
179
|
|
@@ -186,12 +188,14 @@ Capybara::SpecHelper.spec '#has_text?' do
|
|
186
188
|
it 'should be true when text occurs same or fewer times than given' do
|
187
189
|
@session.visit('/with_count')
|
188
190
|
expect(@session).to have_text('count', maximum: 2)
|
191
|
+
expect(@session).to have_text('count').at_most(2).times
|
189
192
|
expect(@session).to have_text(/count/, maximum: 3)
|
190
193
|
end
|
191
194
|
|
192
195
|
it 'should be false when text occurs more times than given' do
|
193
196
|
@session.visit('/with_count')
|
194
197
|
expect(@session).not_to have_text('count', maximum: 1)
|
198
|
+
expect(@session).not_to have_text('count').at_most(1).times
|
195
199
|
expect(@session).not_to have_text('count', maximum: 0)
|
196
200
|
end
|
197
201
|
|
@@ -206,12 +210,14 @@ Capybara::SpecHelper.spec '#has_text?' do
|
|
206
210
|
it 'should be true when text occurs same or more times than given' do
|
207
211
|
@session.visit('/with_count')
|
208
212
|
expect(@session).to have_text('count', minimum: 2)
|
213
|
+
expect(@session).to have_text('count').at_least(2).times
|
209
214
|
expect(@session).to have_text(/count/, minimum: 0)
|
210
215
|
end
|
211
216
|
|
212
217
|
it 'should be false when text occurs fewer times than given' do
|
213
218
|
@session.visit('/with_count')
|
214
219
|
expect(@session).not_to have_text('count', minimum: 3)
|
220
|
+
expect(@session).not_to have_text('count').at_least(3).times
|
215
221
|
end
|
216
222
|
|
217
223
|
it 'should coerce minimum to an integer' do
|