capybara 3.10.1 → 3.11.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 +13 -0
- data/README.md +2 -3
- data/lib/capybara.rb +16 -6
- data/lib/capybara/minitest.rb +8 -9
- data/lib/capybara/node/actions.rb +31 -28
- data/lib/capybara/node/base.rb +2 -1
- data/lib/capybara/node/document_matchers.rb +6 -2
- data/lib/capybara/node/element.rb +10 -10
- data/lib/capybara/node/finders.rb +13 -14
- data/lib/capybara/node/matchers.rb +1 -3
- data/lib/capybara/node/simple.rb +10 -2
- data/lib/capybara/queries/base_query.rb +7 -3
- data/lib/capybara/queries/selector_query.rb +60 -34
- data/lib/capybara/queries/style_query.rb +5 -1
- data/lib/capybara/queries/text_query.rb +2 -2
- data/lib/capybara/queries/title_query.rb +1 -1
- data/lib/capybara/rack_test/node.rb +16 -2
- data/lib/capybara/result.rb +9 -4
- data/lib/capybara/rspec/features.rb +4 -4
- data/lib/capybara/rspec/matcher_proxies.rb +3 -1
- data/lib/capybara/rspec/matchers.rb +25 -287
- data/lib/capybara/rspec/matchers/base.rb +98 -0
- data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
- data/lib/capybara/rspec/matchers/compound.rb +88 -0
- data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
- data/lib/capybara/rspec/matchers/have_selector.rb +69 -0
- data/lib/capybara/rspec/matchers/have_style.rb +23 -0
- data/lib/capybara/rspec/matchers/have_text.rb +33 -0
- data/lib/capybara/rspec/matchers/have_title.rb +29 -0
- data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
- data/lib/capybara/selector.rb +48 -20
- data/lib/capybara/selector/builders/xpath_builder.rb +3 -3
- data/lib/capybara/selector/css.rb +5 -5
- data/lib/capybara/selector/filters/base.rb +11 -3
- data/lib/capybara/selector/filters/expression_filter.rb +3 -3
- data/lib/capybara/selector/filters/node_filter.rb +16 -2
- data/lib/capybara/selector/regexp_disassembler.rb +116 -17
- data/lib/capybara/selector/selector.rb +52 -26
- data/lib/capybara/selenium/driver.rb +6 -2
- data/lib/capybara/selenium/node.rb +15 -14
- data/lib/capybara/selenium/nodes/marionette_node.rb +19 -5
- data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -3
- data/lib/capybara/server.rb +6 -1
- data/lib/capybara/server/animation_disabler.rb +1 -1
- data/lib/capybara/session.rb +4 -2
- data/lib/capybara/session/matchers.rb +7 -3
- data/lib/capybara/spec/public/test.js +5 -5
- data/lib/capybara/spec/session/all_spec.rb +5 -0
- data/lib/capybara/spec/session/has_css_spec.rb +4 -4
- data/lib/capybara/spec/session/has_field_spec.rb +17 -0
- data/lib/capybara/spec/session/node_spec.rb +45 -4
- data/lib/capybara/spec/spec_helper.rb +6 -1
- data/lib/capybara/spec/views/frame_child.erb +1 -1
- data/lib/capybara/spec/views/obscured.erb +44 -0
- data/lib/capybara/spec/views/with_html.erb +1 -1
- data/lib/capybara/version.rb +1 -1
- data/spec/rack_test_spec.rb +15 -0
- data/spec/regexp_dissassembler_spec.rb +88 -8
- data/spec/selector_spec.rb +3 -0
- data/spec/selenium_spec_chrome.rb +9 -15
- data/spec/selenium_spec_chrome_remote.rb +3 -2
- data/spec/selenium_spec_firefox_remote.rb +6 -2
- metadata +54 -3
- data/lib/capybara/rspec/compound.rb +0 -86
| @@ -0,0 +1,98 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'capybara/rspec/matchers/compound'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Capybara
         | 
| 6 | 
            +
              module RSpecMatchers
         | 
| 7 | 
            +
                module Matchers
         | 
| 8 | 
            +
                  class Base
         | 
| 9 | 
            +
                    include ::Capybara::RSpecMatchers::Matchers::Compound if defined?(::Capybara::RSpecMatchers::Matchers::Compound)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    attr_reader :failure_message, :failure_message_when_negated
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    def initialize(*args, &filter_block)
         | 
