capybara 3.34.0 → 3.35.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 +21 -3
- data/lib/capybara/node/base.rb +6 -6
- data/lib/capybara/node/matchers.rb +1 -1
- data/lib/capybara/queries/selector_query.rb +24 -8
- data/lib/capybara/registrations/drivers.rb +18 -12
- data/lib/capybara/registrations/servers.rb +1 -1
- data/lib/capybara/result.rb +3 -3
- data/lib/capybara/selector.rb +2 -2
- data/lib/capybara/selector/builders/css_builder.rb +1 -1
- data/lib/capybara/selector/builders/xpath_builder.rb +3 -1
- data/lib/capybara/selector/definition/button.rb +23 -14
- data/lib/capybara/selector/definition/table_row.rb +1 -1
- data/lib/capybara/selenium/atoms/src/isDisplayed.js +1 -1
- data/lib/capybara/selenium/driver.rb +20 -4
- data/lib/capybara/selenium/extensions/find.rb +1 -1
- data/lib/capybara/selenium/logger_suppressor.rb +1 -1
- data/lib/capybara/selenium/nodes/chrome_node.rb +3 -3
- data/lib/capybara/selenium/nodes/firefox_node.rb +6 -1
- data/lib/capybara/server/animation_disabler.rb +6 -2
- data/lib/capybara/spec/session/has_button_spec.rb +33 -0
- data/lib/capybara/spec/session/html_spec.rb +1 -1
- data/lib/capybara/spec/session/node_spec.rb +1 -1
- data/lib/capybara/spec/session/refresh_spec.rb +1 -1
- data/lib/capybara/spec/session/window/switch_to_window_spec.rb +1 -1
- data/lib/capybara/spec/session/window/window_opened_by_spec.rb +1 -1
- data/lib/capybara/spec/session/window/window_spec.rb +1 -1
- data/lib/capybara/spec/session/window/windows_spec.rb +1 -1
- data/lib/capybara/spec/test_app.rb +1 -0
- data/lib/capybara/spec/views/form.erb +4 -0
- data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
- data/lib/capybara/spec/views/with_js.erb +1 -0
- data/lib/capybara/version.rb +1 -1
- data/spec/selector_spec.rb +15 -1
- data/spec/selenium_spec_chrome.rb +39 -18
- data/spec/selenium_spec_chrome_remote.rb +5 -1
- data/spec/selenium_spec_firefox.rb +15 -13
- data/spec/server_spec.rb +6 -2
- data/spec/shared_selenium_session.rb +14 -0
- metadata +46 -11
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 00bf1cfd1e72093422fb61df1dae86d72948e8026d57fb00084d5bedceb0967e
         | 
| 4 | 
            +
              data.tar.gz: 65a8c6b66d1ef91586ca39f7eabcf800019f853e33e087d1bb65f02a85140c8b
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: f961de052aa75cb3e3a941ea031d40d6d0f1fc2a9a80190c7dbe2f7d8e96b462fa71d4ed96d4dc45687e2baaf151579ca4508a2753a6e2073814857e2dee9bb4
         | 
| 7 | 
            +
              data.tar.gz: 4b02c84f7bd1d9da419e3f43848da87290ba303541df516e6217fd3fc2e4cf5263e212e9a4a5329c8505b2f6111c0ce6ea17537ea3099b04f12c9b662d364088
         | 
    
        data/History.md
    CHANGED
    
    | @@ -1,13 +1,31 @@ | |
| 1 | 
            +
            # Version 3.35.0
         | 
| 2 | 
            +
            Release date: 2020-01-25
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            ### Added
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            * Support Regexp matching for individual class names in :class filter passed an Array
         | 
| 7 | 
            +
            * Animation disabler now supports JQuery animation disabling when JQuery loaded from body [Chien-Wei Huang]
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            ### Fixed
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            * :button selector type use with `enable_aria_role` [Sean Doyle]
         | 
| 12 | 
            +
            * <label> elements don't associate with aria-role buttons
         | 
| 13 | 
            +
            * Ignore Selenium::WebDriver::Error::InvalidSessionIdError when quitting driver [Robin Daugherty]
         | 
| 14 | 
            +
            * Firefox: Don't click input when sending keys if already focused
         | 
| 15 | 
            +
            * Miscellaneous issues with selenium-webdriver 4.0.0.alphas
         | 
| 16 | 
            +
            * Nil return error in node details optimizations
         | 
| 17 | 
            +
            * Animation disabler now inserts XHTML compliant content [Dale Morgan]
         | 
| 18 | 
            +
             | 
| 1 19 | 
             
            # Version 3.34.0
         | 
| 2 20 | 
             
            Release date: 2020-11-26
         | 
| 3 21 |  | 
| 4 22 | 
             
            ### Added
         | 
| 5 23 |  | 
| 6 24 | 
             
            * Ability to fill in with emoji when using Chrome with selenium driver (Firefox already worked)
         | 
| 7 | 
            -
            * Current path  | 
| 25 | 
            +
            * Current path assertions/expectations accept optional filter block
         | 
| 8 26 | 
             
            * Animation disabler now specifies `scroll-behavior: auto;` [Nathan Broadbent]
         | 
| 9 27 | 
             
            * :button selector can now find elements by label text [Sean Doyle]
         | 
| 10 | 
            -
            * `Session#send_keys` to send keys to the current element with focus in drivers that support the | 
| 28 | 
            +
            * `Session#send_keys` to send keys to the current element with focus in drivers that support the
         | 
| 11 29 | 
             
              concept of a current element [Sean Doyle]
         | 
| 12 30 |  | 
| 13 31 | 
             
            ### Changed
         | 
| @@ -21,7 +39,7 @@ Release date: 2020-11-26 | |
| 21 39 | 
             
            * Missing `readonly?` added to Node::Simple
         | 
| 22 40 | 
             
            * Selenium version detection when loaded via alternate method [Joel Hawksley]
         | 
| 23 41 | 
             
            * Connection count issue if REQUEST_URI value changed by app [Blake Williams]
         | 
| 24 | 
            -
            * Maintain URI fragment when redirecting in rack-test driver | 
| 42 | 
            +
            * Maintain URI fragment when redirecting in rack-test driver
         | 
| 25 43 | 
             
            * Text query error message [Wojciech Wnętrzak]
         | 
