capybara 3.18.0 → 3.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/History.md +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
         |