| 14 | 
            +
                      @args = args.dup
         | 
| 15 | 
            +
                      @filter_block = filter_block
         | 
| 16 | 
            +
                    end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  private
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    def session_query_args
         | 
| 21 | 
            +
                      if @args.last.is_a? Hash
         | 
| 22 | 
            +
                        @args.last[:session_options] = session_options
         | 
| 23 | 
            +
                      else
         | 
| 24 | 
            +
                        @args.push(session_options: session_options)
         | 
| 25 | 
            +
                      end
         | 
| 26 | 
            +
                      @args
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    def session_options
         | 
| 30 | 
            +
                      @context_el ||= nil
         | 
| 31 | 
            +
                      if @context_el.respond_to? :session_options
         | 
| 32 | 
            +
                        @context_el.session_options
         | 
| 33 | 
            +
                      elsif @context_el.respond_to? :current_scope
         | 
| 34 | 
            +
                        @context_el.current_scope.session_options
         | 
| 35 | 
            +
                      else
         | 
| 36 | 
            +
                        Capybara.session_options
         | 
| 37 | 
            +
                      end
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  class WrappedElementMatcher < Base
         | 
| 42 | 
            +
                    def matches?(actual)
         | 
| 43 | 
            +
                      element_matches?(wrap(actual))
         | 
| 44 | 
            +
                    rescue Capybara::ExpectationNotMet => err
         | 
| 45 | 
            +
                      @failure_message = err.message
         | 
| 46 | 
            +
                      false
         | 
| 47 | 
            +
                    end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    def does_not_match?(actual)
         | 
| 50 | 
            +
                      element_does_not_match?(wrap(actual))
         | 
| 51 | 
            +
                    rescue Capybara::ExpectationNotMet => err
         | 
| 52 | 
            +
                      @failure_message_when_negated = err.message
         | 
| 53 | 
            +
                      false
         | 
| 54 | 
            +
                    end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  private
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                    def wrap(actual)
         | 
| 59 | 
            +
                      actual = actual.to_capybara_node if actual.respond_to?(:to_capybara_node)
         | 
| 60 | 
            +
                      @context_el = if actual.respond_to?(:has_selector?)
         | 
| 61 | 
            +
                        actual
         | 
| 62 | 
            +
                      else
         | 
| 63 | 
            +
                        Capybara.string(actual.to_s)
         | 
| 64 | 
            +
                      end
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  class NegatedMatcher
         | 
| 69 | 
            +
                    include ::Capybara::RSpecMatchers::Matchers::Compound if defined?(::Capybara::RSpecMatchers::Matchers::Compound)
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                    def initialize(matcher)
         | 
| 72 | 
            +
                      super()
         | 
| 73 | 
            +
                      @matcher = matcher
         | 
| 74 | 
            +
                    end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                    def matches?(actual)
         | 
| 77 | 
            +
                      @matcher.does_not_match?(actual)
         | 
| 78 | 
            +
                    end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                    def does_not_match?(actual)
         | 
| 81 | 
            +
                      @matcher.matches?(actual)
         | 
| 82 | 
            +
                    end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                    def description
         | 
| 85 | 
            +
                      "not #{@matcher.description}"
         | 
| 86 | 
            +
                    end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                    def failure_message
         | 
| 89 | 
            +
                      @matcher.failure_message_when_negated
         | 
| 90 | 
            +
                    end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                    def failure_message_when_negated
         | 
| 93 | 
            +
                      @matcher.failure_message
         | 
| 94 | 
            +
                    end
         | 
| 95 | 
            +
                  end
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
              end
         | 
| 98 | 
            +
            end
         | 
| @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Capybara
         | 
| 4 | 
            +
              module RSpecMatchers
         | 
| 5 | 
            +
                module Matchers
         | 
| 6 | 
            +
                  class BecomeClosed
         | 
| 7 | 
            +
                    def initialize(options)
         | 
| 8 | 
            +
                      @options = options
         | 
| 9 | 
            +
                    end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    def matches?(window)
         | 
| 12 | 
            +
                      @window = window
         | 
| 13 | 
            +
                      @wait_time = Capybara::Queries::BaseQuery.wait(@options, window.session.config.default_max_wait_time)
         | 
| 14 | 
            +
                      timer = Capybara::Helpers.timer(expire_in: @wait_time)
         | 