| 26 44 | 
             
            * Checking a checkbox/radio button with `allow_label_click` now works if there are multiple labels (Issue #2421)
         | 
| 27 45 | 
             
            * `drop` with `Pathname` (Issue #2424)[Máximo Mussini]
         | 
    
        data/lib/capybara/node/base.rb
    CHANGED
    
    | @@ -103,19 +103,19 @@ module Capybara | |
| 103 103 |  | 
| 104 104 | 
             
                  # @api private
         | 
| 105 105 | 
             
                  def find_css(css, **options)
         | 
| 106 | 
            -
                    if base.method(:find_css).arity  | 
| 107 | 
            -
                      base.find_css(css, **options)
         | 
| 108 | 
            -
                    else
         | 
| 106 | 
            +
                    if base.method(:find_css).arity == 1
         | 
| 109 107 | 
             
                      base.find_css(css)
         | 
| 108 | 
            +
                    else
         | 
| 109 | 
            +
                      base.find_css(css, **options)
         | 
| 110 110 | 
             
                    end
         | 
| 111 111 | 
             
                  end
         | 
| 112 112 |  | 
| 113 113 | 
             
                  # @api private
         | 
| 114 114 | 
             
                  def find_xpath(xpath, **options)
         | 
| 115 | 
            -
                    if base.method(:find_xpath).arity  | 
| 116 | 
            -
                      base.find_xpath(xpath, **options)
         | 
| 117 | 
            -
                    else
         | 
| 115 | 
            +
                    if base.method(:find_xpath).arity == 1
         | 
| 118 116 | 
             
                      base.find_xpath(xpath)
         | 
| 117 | 
            +
                    else
         | 
| 118 | 
            +
                      base.find_xpath(xpath, **options)
         | 
| 119 119 | 
             
                    end
         | 
| 120 120 | 
             
                  end
         | 
| 121 121 |  | 
| @@ -386,7 +386,7 @@ module Capybara | |
| 386 386 | 
             
                  #
         | 
| 387 387 | 
             
                  #     page.has_field?('Email', type: 'email')
         | 
| 388 388 | 
             
                  #
         | 
| 389 | 
            -
                  #  | 
| 389 | 
            +
                  # NOTE: 'textarea' and 'select' are valid type values, matching the associated tag names.
         | 
| 390 390 | 
             
                  #
         | 
| 391 391 | 
             
                  # @param [String] locator                  The label, name or id of a field to check for
         | 
| 392 392 | 
             
                  # @option options [String, Regexp] :with   The text content of the field or a Regexp to match
         | 
| @@ -239,16 +239,16 @@ module Capybara | |
| 239 239 |  | 
| 240 240 | 
             
                    case selector_format
         | 
| 241 241 | 
             
                    when :css
         | 
| 242 | 
            -
                      if node.method(:find_css).arity  | 
| 243 | 
            -
                        node.find_css(css, **hints)
         | 
| 244 | 
            -
                      else
         | 
| 242 | 
            +
                      if node.method(:find_css).arity == 1
         | 
| 245 243 | 
             
                        node.find_css(css)
         | 
| 244 | 
            +
                      else
         | 
| 245 | 
            +
                        node.find_css(css, **hints)
         | 
| 246 246 | 
             
                      end
         | 
| 247 247 | 
             
                    when :xpath
         | 
| 248 | 
            -
                      if node.method(:find_xpath).arity  | 
| 249 | 
            -
                        node.find_xpath(xpath(exact), **hints)
         | 
| 250 | 
            -
                      else
         | 
| 248 | 
            +
                      if node.method(:find_xpath).arity == 1
         | 
| 251 249 | 
             
                        node.find_xpath(xpath(exact))
         | 
| 250 | 
            +
                      else
         | 
| 251 | 
            +
                        node.find_xpath(xpath(exact), **hints)
         | 
| 252 252 | 
             
                      end
         | 
| 253 253 | 
             
                    else
         | 
| 254 254 | 
             
                      raise ArgumentError, "Unknown format: #{selector_format}"
         | 
| @@ -482,9 +482,25 @@ module Capybara | |
| 482 482 | 
             
                  end
         | 
| 483 483 |  | 
| 484 484 | 
             
                  def matches_class_filter?(node)
         | 
| 485 | 
            -
                    return true unless use_default_class_filter? &&  | 
| 485 | 
            +
                    return true unless use_default_class_filter? && need_to_process_classes?
         | 
| 486 486 |  | 
| 487 | 
            -
                    options[:class]. | 
| 487 | 
            +
                    if options[:class].is_a? Regexp
         | 
| 488 | 
            +
                      options[:class].match? node[:class]
         | 
| 489 | 
            +
                    else
         | 
| 490 | 
            +
                      classes = (node[:class] || '').split
         | 
| 491 | 
            +
                      options[:class].select { |c| c.is_a? Regexp }.all? do |r|
         | 
| 492 | 
            +
                        classes.any? { |cls| r.match? cls }
         | 
| 493 | 
            +
                      end
         | 
| 494 | 
            +
                    end
         | 
| 495 | 
            +
                  end
         | 
| 496 | 
            +
             | 
| 497 | 
            +
                  def need_to_process_classes?
         | 
| 498 | 
            +
                    case options[:class]
         | 
| 499 | 
            +
                    when Regexp then true
         | 
| 500 | 
            +
                    when Array then options[:class].any?(Regexp)
         | 
| 501 | 
            +
                    else
         | 
| 502 | 
            +
                      false
         | 
| 503 | 
            +
                    end
         | 
| 488 504 | 
             
                  end
         | 
| 489 505 |  | 
| 490 506 | 
             
                  def matches_style_filter?(node)
         | 
| @@ -9,28 +9,34 @@ Capybara.register_driver :selenium do |app| | |
| 9 9 | 
             
            end
         | 
| 10 10 |  | 
| 11 11 | 
             
            Capybara.register_driver :selenium_headless do |app|
         | 
| 12 | 
            -
              Capybara::Selenium::Driver.load_selenium
         | 
| 13 | 
            -
               | 
| 14 | 
            -
              browser_options. | 
| 15 | 
            -
             | 
| 12 | 
            +
              version = Capybara::Selenium::Driver.load_selenium
         | 
| 13 | 
            +
              options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
         | 
| 14 | 
            +
              browser_options = ::Selenium::WebDriver::Firefox::Options.new.tap do |opts|
         | 
| 15 | 
            +
                opts.add_argument '-headless'
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
              Capybara::Selenium::Driver.new(app, **Hash[:browser => :firefox, options_key => browser_options])
         | 
| 16 18 | 
             
            end
         | 
| 17 19 |  | 
| 18 20 | 
             
            Capybara.register_driver :selenium_chrome do |app|
         | 
| 19 | 
            -
              Capybara::Selenium::Driver.load_selenium
         | 
| 21 | 
            +
              version = Capybara::Selenium::Driver.load_selenium
         | 
| 22 | 
            +
              options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
         | 
| 20 23 | 
             
              browser_options = ::Selenium::WebDriver::Chrome::Options.new.tap do |opts|
         | 
| 21 24 | 
             
                # Workaround https://bugs.chromium.org/p/chromedriver/issues/detail?id=2650&q=load&sort=-id&colspec=ID%20Status%20Pri%20Owner%20Summary
         | 
| 22 | 
            -
                opts. | 
| 25 | 
            +
                opts.add_argument('--disable-site-isolation-trials')
         | 
| 23 26 | 
             
              end
         | 
| 24 | 
            -
             | 
| 27 | 
            +
             | 
| 28 | 
            +
              Capybara::Selenium::Driver.new(app, **Hash[:browser => :firefox, options_key => browser_options])
         | 
| 25 29 | 
             
            end
         | 
| 26 30 |  | 
| 27 31 | 
             
            Capybara.register_driver :selenium_chrome_headless do |app|
         | 
| 28 | 
            -
              Capybara::Selenium::Driver.load_selenium
         | 
| 32 | 
            +
              version = Capybara::Selenium::Driver.load_selenium
         | 
| 33 | 
            +
              options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
         | 
| 29 34 | 
             
              browser_options = ::Selenium::WebDriver::Chrome::Options.new.tap do |opts|
         | 
| 30 | 
            -
                opts. | 
| 31 | 
            -
                opts. | 
| 35 | 
            +
                opts.add_argument('--headless')
         | 
| 36 | 
            +
                opts.add_argument('--disable-gpu') if Gem.win_platform?
         | 
| 32 37 | 
             
                # Workaround https://bugs.chromium.org/p/chromedriver/issues/detail?id=2650&q=load&sort=-id&colspec=ID%20Status%20Pri%20Owner%20Summary
         | 
| 33 | 
            -
                opts. | 
| 38 | 
            +
                opts.add_argument('--disable-site-isolation-trials')
         | 
| 34 39 | 
             
              end
         | 
| 35 | 
            -
             | 
| 40 | 
            +
             | 
| 41 | 
            +
              Capybara::Selenium::Driver.new(app, **Hash[:browser => :firefox, options_key => browser_options])
         | 
| 36 42 | 
             
            end
         | 
| @@ -32,7 +32,7 @@ Capybara.register_server :puma do |app, port, host, **options| | |
| 32 32 | 
             
              events = conf.options[:Silent] ? ::Puma::Events.strings : ::Puma::Events.stdio
         | 
| 33 33 |  | 
| 34 34 | 
             
              puma_ver = Gem::Version.new(Puma::Const::PUMA_VERSION)
         | 
| 35 | 
            -
              require_relative 'patches/puma_ssl' if  | 
| 35 | 
            +
              require_relative 'patches/puma_ssl' if Gem::Requirement.new('>=4.0.0', '< 4.1.0').satisfied_by?(puma_ver)
         | 
| 36 36 |  | 
| 37 37 | 
             
              events.log 'Capybara starting Puma...'
         | 
| 38 38 | 
             
              events.log "* Version #{Puma::Const::PUMA_VERSION} , codename: #{Puma::Const::CODE_NAME}"
         | 
    
        data/lib/capybara/result.rb
    CHANGED
    
    | @@ -54,10 +54,10 @@ module Capybara | |
| 54 54 | 
             
                  idx, length = args
         | 
| 55 55 | 
             
                  max_idx = case idx
         | 
| 56 56 | 
             
                  when Integer
         | 
| 57 | 
            -
                    if  | 
| 58 | 
            -
                      length.nil? ? idx : idx + length - 1
         | 
| 59 | 
            -
                    else
         | 
| 57 | 
            +
                    if idx.negative?
         | 
| 60 58 | 
             
                      nil
         | 
| 59 | 
            +
                    else
         | 
| 60 | 
            +
                      length.nil? ? idx : idx + length - 1
         | 
| 61 61 | 
             
                    end
         | 
| 62 62 | 
             
                  when Range
         | 
| 63 63 | 
             
                    # idx.max is broken with beginless ranges
         | 
    
        data/lib/capybara/selector.rb
    CHANGED
    
    | @@ -7,7 +7,7 @@ require 'capybara/selector/definition' | |
| 7 7 | 
             
            #
         | 
| 8 8 | 
             
            # All Selectors below support the listed selector specific filters in addition to the following system-wide filters
         | 
| 9 9 | 
             
            #   * :id (String, Regexp, XPath::Expression) - Matches the id attribute
         | 
| 10 | 
            -
            #   * :class (String, Array<String>, Regexp, XPath::Expression) - Matches the class(es) provided
         | 
| 10 | 
            +
            #   * :class (String, Array<String | Regexp>, Regexp, XPath::Expression) - Matches the class(es) provided
         | 
| 11 11 | 
             
            #   * :style (String, Regexp, Hash<String, String>) - Match on elements style
         | 
| 12 12 | 
             
            #   * :above (Element) - Match elements above the passed element on the page
         | 
| 13 13 | 
             
            #   * :below (Element) - Match elements below the passed element on the page
         | 
| @@ -171,7 +171,7 @@ require 'capybara/selector/definition' | |
| 171 171 | 
             
            #   * Filters:
         | 
| 172 172 | 
             
            #       * :\<any> (String, Regexp) - Match on any specified element attribute
         | 
| 173 173 | 
             
            #
         | 
| 174 | 
            -
            class Capybara::Selector; end
         | 
| 174 | 
            +
            class Capybara::Selector; end # rubocop:disable Lint/EmptyClass
         | 
| 175 175 |  | 
| 176 176 | 
             
            Capybara::Selector::FilterSet.add(:_field) do
         | 
| 177 177 | 
             
              node_filter(:checked, :boolean) { |node, value| !(value ^ node.checked?) }
         | 
| @@ -74,7 +74,7 @@ module Capybara | |
| 74 74 | 
             
                        end.join
         | 
| 75 75 | 
             
                      end
         | 
| 76 76 | 
             
                    else
         | 
| 77 | 
            -
                      cls = Array(classes).group_by { |cl| cl.match?(/^!(?!!!)/) }
         | 
| 77 | 
            +
                      cls = Array(classes).reject { |c| c.is_a? Regexp }.group_by { |cl| cl.match?(/^!(?!!!)/) }
         | 
| 78 78 | 
             
                      [(cls[false].to_a.map { |cl| ".#{Capybara::Selector::CSS.escape(cl.sub(/^!!/, ''))}" } +
         | 
| 79 79 | 
             
                      cls[true].to_a.map { |cl| ":not(.#{Capybara::Selector::CSS.escape(cl.slice(1..-1))})" }).join]
         | 
| 80 80 | 
             
                    end
         | 
| @@ -15,6 +15,8 @@ module Capybara | |
| 15 15 | 
             
                  def add_attribute_conditions(**conditions)
         | 
| 16 16 | 
             
                    @expression = conditions.inject(expression) do |xp, (name, value)|
         | 
| 17 17 | 
             
                      conditions = name == :class ? class_conditions(value) : attribute_conditions(name => value)
         | 
| 18 | 
            +
                      return xp if conditions.nil?
         | 
| 19 | 
            +
             | 
| 18 20 | 
             
                      if xp.is_a? XPath::Expression
         | 
| 19 21 | 
             
                        xp[conditions]
         | 
| 20 22 | 
             
                      else
         | 
| @@ -47,7 +49,7 @@ module Capybara | |
| 47 49 | 
             
                    when XPath::Expression, Regexp
         | 
| 48 50 | 
             
                      attribute_conditions(class: classes)
         | 
| 49 51 | 
             
                    else
         | 
| 50 | 
            -
                      Array(classes).map do |klass|
         | 
| 52 | 
            +
                      Array(classes).reject { |c| c.is_a? Regexp }.map do |klass|
         | 
| 51 53 | 
             
                        if klass.match?(/^!(?!!!)/)
         | 
| 52 54 | 
             
                          !XPath.attr(:class).contains_word(klass.slice(1..-1))
         | 
| 53 55 | 
             
                        else
         | 
| @@ -4,32 +4,29 @@ Capybara.add_selector(:button, locator_type: [String, Symbol]) do | |
| 4 4 | 
             
              xpath(:value, :title, :type, :name) do |locator, **options|
         | 
| 5 5 | 
             
                input_btn_xpath = XPath.descendant(:input)[XPath.attr(:type).one_of('submit', 'reset', 'image', 'button')]
         | 
| 6 6 | 
             
                btn_xpath = XPath.descendant(:button)
         | 
| 7 | 
            -
                 | 
| 7 | 
            +
                aria_btn_xpath = XPath.descendant[XPath.attr(:role).equals('button')]
         | 
| 8 8 | 
             
                image_btn_xpath = XPath.descendant(:input)[XPath.attr(:type) == 'image']
         | 
| 9 9 |  | 
| 10 10 | 
             
                unless locator.nil?
         | 
| 11 11 | 
             
                  locator = locator.to_s
         | 
| 12 | 
            -
                  locator_matchers =  | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
                                     (XPath.attr(:id) == XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for))
         | 
| 17 | 
            -
                  locator_matchers |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
         | 
| 18 | 
            -
                  locator_matchers |= XPath.attr(test_id) == locator if test_id
         | 
| 12 | 
            +
                  locator_matchers = combine_locators(locator, config: self)
         | 
| 13 | 
            +
                  btn_matchers = locator_matchers |
         | 
| 14 | 
            +
                                 XPath.string.n.is(locator) |
         | 
| 15 | 
            +
                                 XPath.descendant(:img)[XPath.attr(:alt).is(locator)]
         | 
| 19 16 |  | 
| 20 17 | 
             
                  input_btn_xpath = input_btn_xpath[locator_matchers] + locate_label(locator).descendant(input_btn_xpath)
         | 
| 21 | 
            -
             | 
| 22 | 
            -
                   | 
| 23 | 
            -
                                        XPath.string.n.is(locator) |
         | 
| 24 | 
            -
                                        XPath.descendant(:img)[XPath.attr(:alt).is(locator)]
         | 
| 25 | 
            -
                                       ] + locate_label(locator).descendant(btn_xpath)
         | 