| 15 | 
            +
                      while window.exists?
         | 
| 16 | 
            +
                        return false if timer.expired?
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                        sleep 0.05
         | 
| 19 | 
            +
                      end
         | 
| 20 | 
            +
                      true
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    def failure_message
         | 
| 24 | 
            +
                      "expected #{@window.inspect} to become closed after #{@wait_time} seconds"
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    def failure_message_when_negated
         | 
| 28 | 
            +
                      "expected #{@window.inspect} not to become closed after #{@wait_time} seconds"
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         | 
| @@ -0,0 +1,88 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            if defined?(::RSpec::Expectations::Version)
         | 
| 4 | 
            +
              module Capybara
         | 
| 5 | 
            +
                module RSpecMatchers
         | 
| 6 | 
            +
                  module Matchers
         | 
| 7 | 
            +
                    module Compound
         | 
| 8 | 
            +
                      include ::RSpec::Matchers::Composable
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                      def and(matcher)
         | 
| 11 | 
            +
                        And.new(self, matcher)
         | 
| 12 | 
            +
                      end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                      def and_then(matcher)
         | 
| 15 | 
            +
                        ::RSpec::Matchers::BuiltIn::Compound::And.new(self, matcher)
         | 
| 16 | 
            +
                      end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                      def or(matcher)
         | 
| 19 | 
            +
                        Or.new(self, matcher)
         | 
| 20 | 
            +
                      end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                      class CapybaraEvaluator
         | 
| 23 | 
            +
                        def initialize(actual)
         | 
| 24 | 
            +
                          @actual = actual
         | 
| 25 | 
            +
                          @match_results = Hash.new { |hsh, matcher| hsh[matcher] = matcher.matches?(@actual) }
         | 
| 26 | 
            +
                        end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                        def matcher_matches?(matcher)
         | 
| 29 | 
            +
                          @match_results[matcher]
         | 
| 30 | 
            +
                        end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                        def reset
         | 
| 33 | 
            +
                          @match_results.clear
         | 
| 34 | 
            +
                        end
         | 
| 35 | 
            +
                      end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                      # @api private
         | 
| 38 | 
            +
                      module Synchronizer
         | 
| 39 | 
            +
                        def match(_expected, actual)
         | 
| 40 | 
            +
                          @evaluator = CapybaraEvaluator.new(actual)
         | 
| 41 | 
            +
                          syncer = sync_element(actual)
         | 
| 42 | 
            +
                          begin
         | 
| 43 | 
            +
                            syncer.synchronize do
         | 
| 44 | 
            +
                              @evaluator.reset
         | 
| 45 | 
            +
                              raise ::Capybara::ElementNotFound unless synchronized_match?
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                              true
         | 
| 48 | 
            +
                            end
         | 
| 49 | 
            +
                          rescue StandardError
         | 
| 50 | 
            +
                            false
         | 
| 51 | 
            +
                          end
         | 
| 52 | 
            +
                        end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                        def sync_element(el)
         | 
| 55 | 
            +
                          if el.respond_to? :synchronize
         | 
| 56 | 
            +
                            el
         | 
| 57 | 
            +
                          elsif el.respond_to? :current_scope
         | 
| 58 | 
            +
                            el.current_scope
         | 
| 59 | 
            +
                          else
         | 
| 60 | 
            +
                            Capybara.string(el)
         | 
| 61 | 
            +
                          end
         | 
| 62 | 
            +
                        end
         | 
| 63 | 
            +
                      end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                      class And < ::RSpec::Matchers::BuiltIn::Compound::And
         | 
| 66 | 
            +
                        include Synchronizer
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                      private
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                        def synchronized_match?
         | 
| 71 | 
            +
                          [matcher_1_matches?, matcher_2_matches?].all?
         | 
| 72 | 
            +
                        end
         | 
| 73 | 
            +
                      end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                      class Or < ::RSpec::Matchers::BuiltIn::Compound::Or
         | 
| 76 | 
            +
                        include Synchronizer
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                      private
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                        def synchronized_match?
         | 
| 81 | 
            +
                          [matcher_1_matches?, matcher_2_matches?].any?
         | 
| 82 | 
            +
                        end
         | 
| 83 | 
            +
                      end
         | 
| 84 | 
            +
                    end
         | 
| 85 | 
            +
                  end
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
              end
         | 
| 88 | 
            +
            end
         | 
| @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'capybara/rspec/matchers/base'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Capybara
         | 
| 6 | 
            +
              module RSpecMatchers
         | 
| 7 | 
            +
                module Matchers
         | 
| 8 | 
            +
                  class HaveCurrentPath < WrappedElementMatcher
         | 
| 9 | 
            +
                    def element_matches?(el)
         | 
| 10 | 
            +
                      el.assert_current_path(*@args)
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    def element_does_not_match?(el)
         | 
| 14 | 
            +
                      el.assert_no_current_path(*@args)
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    def description
         | 
| 18 | 
            +
                      "have current path #{current_path.inspect}"
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  private
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    def current_path
         | 
| 24 | 
            +
                      @args.first
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
            end
         | 
| @@ -0,0 +1,69 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'capybara/rspec/matchers/base'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Capybara
         | 
| 6 | 
            +
              module RSpecMatchers
         | 
| 7 | 
            +
                module Matchers
         | 
| 8 | 
            +
                  class HaveSelector < WrappedElementMatcher
         | 
| 9 | 
            +
                    def element_matches?(el)
         | 
| 10 | 
            +
                      el.assert_selector(*@args, &@filter_block)
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    def element_does_not_match?(el)
         | 
| 14 | 
            +
                      el.assert_no_selector(*@args, &@filter_block)
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    def description
         | 
| 18 | 
            +
                      "have #{query.description}"
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    def query
         | 
| 22 | 
            +
                      @query ||= Capybara::Queries::SelectorQuery.new(*session_query_args, &@filter_block)
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  class HaveAllSelectors < WrappedElementMatcher
         | 
| 27 | 
            +
                    def element_matches?(el)
         | 
| 28 | 
            +
                      el.assert_all_of_selectors(*@args, &@filter_block)
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    def does_not_match?(_actual)
         | 
| 32 | 
            +
                      raise ArgumentError, 'The have_all_selectors matcher does not support use with not_to/should_not'
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    def description
         | 
| 36 | 
            +
                      'have all selectors'
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  class HaveNoSelectors < WrappedElementMatcher
         | 
| 41 | 
            +
                    def element_matches?(el)
         | 
| 42 | 
            +
                      el.assert_none_of_selectors(*@args, &@filter_block)
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    def does_not_match?(_actual)
         | 
| 46 | 
            +
                      raise ArgumentError, 'The have_none_of_selectors matcher does not support use with not_to/should_not'
         | 
| 47 | 
            +
                    end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    def description
         | 
| 50 | 
            +
                      'have no selectors'
         | 
| 51 | 
            +
                    end
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  class HaveAnySelectors < WrappedElementMatcher
         | 
| 55 | 
            +
                    def element_matches?(el)
         | 
| 56 | 
            +
                      el.assert_any_of_selectors(*@args, &@filter_block)
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    def does_not_match?(_actual)
         | 
| 60 | 
            +
                      el.assert_none_of_selectors(*@args, &@filter_block)
         | 
| 61 | 
            +
                    end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                    def description
         | 
| 64 | 
            +
                      'have any selectors'
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
              end
         | 
| 69 | 
            +
            end
         | 
| @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'capybara/rspec/matchers/base'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Capybara
         | 
| 6 | 
            +
              module RSpecMatchers
         | 
| 7 | 
            +
                module Matchers
         | 
| 8 | 
            +
                  class HaveStyle < WrappedElementMatcher
         | 
| 9 | 
            +
                    def element_matches?(el)
         | 
| 10 | 
            +
                      el.assert_style(*@args)
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    def does_not_match?(_actual)
         | 
| 14 | 
            +
                      raise ArgumentError, 'The have_style matcher does not support use with not_to/should_not'
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    def description
         | 
| 18 | 
            +
                      'have style'
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
            end
         | 
| @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'capybara/rspec/matchers/base'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Capybara
         | 
| 6 | 
            +
              module RSpecMatchers
         | 
| 7 | 
            +
                module Matchers
         | 
| 8 | 
            +
                  class HaveText < WrappedElementMatcher
         | 
| 9 | 
            +
                    def element_matches?(el)
         | 
| 10 | 
            +
                      el.assert_text(*@args)
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    def element_does_not_match?(el)
         | 
| 14 | 
            +
                      el.assert_no_text(*@args)
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    def description
         | 