| 18 | 
            +
                  btn_xpath = btn_xpath[btn_matchers] + locate_label(locator).descendant(btn_xpath)
         | 
| 19 | 
            +
                  aria_btn_xpath = aria_btn_xpath[btn_matchers]
         | 
| 26 20 |  | 
| 27 21 | 
             
                  alt_matches = XPath.attr(:alt).is(locator)
         | 
| 28 22 | 
             
                  alt_matches |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
         | 
| 29 23 | 
             
                  image_btn_xpath = image_btn_xpath[alt_matches] + locate_label(locator).descendant(image_btn_xpath)
         | 
| 30 24 | 
             
                end
         | 
| 31 25 |  | 
| 32 | 
            -
                 | 
| 26 | 
            +
                btn_xpaths = [input_btn_xpath, btn_xpath, image_btn_xpath]
         | 
| 27 | 
            +
                btn_xpaths << aria_btn_xpath if enable_aria_role
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                %i[value title type].inject(btn_xpaths.inject(&:union)) do |memo, ef|
         | 
| 33 30 | 
             
                  memo.where(find_by_attr(ef, options[ef]))
         | 
| 34 31 | 
             
                end
         | 
| 35 32 | 
             
              end
         | 
| @@ -51,4 +48,16 @@ Capybara.add_selector(:button, locator_type: [String, Symbol]) do | |
| 51 48 | 
             
              describe_node_filters do |disabled: nil, **|
         | 
| 52 49 | 
             
                ' that is disabled' if disabled == true
         | 
| 53 50 | 
             
              end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
              def combine_locators(locator, config:)
         | 
| 53 | 
            +
                [
         | 
| 54 | 
            +
                  XPath.attr(:id).equals(locator),
         | 
| 55 | 
            +
                  XPath.attr(:name).equals(locator),
         | 
| 56 | 
            +
                  XPath.attr(:value).is(locator),
         | 
| 57 | 
            +
                  XPath.attr(:title).is(locator),
         | 
| 58 | 
            +
                  (XPath.attr(:id) == XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for)),
         | 
| 59 | 
            +
                  (XPath.attr(:'aria-label').is(locator) if config.enable_aria_label),
         | 
| 60 | 
            +
                  (XPath.attr(test_id) == locator if config.test_id)
         | 
| 61 | 
            +
                ].compact.inject(&:|)
         | 
| 62 | 
            +
              end
         | 
| 54 63 | 
             
            end
         | 
| @@ -9,7 +9,7 @@ Capybara.add_selector(:table_row, locator_type: [Array, Hash]) do | |
| 9 9 | 
             
                    cell_xp = XPath.descendant(:td)[
         | 
| 10 10 | 
             
                      XPath.string.n.is(cell) & XPath.position.equals(header_xp.preceding_sibling.count.plus(1))
         | 
| 11 11 | 
             
                    ]
         | 
| 12 | 
            -
                    xp | 
| 12 | 
            +
                    xp.where(cell_xp)
         | 
| 13 13 | 
             
                  end
         | 
| 14 14 | 
             
                else
         | 
| 15 15 | 
             
                  initial_td = XPath.descendant(:td)[XPath.string.n.is(locator.shift)]
         | 
| @@ -158,7 +158,7 @@ | |
| 158 158 | 
             
                  // the overflow style of the body, and the body is really overflow:visible.
         | 
| 159 159 | 
             
                  var overflowElem = e;
         | 