| 18 | 
            +
                      "text #{format(text)}"
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    def format(content)
         | 
| 22 | 
            +
                      content.inspect
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  private
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    def text
         | 
| 28 | 
            +
                      @args[0].is_a?(Symbol) ? @args[1] : @args[0]
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         | 
| @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'capybara/rspec/matchers/base'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Capybara
         | 
| 6 | 
            +
              module RSpecMatchers
         | 
| 7 | 
            +
                module Matchers
         | 
| 8 | 
            +
                  class HaveTitle < WrappedElementMatcher
         | 
| 9 | 
            +
                    def element_matches?(el)
         | 
| 10 | 
            +
                      el.assert_title(*@args)
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    def element_does_not_match?(el)
         | 
| 14 | 
            +
                      el.assert_no_title(*@args)
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    def description
         | 
| 18 | 
            +
                      "have title #{title.inspect}"
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  private
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    def title
         | 
| 24 | 
            +
                      @args.first
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
            end
         | 
| @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'capybara/rspec/matchers/have_selector'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Capybara
         | 
| 6 | 
            +
              module RSpecMatchers
         | 
| 7 | 
            +
                module Matchers
         | 
| 8 | 
            +
                  class MatchSelector < HaveSelector
         | 
| 9 | 
            +
                    def element_matches?(el)
         | 
| 10 | 
            +
                      el.assert_matches_selector(*@args, &@filter_block)
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    def element_does_not_match?(el)
         | 
| 14 | 
            +
                      el.assert_not_matches_selector(*@args, &@filter_block)
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    def description
         | 
| 18 | 
            +
                      "match #{query.description}"
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    def query
         | 
| 22 | 
            +
                      @query ||= Capybara::Queries::MatchQuery.new(*session_query_args, &@filter_block)
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
            end
         | 
    
        data/lib/capybara/selector.rb
    CHANGED
    
    | @@ -34,7 +34,8 @@ Capybara.add_selector(:css) do | |
| 34 34 | 
             
            end
         | 
| 35 35 |  | 
| 36 36 | 
             
            Capybara.add_selector(:id) do
         | 
| 37 | 
            -
              xpath { |id| XPath.descendant[ | 
| 37 | 
            +
              xpath { |id| XPath.descendant[builder.attribute_conditions(id: id)] }
         | 
| 38 | 
            +
              locator_filter {  |node, id| id.is_a?(Regexp) ? node[:id] =~ id : true }
         | 
| 38 39 | 
             
            end
         | 
| 39 40 |  | 
| 40 41 | 
             
            Capybara.add_selector(:field) do
         | 
| @@ -59,7 +60,10 @@ Capybara.add_selector(:field) do | |
| 59 60 |  | 
| 60 61 | 
             
              node_filter(:readonly, :boolean) { |node, value| !(value ^ node.readonly?) }
         | 
| 61 62 | 
             
              node_filter(:with) do |node, with|
         | 
| 62 | 
            -
                 | 
| 63 | 
            +
                val = node.value
         | 
| 64 | 
            +
                (with.is_a?(Regexp) ? val =~ with : val == with.to_s).tap do |res|
         | 
| 65 | 
            +
                  add_error("Expected value to be #{with.inspect} but was #{val.inspect}") unless res
         | 
| 66 | 
            +
                end
         | 
| 63 67 | 
             
              end
         | 
| 64 68 |  | 
| 65 69 | 
             
              describe_expression_filters do |type: nil, **options|
         | 
| @@ -109,16 +113,13 @@ Capybara.add_selector(:link) do | |
| 109 113 |  | 
| 110 114 | 
             
              node_filter(:href) do |node, href|
         | 
| 111 115 | 
             
                # If not a Regexp it's been handled in the main XPath
         | 
| 112 | 
            -
                href.is_a?(Regexp) ? node[:href].match(href) : true
         | 
| 116 | 
            +
                (href.is_a?(Regexp) ? node[:href].match(href) : true).tap do |res|
         | 
| 117 | 
            +
                  add_error "Expected href to match #{href.inspect} but it was #{node[:href].inspect}" unless res
         | 
| 118 | 
            +
                end
         | 
| 113 119 | 
             
              end
         | 
| 114 120 |  | 
| 115 121 | 
             
              expression_filter(:download, valid_values: [true, false, String]) do |expr, download|
         | 
| 116 | 
            -
                 | 
| 117 | 
            -
                when true then XPath.attr(:download)
         | 
| 118 | 
            -
                when false then !XPath.attr(:download)
         | 
| 119 | 
            -
                when String then XPath.attr(:download) == download
         | 
| 120 | 
            -
                end
         | 
| 121 | 
            -
                expr[mod]
         | 
| 122 | 
            +
                expr[builder.attribute_conditions(download: download)]
         | 
| 122 123 | 
             
              end
         | 
| 123 124 |  | 
| 124 125 | 
             
              describe_expression_filters do |**options|
         | 
| @@ -178,7 +179,9 @@ end | |
| 178 179 | 
             
            Capybara.add_selector(:link_or_button) do
         | 
| 179 180 | 
             
              label 'link or button'
         | 
| 180 181 | 
             
              xpath do |locator, **options|
         | 
| 181 | 
            -
                self.class.all.values_at(:link, :button).map  | 
| 182 | 
            +
                self.class.all.values_at(:link, :button).map do |selector|
         | 
| 183 | 
            +
                  instance_exec(locator, options, &selector.xpath)
         | 
| 184 | 
            +
                end.reduce(:union)
         | 
| 182 185 | 
             
              end
         | 
| 183 186 |  | 
| 184 187 | 
             
              node_filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| node.tag_name == 'a' || !(value ^ node.disabled?) }
         | 
| @@ -210,7 +213,10 @@ Capybara.add_selector(:fillable_field) do | |
| 210 213 | 
             
              filter_set(:_field, %i[disabled multiple name placeholder])
         | 
| 211 214 |  | 
| 212 215 | 
             
              node_filter(:with) do |node, with|
         | 
| 213 | 
            -
                 | 
| 216 | 
            +
                val = node.value
         | 
| 217 | 
            +
                (with.is_a?(Regexp) ? val =~ with : val == with.to_s).tap do |res|
         | 
| 218 | 
            +
                  add_error("Expected value to be #{with.inspect} but was #{val.inspect}") unless res
         | 
| 219 | 
            +
                end
         | 
| 214 220 | 
             
              end
         | 
| 215 221 |  | 
| 216 222 | 
             
              describe_expression_filters
         | 
| @@ -231,7 +237,12 @@ Capybara.add_selector(:radio_button) do | |
| 231 237 |  | 
| 232 238 | 
             
              filter_set(:_field, %i[checked unchecked disabled name])
         | 
| 233 239 |  | 
| 234 | 
            -
              node_filter(:option)  | 
| 240 | 
            +
              node_filter(:option) do |node, value|
         | 
| 241 | 
            +
                val = node.value
         | 
| 242 | 
            +
                (val == value.to_s).tap do |res|
         | 
| 243 | 
            +
                  add_error("Expected option value to be #{value.inspect} but it was #{val.inspect}") unless res
         | 
| 244 | 
            +
                end
         | 
| 245 | 
            +
              end
         | 
| 235 246 |  | 
| 236 247 | 
             
              describe_expression_filters
         | 
| 237 248 | 
             
              describe_node_filters do |option: nil, **|
         | 
| @@ -249,7 +260,12 @@ Capybara.add_selector(:checkbox) do | |
| 249 260 |  | 
| 250 261 | 
             
              filter_set(:_field, %i[checked unchecked disabled name])
         | 
| 251 262 |  | 
| 252 | 
            -
              node_filter(:option)  | 
| 263 | 
            +
              node_filter(:option) do |node, value|
         | 
| 264 | 
            +
                val = node.value
         | 
| 265 | 
            +
                (val == value.to_s).tap do |res|
         | 
| 266 | 
            +
                  add_error("Expected option value to be #{value.inspect} but it was #{val.inspect}") unless res
         | 
| 267 | 
            +
                end
         | 
| 268 | 
            +
              end
         | 
| 253 269 |  | 
| 254 270 | 
             
              describe_expression_filters
         | 
| 255 271 | 
             
              describe_node_filters do |option: nil, **|
         | 
| @@ -273,23 +289,29 @@ Capybara.add_selector(:select) do | |
| 273 289 | 
             
                else
         | 
| 274 290 | 
             
                  node.all(:xpath, './/option', visible: false, wait: false).map { |option| option.text(:all) }
         | 
| 275 291 | 
             
                end
         | 
| 276 | 
            -
                options.sort == actual.sort
         | 
| 292 | 
            +
                (options.sort == actual.sort).tap do |res|
         | 
| 293 | 
            +
                  add_error("Expected options #{options.inspect} found #{actual.inspect}") unless res
         | 
| 294 | 
            +
                end
         | 
| 277 295 | 
             
              end
         | 
| 278 296 |  | 
| 279 297 | 
             
              expression_filter(:with_options) do |expr, options|
         | 
| 280 298 | 
             
                options.inject(expr) do |xpath, option|
         | 
| 281 | 
            -
                  xpath[ | 
| 299 | 
            +
                  xpath[self.class.all[:option].call(option)]
         | 
| 282 300 | 
             
                end
         | 
| 283 301 | 
             
              end
         | 
| 284 302 |  | 
| 285 303 | 
             
              node_filter(:selected) do |node, selected|
         | 
| 286 304 | 
             
                actual = node.all(:xpath, './/option', visible: false, wait: false).select(&:selected?).map { |option| option.text(:all) }
         | 
| 287 | 
            -
                Array(selected).sort == actual.sort
         | 
| 305 | 
            +
                (Array(selected).sort == actual.sort).tap do |res|
         | 
| 306 | 
            +
                  add_error("Expected #{selected.inspect} to be selected found #{actual.inspect}") unless res
         | 
| 307 | 
            +
                end
         | 
| 288 308 | 
             
              end
         | 
| 289 309 |  | 
| 290 310 | 
             
              node_filter(:with_selected) do |node, selected|
         | 
| 291 311 | 
             
                actual = node.all(:xpath, './/option', visible: false, wait: false).select(&:selected?).map { |option| option.text(:all) }
         | 
| 292 | 
            -
                (Array(selected) - actual).empty | 
| 312 | 
            +
                (Array(selected) - actual).empty?.tap do |res|
         | 
| 313 | 
            +
                  add_error("Expected at least #{selected.inspect} to be selected found #{actual.inspect}") unless res
         | 
| 314 | 
            +
                end
         | 
| 293 315 | 
             
              end
         | 
| 294 316 |  | 
| 295 317 | 
             
              describe_expression_filters do |with_options: nil, **opts|
         | 
| @@ -320,12 +342,14 @@ Capybara.add_selector(:datalist_input) do | |
| 320 342 |  | 
| 321 343 | 
             
              node_filter(:options) do |node, options|
         | 
| 322 344 | 
             
                actual = node.find("//datalist[@id=#{node[:list]}]", visible: :all).all(:datalist_option, wait: false).map(&:value)
         | 
| 323 | 
            -
                options.sort == actual.sort
         | 
| 345 | 
            +
                (options.sort == actual.sort).tap do |res|
         | 
| 346 | 
            +
                  add_error("Expected #{options.inspect} options found #{actual.inspect}") unless res
         | 
| 347 | 
            +
                end
         | 
| 324 348 | 
             
              end
         | 
| 325 349 |  | 
| 326 350 | 
             
              expression_filter(:with_options) do |expr, options|
         | 
| 327 351 | 
             
                options.inject(expr) do |xpath, option|
         | 
| 328 | 
            -
                  xpath[XPath.attr(:list) == XPath.anywhere(:datalist)[ | 
| 352 | 
            +
                  xpath[XPath.attr(:list) == XPath.anywhere(:datalist)[self.class.all[:datalist_option].call(option)].attr(:id)]
         | 
| 329 353 | 
             
                end
         | 
| 330 354 | 
             
              end
         | 
| 331 355 |  | 
| @@ -471,7 +495,11 @@ Capybara.add_selector(:element) do | |
| 471 495 | 
             
              end
         | 
| 472 496 |  | 
| 473 497 | 
             
              node_filter(:attributes, matcher: /.+/) do |node, name, val|
         | 
| 474 | 
            -
                val.is_a?(Regexp) | 
| 498 | 
            +
                next true unless val.is_a?(Regexp)
         | 
| 499 | 
            +
             | 
| 500 | 
            +
                (node[name] =~ val).tap do |res|
         | 
| 501 | 
            +
                  add_error("Expected #{name} to match #{val.inspect} but it was #{node[name]}") unless res
         | 
| 502 | 
            +
                end
         | 
| 475 503 | 
             
              end
         | 
| 476 504 |  | 
| 477 505 | 
             
              describe_expression_filters do |**options|
         |