| 160 160 | 
             
                  if (htmlOverflowStyle == "visible") {
         | 
| 161 | 
            -
                    //  | 
| 161 | 
            +
                    // NOTE: bodyElem will be null/undefined in SVG documents.
         | 
| 162 162 | 
             
                    if (e == htmlElem && bodyElem) {
         | 
| 163 163 | 
             
                      overflowElem = bodyElem;
         | 
| 164 164 | 
             
                    } else if (e == bodyElem) {
         | 
| @@ -12,9 +12,13 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base | |
| 12 12 | 
             
                clear_session_storage: nil
         | 
| 13 13 | 
             
              }.freeze
         | 
| 14 14 | 
             
              SPECIAL_OPTIONS = %i[browser clear_local_storage clear_session_storage timeout native_displayed].freeze
         | 
| 15 | 
            +
              CAPS_VERSION = Gem::Requirement.new('~> 4.0.0.alpha6')
         | 
| 16 | 
            +
             | 
| 15 17 | 
             
              attr_reader :app, :options
         | 
| 16 18 |  | 
| 17 19 | 
             
              class << self
         | 
| 20 | 
            +
                attr_reader :selenium_webdriver_version
         | 
| 21 | 
            +
             | 
| 18 22 | 
             
                def load_selenium
         | 
| 19 23 | 
             
                  require 'selenium-webdriver'
         | 
| 20 24 | 
             
                  require 'capybara/selenium/logger_suppressor'
         | 
| @@ -32,15 +36,18 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base | |
| 32 36 | 
             
                  # Selenium::WebDriver::VERSION. Ideally we'd
         | 
| 33 37 | 
             
                  # use the constant in all cases, but earlier versions
         | 
| 34 38 | 
             
                  # of `selenium-webdriver` didn't provide the constant.
         | 
| 35 | 
            -
                  selenium_webdriver_version =
         | 
| 39 | 
            +
                  @selenium_webdriver_version =
         | 
| 36 40 | 
             
                    if Gem.loaded_specs['selenium-webdriver']
         | 
| 37 41 | 
             
                      Gem.loaded_specs['selenium-webdriver'].version
         | 
| 38 42 | 
             
                    else
         | 
| 39 43 | 
             
                      Gem::Version.new(Selenium::WebDriver::VERSION)
         | 
| 40 44 | 
             
                    end
         | 
| 41 | 
            -
             | 
| 45 | 
            +
             | 
| 46 | 
            +
                  unless Gem::Requirement.new('>= 3.5.0').satisfied_by? @selenium_webdriver_version
         | 
| 42 47 | 
             
                    warn "Warning: You're using an unsupported version of selenium-webdriver, please upgrade."
         | 
| 43 48 | 
             
                  end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  @selenium_webdriver_version
         | 
| 44 51 | 
             
                rescue LoadError => e
         | 
| 45 52 | 
             
                  raise e unless e.message.include?('selenium-webdriver')
         | 
| 46 53 |  | 
| @@ -66,7 +73,15 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base | |
| 66 73 | 
             
                    end
         | 
| 67 74 | 
             
                  end
         | 
| 68 75 | 
             
                  processed_options = options.reject { |key, _val| SPECIAL_OPTIONS.include?(key) }
         | 
| 69 | 
            -
             | 
| 76 | 
            +
             | 
| 77 | 
            +
                  @browser = if options[:browser] == :firefox &&
         | 
| 78 | 
            +
                                RUBY_VERSION >= '3.0' &&
         | 
| 79 | 
            +
                                Capybara::Selenium::Driver.selenium_webdriver_version <= Gem::Version.new('4.0.0.alpha1')
         | 
| 80 | 
            +
                    # selenium-webdriver 3.x doesn't correctly pass options through for Firefox with Ruby 3 so workaround that
         | 
| 81 | 
            +
                    Selenium::WebDriver::Firefox::Driver.new(**processed_options)
         | 
| 82 | 
            +
                  else
         | 
| 83 | 
            +
                    Selenium::WebDriver.for(options[:browser], processed_options)
         | 
| 84 | 
            +
                  end
         | 
| 70 85 |  | 
| 71 86 | 
             
                  specialize_driver
         | 
| 72 87 | 
             
                  setup_exit_handler
         | 
| @@ -265,7 +280,8 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base | |
| 265 280 |  | 
| 266 281 | 
             
              def quit
         | 
| 267 282 | 
             
                @browser&.quit
         | 
| 268 | 
            -
              rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED
         | 
| 283 | 
            +
              rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED,
         | 
| 284 | 
            +
                     Selenium::WebDriver::Error::InvalidSessionIdError
         | 
| 269 285 | 
             
                # Browser must have already gone
         | 
| 270 286 | 
             
              rescue Selenium::WebDriver::Error::UnknownError => e
         | 
| 271 287 | 
             
                unless silenced_unknown_error_message?(e.message) # Most likely already gone
         | 
| @@ -28,7 +28,7 @@ module Capybara | |
| 28 28 | 
             
                    hints_js, functions = build_hints_js(uses_visibility, styles, position)
         | 
| 29 29 | 
             
                    return [] unless functions.any?
         | 
| 30 30 |  | 
| 31 | 
            -
                    es_context.execute_script(hints_js, elements).map! do |results|
         | 
| 31 | 
            +
                    (es_context.execute_script(hints_js, elements) || []).map! do |results|
         | 
| 32 32 | 
             
                      hint = {}
         | 
| 33 33 | 
             
                      hint[:style] = results.pop if functions.include?(:style_func)
         | 
| 34 34 | 
             
                      hint[:position] = results.pop if functions.include?(:position_func)
         | 
| @@ -118,15 +118,15 @@ private | |
| 118 118 | 
             
              end
         | 
| 119 119 |  | 
| 120 120 | 
             
              def chromedriver_fixed_actions_key_state?
         | 
| 121 | 
            -
                Gem:: | 
| 121 | 
            +
                Gem::Requirement.new('>= 76.0.3809.68').satisfied_by?(chromedriver_version)
         | 
| 122 122 | 
             
              end
         | 
| 123 123 |  | 
| 124 124 | 
             
              def chromedriver_supports_displayed_endpoint?
         | 
| 125 | 
            -
                Gem:: | 
| 125 | 
            +
                Gem::Requirement.new('>= 76.0.3809.25').satisfied_by?(chromedriver_version)
         | 
| 126 126 | 
             
              end
         | 
| 127 127 |  | 
| 128 128 | 
             
              def chromedriver_version
         | 
| 129 | 
            -
                capabilities['chrome']['chromedriverVersion'].split(' ')[0]
         | 
| 129 | 
            +
                Gem::Version.new(capabilities['chrome']['chromedriverVersion'].split(' ')[0]) # rubocop:disable Style/RedundantArgument
         | 
| 130 130 | 
             
              end
         | 
| 131 131 |  | 
| 132 132 | 
             
              def native_displayed?
         | 
| @@ -40,11 +40,16 @@ class Capybara::Selenium::FirefoxNode < Capybara::Selenium::Node | |
| 40 40 | 
             
                path_names.each { |path| native.send_keys(path) }
         | 
| 41 41 | 
             
              end
         | 
| 42 42 |  | 
| 43 | 
            +
              def focused?
         | 
| 44 | 
            +
                driver.evaluate_script('arguments[0] == document.activeElement', self)
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
             | 
| 43 47 | 
             
              def send_keys(*args)
         | 
| 44 48 | 
             
                # https://github.com/mozilla/geckodriver/issues/846
         | 
| 45 49 | 
             
                return super(*args.map { |arg| arg == :space ? ' ' : arg }) if args.none?(Array)
         | 
| 46 50 |  | 
| 47 | 
            -
                native.click
         | 
| 51 | 
            +
                native.click unless focused?
         | 
| 52 | 
            +
             | 
| 48 53 | 
             
                _send_keys(args).perform
         | 
| 49 54 | 
             
              end
         | 
| 50 55 |  | 
| @@ -40,11 +40,15 @@ module Capybara | |
| 40 40 | 
             
                  end
         | 
| 41 41 |  | 
| 42 42 | 
             
                  def insert_disable(html)
         | 
| 43 | 
            -
                    html.sub(%r{(</ | 
| 43 | 
            +
                    html.sub(%r{(</body>)}, "#{disable_markup}\\1")
         | 
| 44 44 | 
             
                  end
         | 
| 45 45 |  | 
| 46 46 | 
             
                  DISABLE_MARKUP_TEMPLATE = <<~HTML
         | 
| 47 | 
            -
                    <script | 
| 47 | 
            +
                    <script>
         | 
| 48 | 
            +
                    //<![CDATA[
         | 
| 49 | 
            +
                      (typeof jQuery !== 'undefined') && (jQuery.fx.off = true);
         | 
| 50 | 
            +
                    //]]>
         | 
| 51 | 
            +
                    </script>
         | 
| 48 52 | 
             
                    <style>
         | 
| 49 53 | 
             
                      %<selector>s, %<selector>s::before, %<selector>s::after {
         | 
| 50 54 | 
             
                         transition: none !important;
         | 
| @@ -46,9 +46,25 @@ Capybara::SpecHelper.spec '#has_button?' do | |
| 46 46 | 
             
                expect(@session).to have_button('ARIA button', enable_aria_role: true)
         | 
| 47 47 | 
             
              end
         | 
| 48 48 |  | 
| 49 | 
            +
              it 'should be false for a role=button within a label when enable_aria_role: true' do
         | 
| 50 | 
            +
                expect(@session).not_to have_button('role=button within label', enable_aria_role: true)
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
             | 
| 49 53 | 
             
              it 'should be false for role=button when enable_aria_role: false' do
         | 
| 50 54 | 
             
                expect(@session).not_to have_button('ARIA button', enable_aria_role: false)
         | 
| 51 55 | 
             
              end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
              it 'should be false for a role=button within a label when enable_aria_role: false' do
         | 
| 58 | 
            +
                expect(@session).not_to have_button('role=button within label', enable_aria_role: false)
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
              it 'should not affect other selectors when enable_aria_role: true' do
         | 
| 62 | 
            +
                expect(@session).to have_button('Click me!', enable_aria_role: true)
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
              it 'should not affect other selectors when enable_aria_role: false' do
         | 
| 66 | 
            +
                expect(@session).to have_button('Click me!', enable_aria_role: false)
         | 
| 67 | 
            +
              end
         | 
| 52 68 | 
             
            end
         | 
| 53 69 |  | 
| 54 70 | 
             
            Capybara::SpecHelper.spec '#has_no_button?' do
         | 
| @@ -81,7 +97,24 @@ Capybara::SpecHelper.spec '#has_no_button?' do | |
| 81 97 | 
             
                expect(@session).to have_no_button('ARIA button', enable_aria_role: false)
         | 
| 82 98 | 
             
              end
         | 
| 83 99 |  | 
| 100 | 
            +
              it 'should be true for role=button within a label when enable_aria_role: false' do
         | 
| 101 | 
            +
                expect(@session).to have_no_button('role=button within label', enable_aria_role: false)
         | 
| 102 | 
            +
              end
         | 
| 103 | 
            +
             | 
| 84 104 | 
             
              it 'should be false for role=button when enable_aria_role: true' do
         | 
| 85 105 | 
             
                expect(@session).not_to have_no_button('ARIA button', enable_aria_role: true)
         | 
| 86 106 | 
             
              end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
              it 'should be true for a role=button within a label when enable_aria_role: true' do
         | 
| 109 | 
            +
                # label element does not associate with aria button
         | 
| 110 | 
            +
                expect(@session).to have_no_button('role=button within label', enable_aria_role: true)
         | 
| 111 | 
            +
              end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
              it 'should not affect other selectors when enable_aria_role: true' do
         | 
| 114 | 
            +
                expect(@session).to have_no_button('Junk button that does not exist', enable_aria_role: true)
         | 
| 115 | 
            +
              end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
              it 'should not affect other selectors when enable_aria_role: false' do
         | 
| 118 | 
            +
                expect(@session).to have_no_button('Junk button that does not exist', enable_aria_role: false)
         | 
| 119 | 
            +
              end
         | 
| 87 120 | 
             
            end
         | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            #  | 
| 3 | 
            +
            # NOTE: This file uses `sleep` to sync up parts of the tests. This is only implemented like this
         | 
| 4 4 | 
             
            # because of the methods being tested. In tests using Capybara this type of behavior should be implemented
         | 
| 5 5 | 
             
            # using Capybara provided assertions with builtin waiting behavior.
         | 
| 6 6 |  | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            #  | 
| 3 | 
            +
            # NOTE: This file uses `sleep` to sync up parts of the tests. This is only implemented like this
         | 
| 4 4 | 
             
            # because of the methods being tested. In tests using Capybara this type of behavior should be implemented
         | 
| 5 5 | 
             
            # using Capybara provided assertions with builtin waiting behavior.
         | 
| 6 6 |  | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            #  | 
| 3 | 
            +
            # NOTE: This file uses `sleep` to sync up parts of the tests. This is only implemented like this
         | 
| 4 4 | 
             
            # because of the methods being tested. In tests using Capybara this type of behavior should be implemented
         | 
| 5 5 | 
             
            # using Capybara provided assertions with builtin waiting behavior.
         | 
| 6 6 |  | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            #  | 
| 3 | 
            +
            # NOTE: This file uses `sleep` to sync up parts of the tests. This is only implemented like this
         | 
| 4 4 | 
             
            # because of the methods being tested. In tests using Capybara this type of behavior should be implemented
         | 
| 5 5 | 
             
            # using Capybara provided assertions with builtin waiting behavior.
         | 
| 6 6 |  | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            #  | 
| 3 | 
            +
            # NOTE: This file uses `sleep` to sync up parts of the tests. This is only implemented like this
         | 
| 4 4 | 
             
            # because of the methods being tested. In tests using Capybara this type of behavior should be implemented
         | 
| 5 5 | 
             
            # using Capybara provided assertions with builtin waiting behavior.
         | 
| 6 6 |  | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            #  | 
| 3 | 
            +
            # NOTE: This file uses `sleep` to sync up parts of the tests. This is only implemented like this
         | 
| 4 4 | 
             
            # because of the methods being tested. In tests using Capybara this type of behavior should be implemented
         | 
| 5 5 | 
             
            # using Capybara provided assertions with builtin waiting behavior.
         | 
| 6 6 |  | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            #  | 
| 3 | 
            +
            # NOTE: This file uses `sleep` to sync up parts of the tests. This is only implemented like this
         | 
| 4 4 | 
             
            # because of the methods being tested. In tests using Capybara this type of behavior should be implemented
         | 
| 5 5 | 
             
            # using Capybara provided assertions with builtin waiting behavior.
         | 
| 6 6 |  | 
| @@ -462,6 +462,10 @@ New line after and before textarea tag | |
| 462 462 | 
             
                  button within label element
         | 
| 463 463 | 
             
                  <button></button>
         | 
| 464 464 | 
             
                </label>
         | 
| 465 | 
            +
                <label>
         | 
| 466 | 
            +
                  role=button within label element
         | 
| 467 | 
            +
                  <span role="button">with other text</span>
         | 
| 468 | 
            +
                </label>
         | 
| 465 469 | 
             
                <input type="button" disabled="disabled" value="Disabled button"/>
         | 
| 466 470 | 
             
                <span role="button">ARIA button</span>
         | 
| 467 471 | 
             
              </p>
         | 
| @@ -0,0 +1,24 @@ | |
| 1 | 
            +
             | 
| 2 | 
            +
            <html>
         | 
| 3 | 
            +
              <head>
         | 
| 4 | 
            +
                <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
         | 
| 5 | 
            +
                <title>with_jquery_animation</title>
         | 
| 6 | 
            +
                <style>
         | 
| 7 | 
            +
                  body {
         | 
| 8 | 
            +
                    height: 2000px;
         | 
| 9 | 
            +
                  }
         | 
| 10 | 
            +
                </style>
         | 
| 11 | 
            +
              </head>
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              <body id="with_animation">
         | 
| 14 | 
            +
                <a href="#" id='scroll'>scroll top 500</a>
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                <script src="/jquery.js" type="text/javascript" charset="utf-8"></script>
         | 
| 17 | 
            +
                <script type='text/javascript'>
         | 
| 18 | 
            +
                  $('#scroll').click(function(e){
         | 
| 19 | 
            +
                    e.preventDefault();
         | 
| 20 | 
            +
                    $('html, body').animate({ scrollTop: 500 }, 'slow');
         | 
| 21 | 
            +
                  });
         | 
| 22 | 
            +
                </script>
         | 
| 23 | 
            +
              </body>
         | 
| 24 | 
            +
            </html>
         | 
    
        data/lib/capybara/version.rb
    CHANGED
    
    
    
        data/spec/selector_spec.rb
    CHANGED
    
    | @@ -327,9 +327,23 @@ RSpec.describe Capybara do | |
| 327 327 | 
             
                      expect(string.find(:custom_xpath_selector, './/div', class: /dOm WoR/i)[:id]).to eq 'random_words'
         | 
| 328 328 | 
             
                    end
         | 
| 329 329 |  | 
| 330 | 
            -
                    it 'accepts Regexp for CSS  | 
| 330 | 
            +
                    it 'accepts Regexp for CSS based selectors' do
         | 
| 331 331 | 
             
                      expect(string.find(:custom_css_selector, 'div', class: /random/)[:id]).to eq 'random_words'
         | 
| 332 332 | 
             
                    end
         | 
| 333 | 
            +
             | 
| 334 | 
            +
                    it 'accepts Regexp for individual class names for XPath based selectors' do
         | 
| 335 | 
            +
                      expect(string.find(:custom_xpath_selector, './/div', class: [/random/, 'some'])[:id]).to eq 'random_words'
         | 
| 336 | 
            +
                      expect(string.find(:custom_xpath_selector, './/div', class: [/om/, /wor/])[:id]).to eq 'random_words'
         | 
| 337 | 
            +
                      expect { string.find(:custom_xpath_selector, './/div', class: [/not/, /wor/]) }.to raise_error(Capybara::ElementNotFound)
         | 
| 338 | 
            +
                      expect { string.find(:custom_xpath_selector, './/div', class: [/dom wor/]) }.to raise_error(Capybara::ElementNotFound)
         | 
| 339 | 
            +
                    end
         | 
| 340 | 
            +
             | 
| 341 | 
            +
                    it 'accepts Regexp for individual class names for CSS based selectors' do
         | 
| 342 | 
            +
                      expect(string.find(:custom_css_selector, 'div', class: [/random/])[:id]).to eq 'random_words'
         | 
| 343 | 
            +
                      expect(string.find(:custom_css_selector, 'div', class: [/om/, /wor/, 'some'])[:id]).to eq 'random_words'
         | 
| 344 | 
            +
                      expect { string.find(:custom_css_selector, 'div', class: [/not/, /wor/]) }.to raise_error(Capybara::ElementNotFound)
         | 
| 345 | 
            +
                      expect { string.find(:custom_css_selector, 'div', class: [/dom wor/]) }.to raise_error(Capybara::ElementNotFound)
         | 
| 346 | 
            +
                    end
         | 
| 333 347 | 
             
                  end
         | 
| 334 348 |  | 
| 335 349 | 
             
                  context 'with :style option' do
         | 
| @@ -19,39 +19,56 @@ browser_options.add_preference('download.default_directory', Capybara.save_path) | |
| 19 19 | 
             
            browser_options.add_preference(:download, default_directory: Capybara.save_path)
         | 
| 20 20 |  | 
| 21 21 | 
             
            Capybara.register_driver :selenium_chrome do |app|
         | 
| 22 | 
            -
              Capybara::Selenium::Driver. | 
| 22 | 
            +
              version = Capybara::Selenium::Driver.load_selenium
         | 
| 23 | 
            +
              options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
         | 
| 24 | 
            +
              driver_options = { browser: :chrome, timeout: 30 }.tap do |opts|
         | 
| 25 | 
            +
                opts[options_key] = browser_options
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              Capybara::Selenium::Driver.new(app, **driver_options).tap do |driver|
         | 
| 23 29 | 
             
                # Set download dir for Chrome < 77
         | 
| 24 30 | 
             
                driver.browser.download_path = Capybara.save_path
         | 
| 25 31 | 
             
              end
         | 
| 26 32 | 
             
            end
         | 
| 27 33 |  | 
| 28 34 | 
             
            Capybara.register_driver :selenium_chrome_not_clear_storage do |app|
         | 
| 29 | 
            -
               | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
               | 
| 35 | 
            +
              version = Capybara::Selenium::Driver.load_selenium
         | 
| 36 | 
            +
              options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
         | 
| 37 | 
            +
              chrome_options = { browser: :chrome, clear_local_storage: false, clear_session_storage: false }.tap do |opts|
         | 
| 38 | 
            +
                opts[options_key] = browser_options
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
              Capybara::Selenium::Driver.new(app, **chrome_options)
         | 
| 34 42 | 
             
            end
         | 
| 35 43 |  | 
| 36 44 | 
             
            Capybara.register_driver :selenium_chrome_not_clear_session_storage do |app|
         | 
| 37 | 
            -
               | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
               | 
| 45 | 
            +
              version = Capybara::Selenium::Driver.load_selenium
         | 
| 46 | 
            +
              options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
         | 
| 47 | 
            +
              chrome_options = { browser: :chrome, clear_session_storage: false }.tap do |opts|
         | 
| 48 | 
            +
                opts[options_key] = browser_options
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
              Capybara::Selenium::Driver.new(app, **chrome_options)
         | 
| 42 52 | 
             
            end
         | 
| 43 53 |  | 
| 44 54 | 
             
            Capybara.register_driver :selenium_chrome_not_clear_local_storage do |app|
         | 
| 45 | 
            -
               | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
               | 
| 55 | 
            +
              version = Capybara::Selenium::Driver.load_selenium
         | 
| 56 | 
            +
              options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
         | 
| 57 | 
            +
              chrome_options = { browser: :chrome, clear_local_storage: false }.tap do |opts|
         | 
| 58 | 
            +
                opts[options_key] = browser_options
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
              Capybara::Selenium::Driver.new(app, **chrome_options)
         | 
| 50 61 | 
             
            end
         | 
| 51 62 |  | 
| 52 63 | 
             
            Capybara.register_driver :selenium_driver_subclass_with_chrome do |app|
         | 
| 64 | 
            +
              version = Capybara::Selenium::Driver.load_selenium
         | 
| 65 | 
            +
              options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
         | 
| 53 66 | 
             
              subclass = Class.new(Capybara::Selenium::Driver)
         | 
| 54 | 
            -
               | 
| 67 | 
            +
              chrome_options = { browser: :chrome, timeout: 30 }.tap do |opts|
         | 
| 68 | 
            +
                opts[options_key] = browser_options
         | 
| 69 | 
            +
              end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
              subclass.new(app, **chrome_options)
         | 
| 55 72 | 
             
            end
         | 
| 56 73 |  | 
| 57 74 | 
             
            module TestSessions
         | 
| @@ -171,7 +188,7 @@ RSpec.describe 'Capybara::Session with chrome' do | |
| 171 188 | 
             
                before { skip 'Only makes sense in W3C mode' if ENV['W3C'] == 'false' }
         | 
| 172 189 |  | 
| 173 190 | 
             
                it 'does not error getting log types' do
         | 
| 174 | 
            -
                  skip if Gem:: | 
| 191 | 
            +
                  skip if Gem::Requirement.new('< 75.0.3770.90').satisfied_by? chromedriver_version
         | 
| 175 192 | 
             
                  expect do
         | 
| 176 193 | 
             
                    session.driver.browser.manage.logs.available_types
         | 
| 177 194 | 
             
                  end.not_to raise_error
         | 
| @@ -183,4 +200,8 @@ RSpec.describe 'Capybara::Session with chrome' do | |
| 183 200 | 
             
                  end.not_to raise_error
         | 
| 184 201 | 
             
                end
         | 
| 185 202 | 
             
              end
         | 
| 203 | 
            +
             | 
| 204 | 
            +
              def chromedriver_version
         | 
| 205 | 
            +
                Gem::Version.new(session.driver.browser.capabilities['chrome']['chromedriverVersion'].split[0])
         | 
| 206 | 
            +
              end
         | 
| 186 207 | 
             
            end
         | 
| @@ -81,7 +81,7 @@ RSpec.describe 'Capybara::Session with remote Chrome' do | |
| 81 81 | 
             
                before { skip 'Only makes sense in W3C mode' if ENV['W3C'] == 'false' }
         | 
| 82 82 |  | 
| 83 83 | 
             
                it 'does not error when getting log types' do
         | 
| 84 | 
            -
                  skip  | 
| 84 | 
            +
                  skip unless Gem::Requirement.new('>= 75.0.3770.90').satisfied_by? chromedriver_version
         | 
| 85 85 | 
             
                  expect do
         | 
| 86 86 | 
             
                    session.driver.browser.manage.logs.available_types
         | 
| 87 87 | 
             
                  end.not_to raise_error
         | 
| @@ -93,4 +93,8 @@ RSpec.describe 'Capybara::Session with remote Chrome' do | |
| 93 93 | 
             
                  end.not_to raise_error
         | 
| 94 94 | 
             
                end
         | 
| 95 95 | 
             
              end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
              def chromedriver_version
         | 
| 98 | 
            +
                Gem::Version.new(session.driver.browser.capabilities['chrome']['chromedriverVersion'].split[0])
         | 
| 99 | 
            +
              end
         | 
| 96 100 | 
             
            end
         | 
| @@ -14,28 +14,30 @@ browser_options.profile = Selenium::WebDriver::Firefox::Profile.new.tap do |prof | |
| 14 14 | 
             
              profile['browser.download.dir'] = Capybara.save_path
         | 
| 15 15 | 
             
              profile['browser.download.folderList'] = 2
         | 
| 16 16 | 
             
              profile['browser.helperApps.neverAsk.saveToDisk'] = 'text/csv'
         | 
| 17 | 
            +
              profile['browser.startup.homepage'] = 'about:blank' # workaround bug in Selenium 4 alpha4-7
         | 
| 17 18 | 
             
            end
         | 
| 18 19 |  | 
| 19 20 | 
             
            Capybara.register_driver :selenium_firefox do |app|
         | 
| 20 21 | 
             
              # ::Selenium::WebDriver.logger.level = "debug"
         | 
| 21 | 
            -
              Capybara::Selenium::Driver. | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
                 | 
| 25 | 
            -
                timeout: 31
         | 
| 22 | 
            +
              version = Capybara::Selenium::Driver.load_selenium
         | 
| 23 | 
            +
              options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
         | 
| 24 | 
            +
              driver_options = { browser: :firefox, timeout: 31 }.tap do |opts|
         | 
| 25 | 
            +
                opts[options_key] = browser_options
         | 
| 26 26 | 
             
                # Get a trace level log from geckodriver
         | 
| 27 27 | 
             
                # :driver_opts => { args: ['-vv'] }
         | 
| 28 | 
            -
               | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              Capybara::Selenium::Driver.new(app, **driver_options)
         | 
| 29 31 | 
             
            end
         | 
| 30 32 |  | 
| 31 33 | 
             
            Capybara.register_driver :selenium_firefox_not_clear_storage do |app|
         | 
| 32 | 
            -
              Capybara::Selenium::Driver. | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
                 | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
              )
         | 
| 34 | 
            +
              version = Capybara::Selenium::Driver.load_selenium
         | 
| 35 | 
            +
              options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
         | 
| 36 | 
            +
              driver_options = { browser: :firefox, clear_local_storage: false, clear_session_storage: false }.tap do |opts|
         | 
| 37 | 
            +
                opts[options_key] = browser_options
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              Capybara::Selenium::Driver.new(app, **driver_options)
         | 
| 39 41 | 
             
            end
         | 
| 40 42 |  | 
| 41 43 | 
             
            module TestSessions
         | 
    
        data/spec/server_spec.rb
    CHANGED
    
    | @@ -108,8 +108,12 @@ RSpec.describe Capybara::Server do | |
| 108 108 | 
             
                  expect(options.fetch(:environment)).to be_a(String)
         | 
| 109 109 | 
             
                  method.call(app, events, options)
         | 
| 110 110 | 
             
                end
         | 
| 111 | 
            -
                described_class.new(app_proc).boot
         | 
| 112 | 
            -
                expect(Puma::Server).to have_received(:new)
         | 
| 111 | 
            +
                server = described_class.new(app_proc).boot
         | 
| 112 | 
            +
                expect(Puma::Server).to have_received(:new).with(
         | 
| 113 | 
            +
                  anything,
         | 
| 114 | 
            +
                  anything,
         | 
| 115 | 
            +
                  satisfy { |opts| opts.final_options[:Port] == server.port }
         | 
| 116 | 
            +
                )
         | 
| 113 117 | 
             
              ensure
         | 
| 114 118 | 
             
                Capybara.server = :default
         | 
| 115 119 | 
             
              end
         | 
| @@ -411,6 +411,13 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode| | |
| 411 411 | 
             
                      JS
         | 
| 412 412 | 
             
                      expect(scroll_y).to eq 500
         | 
| 413 413 | 
             
                    end
         | 
| 414 | 
            +
             | 
| 415 | 
            +
                    it 'should scroll the page instantly without jquery animation', requires: [:js] do
         | 
| 416 | 
            +
                      @animation_session.visit('with_jquery_animation')
         | 
| 417 | 
            +
                      @animation_session.click_link('scroll top 500')
         | 
| 418 | 
            +
                      scroll_y = @animation_session.evaluate_script('window.scrollY')
         | 
| 419 | 
            +
                      expect(scroll_y).to eq 500
         | 
| 420 | 
            +
                    end
         | 
| 414 421 | 
             
                  end
         | 
| 415 422 |  | 
| 416 423 | 
             
                  context 'when set to `false`' do
         | 
| @@ -433,6 +440,13 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode| | |
| 433 440 | 
             
                      # measured over 0.5 seconds: 0, 75, 282, 478, 500
         | 
| 434 441 | 
             
                      expect(scroll_y).to be < 500
         | 
| 435 442 | 
             
                    end
         | 
| 443 | 
            +
             | 
| 444 | 
            +
                    it 'should scroll the page with jquery animation', requires: [:js] do
         | 
| 445 | 
            +
                      @animation_session.visit('with_jquery_animation')
         | 
| 446 | 
            +
                      @animation_session.click_link('scroll top 500')
         | 
| 447 | 
            +
                      scroll_y = @animation_session.evaluate_script('window.scrollY')
         | 
| 448 | 
            +
                      expect(scroll_y).to be < 500
         | 
| 449 | 
            +
                    end
         | 
| 436 450 | 
             
                  end
         | 
| 437 451 |  | 
| 438 452 | 
             
                  context 'if we pass in css that matches elements' do
         | 
    
        metadata
    CHANGED
    
    | @@ -1,16 +1,16 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: capybara
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 3. | 
| 4 | 
            +
              version: 3.35.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Thomas Walpole
         | 
| 8 8 | 
             
            - Jonas Nicklas
         | 
| 9 | 
            -
            autorequire:
         | 
| 9 | 
            +
            autorequire: 
         | 
| 10 10 | 
             
            bindir: bin
         | 
| 11 11 | 
             
            cert_chain:
         | 
| 12 12 | 
             
            - gem-public_cert.pem
         | 
| 13 | 
            -
            date:  | 
| 13 | 
            +
            date: 2021-01-26 00:00:00.000000000 Z
         | 
| 14 14 | 
             
            dependencies:
         | 
| 15 15 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 16 16 | 
             
              name: addressable
         | 
| @@ -86,16 +86,22 @@ dependencies: | |
| 86 86 | 
             
              name: regexp_parser
         | 
| 87 87 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 88 88 | 
             
                requirements:
         | 
| 89 | 
            -
                - - " | 
| 89 | 
            +
                - - ">="
         | 
| 90 90 | 
             
                  - !ruby/object:Gem::Version
         | 
| 91 91 | 
             
                    version: '1.5'
         | 
| 92 | 
            +
                - - "<"
         | 
| 93 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 94 | 
            +
                    version: '3.0'
         | 
| 92 95 | 
             
              type: :runtime
         | 
| 93 96 | 
             
              prerelease: false
         | 
| 94 97 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 95 98 | 
             
                requirements:
         | 
| 96 | 
            -
                - - " | 
| 99 | 
            +
                - - ">="
         | 
| 97 100 | 
             
                  - !ruby/object:Gem::Version
         | 
| 98 101 | 
             
                    version: '1.5'
         | 
| 102 | 
            +
                - - "<"
         | 
| 103 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 104 | 
            +
                    version: '3.0'
         | 
| 99 105 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 100 106 | 
             
              name: xpath
         | 
| 101 107 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -270,14 +276,28 @@ dependencies: | |
| 270 276 | 
             
                requirements:
         | 
| 271 277 | 
             
                - - "~>"
         | 
| 272 278 | 
             
                  - !ruby/object:Gem::Version
         | 
| 273 | 
            -
                    version: 1.1 | 
| 279 | 
            +
                    version: '1.1'
         | 
| 274 280 | 
             
              type: :development
         | 
| 275 281 | 
             
              prerelease: false
         | 
| 276 282 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 277 283 | 
             
                requirements:
         | 
| 278 284 | 
             
                - - "~>"
         | 
| 279 285 | 
             
                  - !ruby/object:Gem::Version
         | 
| 280 | 
            -
                    version: 1.1 | 
| 286 | 
            +
                    version: '1.1'
         | 
| 287 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 288 | 
            +
              name: rubocop-minitest
         | 
| 289 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 290 | 
            +
                requirements:
         | 
| 291 | 
            +
                - - ">="
         | 
| 292 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 293 | 
            +
                    version: '0'
         | 
| 294 | 
            +
              type: :development
         | 
| 295 | 
            +
              prerelease: false
         | 
| 296 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 297 | 
            +
                requirements:
         | 
| 298 | 
            +
                - - ">="
         | 
| 299 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 300 | 
            +
                    version: '0'
         | 
| 281 301 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 282 302 | 
             
              name: rubocop-performance
         | 
| 283 303 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -292,20 +312,34 @@ dependencies: | |
| 292 312 | 
             
                - - ">="
         | 
| 293 313 | 
             
                  - !ruby/object:Gem::Version
         | 
| 294 314 | 
             
                    version: '0'
         | 
| 315 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 316 | 
            +
              name: rubocop-rake
         | 
| 317 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 318 | 
            +
                requirements:
         | 
| 319 | 
            +
                - - ">="
         | 
| 320 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 321 | 
            +
                    version: '0'
         | 
| 322 | 
            +
              type: :development
         | 
| 323 | 
            +
              prerelease: false
         | 
| 324 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 325 | 
            +
                requirements:
         | 
| 326 | 
            +
                - - ">="
         | 
| 327 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 328 | 
            +
                    version: '0'
         | 
| 295 329 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 296 330 | 
             
              name: rubocop-rspec
         | 
| 297 331 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 298 332 | 
             
                requirements:
         | 
| 299 333 | 
             
                - - "~>"
         | 
| 300 334 | 
             
                  - !ruby/object:Gem::Version
         | 
| 301 | 
            -
                    version: 2.0 | 
| 335 | 
            +
                    version: '2.0'
         | 
| 302 336 | 
             
              type: :development
         | 
| 303 337 | 
             
              prerelease: false
         | 
| 304 338 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 305 339 | 
             
                requirements:
         | 
| 306 340 | 
             
                - - "~>"
         | 
| 307 341 | 
             
                  - !ruby/object:Gem::Version
         | 
| 308 | 
            -
                    version: 2.0 | 
| 342 | 
            +
                    version: '2.0'
         | 
| 309 343 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 310 344 | 
             
              name: sauce_whisk
         | 
| 311 345 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -672,6 +706,7 @@ files: | |
| 672 706 | 
             
            - lib/capybara/spec/views/with_html.erb
         | 
| 673 707 | 
             
            - lib/capybara/spec/views/with_html5_svg.erb
         | 
| 674 708 | 
             
            - lib/capybara/spec/views/with_html_entities.erb
         | 
| 709 | 
            +
            - lib/capybara/spec/views/with_jquery_animation.erb
         | 
| 675 710 | 
             
            - lib/capybara/spec/views/with_js.erb
         | 
| 676 711 | 
             
            - lib/capybara/spec/views/with_jstree.erb
         | 
| 677 712 | 
             
            - lib/capybara/spec/views/with_namespace.erb
         | 
| @@ -730,7 +765,7 @@ licenses: | |
| 730 765 | 
             
            metadata:
         | 
| 731 766 | 
             
              changelog_uri: https://github.com/teamcapybara/capybara/blob/master/History.md
         | 
| 732 767 | 
             
              source_code_uri: https://github.com/teamcapybara/capybara
         | 
| 733 | 
            -
            post_install_message:
         | 
| 768 | 
            +
            post_install_message: 
         | 
| 734 769 | 
             
            rdoc_options: []
         | 
| 735 770 | 
             
            require_paths:
         | 
| 736 771 | 
             
            - lib
         | 
| @@ -746,7 +781,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 746 781 | 
             
                  version: '0'
         | 
| 747 782 | 
             
            requirements: []
         | 
| 748 783 | 
             
            rubygems_version: 3.1.4
         | 
| 749 | 
            -
            signing_key:
         | 
| 784 | 
            +
            signing_key: 
         | 
| 750 785 | 
             
            specification_version: 4
         | 
| 751 786 | 
             
            summary: Capybara aims to simplify the process of integration testing Rack applications,
         | 
| 752 787 | 
             
              such as Rails, Sinatra or Merb
         |