diff --git a/.rubocop.yml b/.rubocop.yml index b6d8d604..9729561f 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -15,11 +15,11 @@ Metrics/LineLength: Exclude: - 'spec/**/*' - 'lib/capybara/spec/**/*' + - 'lib/capybara/selector.rb' IgnoredPatterns: - '\s*# ' - '\s*(raise|warn) ' Max: 120 - Enabled: false Metrics/BlockLength: Exclude: diff --git a/lib/capybara.rb b/lib/capybara.rb index 323b883e..21165566 100644 --- a/lib/capybara.rb +++ b/lib/capybara.rb @@ -460,7 +460,8 @@ end Capybara.register_server :webrick do |app, port, host, **options| require 'rack/handler/webrick' - Rack::Handler::WEBrick.run(app, { Host: host, Port: port, AccessLog: [], Logger: WEBrick::Log.new(nil, 0) }.merge(options)) + options = { Host: host, Port: port, AccessLog: [], Logger: WEBrick::Log.new(nil, 0) }.merge(options) + Rack::Handler::WEBrick.run(app, options) end Capybara.register_server :puma do |app, port, host, **options| @@ -476,8 +477,8 @@ Capybara.register_server :puma do |app, port, host, **options| # If we just run the Puma Rack handler it installs signal handlers which prevent us from being able to interrupt tests. # Therefore construct and run the Server instance ourselves. # Rack::Handler::Puma.run(app, { Host: host, Port: port, Threads: "0:4", workers: 0, daemon: false }.merge(options)) - - conf = Rack::Handler::Puma.config(app, { Host: host, Port: port, Threads: '0:4', workers: 0, daemon: false }.merge(options)) + options = { Host: host, Port: port, Threads: '0:4', workers: 0, daemon: false }.merge(options) + conf = Rack::Handler::Puma.config(app, options) events = conf.options[:Silent] ? ::Puma::Events.strings : ::Puma::Events.stdio events.log 'Capybara starting Puma...' diff --git a/lib/capybara/minitest.rb b/lib/capybara/minitest.rb index 716992ca..5b8829c8 100644 --- a/lib/capybara/minitest.rb +++ b/lib/capybara/minitest.rb @@ -42,12 +42,12 @@ module Capybara # @!method assert_no_current_path # see {Capybara::SessionMatchers#assert_no_current_path} - %w[assert_text assert_no_text assert_title assert_no_title assert_current_path assert_no_current_path].each do |assertion_name| + %w[text no_text title no_title current_path no_current_path].each do |assertion_name| class_eval <<-ASSERTION, __FILE__, __LINE__ + 1 - def #{assertion_name} *args + def assert_#{assertion_name} *args self.assertions +=1 subject, args = determine_subject(args) - subject.#{assertion_name}(*args) + subject.assert_#{assertion_name}(*args) rescue Capybara::ExpectationNotMet => e raise ::Minitest::Assertion, e.message end @@ -86,15 +86,14 @@ module Capybara # @!method assert_style # see {Capybara::Node::Matchers#assert_style} - %w[assert_selector assert_no_selector - assert_all_of_selectors assert_none_of_selectors assert_any_of_selectors - assert_matches_selector assert_not_matches_selector - assert_style].each do |assertion_name| + %w[selector no_selector style + all_of_selectors none_of_selectors any_of_selectors + matches_selector not_matches_selector].each do |assertion_name| class_eval <<-ASSERTION, __FILE__, __LINE__ + 1 - def #{assertion_name} *args, &optional_filter_block + def assert_#{assertion_name} *args, &optional_filter_block self.assertions +=1 subject, args = determine_subject(args) - subject.#{assertion_name}(*args, &optional_filter_block) + subject.assert_#{assertion_name}(*args, &optional_filter_block) rescue Capybara::ExpectationNotMet => e raise ::Minitest::Assertion, e.message end diff --git a/lib/capybara/node/actions.rb b/lib/capybara/node/actions.rb index 31b604ed..d15cb998 100644 --- a/lib/capybara/node/actions.rb +++ b/lib/capybara/node/actions.rb @@ -322,7 +322,8 @@ module Capybara rescue StandardError # rubocop:disable Lint/HandleExceptions swallow extra errors end - def _check_with_label(selector, checked, locator, allow_label_click: session_options.automatic_label_click, **options) + def _check_with_label(selector, checked, locator, + allow_label_click: session_options.automatic_label_click, **options) options[:allow_self] = true if locator.nil? synchronize(Capybara::Queries::BaseQuery.wait(options, session_options.default_max_wait_time)) do diff --git a/lib/capybara/node/matchers.rb b/lib/capybara/node/matchers.rb index 9d3ffe6b..68149a90 100644 --- a/lib/capybara/node/matchers.rb +++ b/lib/capybara/node/matchers.rb @@ -100,9 +100,7 @@ module Capybara # def assert_selector(*args, &optional_filter_block) _verify_selector_result(args, optional_filter_block) do |result, query| - unless result.matches_count? && (result.any? || query.expects_none?) - raise Capybara::ExpectationNotMet, result.failure_message - end + raise Capybara::ExpectationNotMet, result.failure_message unless result.matches_count? && (result.any? || query.expects_none?) end end diff --git a/lib/capybara/node/simple.rb b/lib/capybara/node/simple.rb index fb4fb727..5fecbdee 100644 --- a/lib/capybara/node/simple.rb +++ b/lib/capybara/node/simple.rb @@ -104,10 +104,11 @@ module Capybara return false if (tag_name == 'input') && (native[:type] == 'hidden') if check_ancestors - !find_xpath("boolean(./ancestor-or-self::*[contains(@style, 'display:none') or contains(@style, 'display: none') or @hidden or name()='script' or name()='head'])") + !find_xpath(VISIBILITY_XPATH) + # !find_xpath("boolean(./ancestor-or-self::*[contains(@style, 'display:none') or contains(@style, 'display: none') or @hidden or name()='script' or name()='head'])") else # No need for an xpath if only checking the current element - !(native.has_attribute?('hidden') || (native[:style] =~ /display:\s?none/) || %w[script head].include?(tag_name)) + !(native.key?('hidden') || (native[:style] =~ /display:\s?none/) || %w[script head].include?(tag_name)) end end @@ -185,6 +186,14 @@ module Capybara option[:value] || option.content end + + VISIBILITY_XPATH = XPath.generate do |x| + x.ancestor_or_self[ + x.attr(:style)[x.contains('display:none') | x.contains('display: none')] | + x.attr(:hidden) | + x.qname.one_of('script', 'head') + ].boolean + end.to_s.freeze end end end diff --git a/lib/capybara/queries/base_query.rb b/lib/capybara/queries/base_query.rb index af8686d4..4ca3e86f 100644 --- a/lib/capybara/queries/base_query.rb +++ b/lib/capybara/queries/base_query.rb @@ -77,17 +77,21 @@ module Capybara message = +'' count, between, maximum, minimum = options.values_at(:count, :between, :maximum, :minimum) if count - message << " #{count} #{Capybara::Helpers.declension('time', 'times', count)}" + message << " #{occurrences count}" elsif between message << " between #{between.first} and #{between.last} times" elsif maximum - message << " at most #{maximum} #{Capybara::Helpers.declension('time', 'times', maximum)}" + message << " at most #{occurrences maximum}" elsif minimum - message << " at least #{minimum} #{Capybara::Helpers.declension('time', 'times', minimum)}" + message << " at least #{occurrences minimum}" end message end + def occurrences(count) + "#{count} #{Capybara::Helpers.declension('time', 'times', count)}" + end + def assert_valid_keys invalid_keys = @options.keys - valid_keys return if invalid_keys.empty? diff --git a/lib/capybara/queries/selector_query.rb b/lib/capybara/queries/selector_query.rb index 6e649827..94f8c15f 100644 --- a/lib/capybara/queries/selector_query.rb +++ b/lib/capybara/queries/selector_query.rb @@ -24,7 +24,8 @@ module Capybara raise ArgumentError, "Unused parameters passed to #{self.class.name} : #{args}" unless args.empty? - @expression = selector.call(@locator, @options.merge(selector_config: { enable_aria_label: enable_aria_label, test_id: test_id })) + selector_config = { enable_aria_label: enable_aria_label, test_id: test_id } + @expression = selector.call(@locator, @options.merge(selector_config: selector_config)) warn_exact_usage @@ -34,21 +35,23 @@ module Capybara def name; selector.name; end def label; selector.label || selector.name; end - def description(applied = false) + def description(only_applied = false) desc = +'' - if !applied || applied_filters + show_for = show_for_stage(only_applied) + + if show_for[:any] desc << 'visible ' if visible == :visible desc << 'non-visible ' if visible == :hidden end desc << "#{label} #{locator.inspect}" - if !applied || applied_filters + if show_for[:any] desc << " with#{' exact' if exact_text == true} text #{options[:text].inspect}" if options[:text] desc << " with exact text #{exact_text}" if exact_text.is_a?(String) end desc << " with id #{options[:id]}" if options[:id] desc << " with classes [#{Array(options[:class]).join(',')}]" if options[:class] - desc << selector.description(node_filters: !applied || (applied_filters == :node), **options) - desc << ' that also matches the custom filter block' if @filter_block && (!applied || (applied_filters == :node)) + desc << selector.description(node_filters: show_for[:node], **options) + desc << ' that also matches the custom filter block' if @filter_block && show_for[:node] desc << " within #{@resolved_node.inspect}" if describe_within? desc end @@ -60,17 +63,17 @@ module Capybara def matches_filters?(node) return true if (@resolved_node&.== node) && options[:allow_self] - @applied_filters ||= :system + applied_filters << :system return false unless matches_system_filters?(node) - @applied_filters = :node + applied_filters << :node matches_node_filters?(node) && matches_filter_block?(node) rescue *(node.respond_to?(:session) ? node.session.driver.invalid_element_errors : []) false end def visible - case (vis = options.fetch(:visible) { @selector.default_visibility(session_options.ignore_hidden_elements, options) }) + case (vis = options.fetch(:visible) { default_visibility }) when true then :visible when false then :all else vis @@ -98,7 +101,7 @@ module Capybara # @api private def resolve_for(node, exact = nil) - @applied_filters = false + applied_filters.clear @resolved_node = node node.synchronize do children = find_nodes_by_selector_format(node, exact).map(&method(:to_element)) @@ -121,8 +124,14 @@ module Capybara private + def show_for_stage(only_applied) + lambda do |stage = :any| + !only_applied || (stage == :any ? applied_filters.any? : applied_filters.include?(stage)) + end + end + def applied_filters - @applied_filters ||= false + @applied_filters ||= [] end def find_selector(locator) @@ -183,17 +192,21 @@ module Capybara end end + def filter_set(name) + ::Capybara::Selector::FilterSet.all[name] + end + def node_filters if options.key?(:filter_set) - ::Capybara::Selector::FilterSet.all[options[:filter_set]].node_filters + filter_set(options[:filter_set]) else - @selector.node_filters - end + @selector + end.node_filters end def expression_filters filters = @selector.expression_filters - filters.merge ::Capybara::Selector::FilterSet.all[options[:filter_set]].expression_filters if options.key?(:filter_set) + filters.merge filter_set(options[:filter_set]).expression_filters if options.key?(:filter_set) filters end @@ -253,7 +266,7 @@ module Capybara unapplied_options = options.keys - valid_keys expression_filters.inject(expression) do |expr, (name, ef)| if ef.matcher? - unapplied_options.select { |option_name| ef.handles_option?(option_name) }.inject(expr) do |memo, option_name| + unapplied_options.select(&ef.method(:handles_option?)).inject(expr) do |memo, option_name| unapplied_options.delete(option_name) ef.apply_filter(memo, option_name, options[option_name]) end @@ -348,6 +361,10 @@ module Capybara !!node.text(text_visible, normalize_ws: normalize_ws).match(regexp) end + def default_visibility + @selector.default_visibility(session_options.ignore_hidden_elements, options) + end + def builder selector.builder end diff --git a/lib/capybara/queries/style_query.rb b/lib/capybara/queries/style_query.rb index 31495201..ea4b9f0d 100644 --- a/lib/capybara/queries/style_query.rb +++ b/lib/capybara/queries/style_query.rb @@ -5,7 +5,7 @@ module Capybara module Queries class StyleQuery < BaseQuery def initialize(expected_styles, session_options:, **options) - @expected_styles = expected_styles.each_with_object({}) { |(style, value), str_keys| str_keys[style.to_s] = value } + @expected_styles = stringify_keys(expected_styles) @options = options @actual_styles = {} super(@options) @@ -33,6 +33,10 @@ module Capybara private + def stringify_keys(hsh) + hsh.each_with_object({}) { |(k, v), str_keys| str_keys[k.to_s] = v } + end + def valid_keys %i[wait] end diff --git a/lib/capybara/queries/text_query.rb b/lib/capybara/queries/text_query.rb index d1a21ddb..8f8bbdd3 100644 --- a/lib/capybara/queries/text_query.rb +++ b/lib/capybara/queries/text_query.rb @@ -65,7 +65,7 @@ module Capybara insensitive_count = @actual_text.scan(insensitive_regexp).size return if insensitive_count == @count - "it was found #{insensitive_count} #{Capybara::Helpers.declension('time', 'times', insensitive_count)} using a case insensitive search" + "it was found #{occurrences insensitive_count} using a case insensitive search" end def invisible_message @@ -73,7 +73,7 @@ module Capybara invisible_count = invisible_text.scan(@search_regexp).size return if invisible_count == @count - "it was found #{invisible_count} #{Capybara::Helpers.declension('time', 'times', invisible_count)} including non-visible text" + "it was found #{occurrences invisible_count} including non-visible text" rescue StandardError # An error getting the non-visible text (if element goes out of scope) should not affect the response nil diff --git a/lib/capybara/queries/title_query.rb b/lib/capybara/queries/title_query.rb index 325f8d79..847fe46d 100644 --- a/lib/capybara/queries/title_query.rb +++ b/lib/capybara/queries/title_query.rb @@ -8,7 +8,7 @@ module Capybara @expected_title = expected_title.is_a?(Regexp) ? expected_title : expected_title.to_s @options = options super(@options) - @search_regexp = Capybara::Helpers.to_regexp(@expected_title, all_whitespace: true, exact: options.fetch(:exact, false)) + @search_regexp = Helpers.to_regexp(@expected_title, all_whitespace: true, exact: options.fetch(:exact, false)) assert_valid_keys end diff --git a/lib/capybara/rack_test/node.rb b/lib/capybara/rack_test/node.rb index 7a892bb2..b1dab8c3 100644 --- a/lib/capybara/rack_test/node.rb +++ b/lib/capybara/rack_test/node.rb @@ -96,9 +96,11 @@ class Capybara::RackTest::Node < Capybara::Driver::Node return true if string_node.disabled? if %w[option optgroup].include? tag_name - find_xpath('parent::*[self::optgroup or self::select or self::datalist]')[0].disabled? + find_xpath(OPTION_OWNER_XPATH)[0].disabled? + # find_xpath('parent::*[self::optgroup or self::select or self::datalist]')[0].disabled? else - !find_xpath('parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]').empty? + !find_xpath(DISABLED_BY_FIELDSET_XPATH).empty? + # !find_xpath('parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]').empty? end end @@ -256,4 +258,18 @@ protected def textarea? tag_name == 'textarea' end + + OPTION_OWNER_XPATH = XPath.parent(:optgroup, :select, :datalist).to_s.freeze + DISABLED_BY_FIELDSET_XPATH = XPath.generate do |x| + x.parent(:fieldset)[ + XPath.attr(:disabled) + ] + x.ancestor[ + ~x.self(:legend) | + x.preceding_sibling(:legend) + ][ + x.parent(:fieldset)[ + x.attr(:disabled) + ] + ] + end.to_s.freeze end diff --git a/lib/capybara/result.rb b/lib/capybara/result.rb index b4bd30cf..a43ec18c 100644 --- a/lib/capybara/result.rb +++ b/lib/capybara/result.rb @@ -108,7 +108,8 @@ module Capybara if count.zero? message << ' but there were no matches' else - message << ", found #{count} #{Capybara::Helpers.declension('match', 'matches', count)}: " << full_results.map(&:text).map(&:inspect).join(', ') + message << ", found #{count} #{Capybara::Helpers.declension('match', 'matches', count)}: " \ + << full_results.map(&:text).map(&:inspect).join(', ') end unless rest.empty? elements = rest.map { |el| el.text rescue '<>' }.map(&:inspect).join(', ') # rubocop:disable Style/RescueModifier diff --git a/lib/capybara/rspec/features.rb b/lib/capybara/rspec/features.rb index 636ea420..e3ec4f89 100644 --- a/lib/capybara/rspec/features.rb +++ b/lib/capybara/rspec/features.rb @@ -14,10 +14,10 @@ RSpec.configure do |config| end RSpec.configure do |config| - config.alias_example_group_to :feature, capybara_feature: true, type: :feature - config.alias_example_group_to :xfeature, capybara_feature: true, type: :feature, skip: 'Temporarily disabled with xfeature' - config.alias_example_group_to :ffeature, capybara_feature: true, type: :feature, focus: true + config.alias_example_group_to :feature, :capybara_feature, type: :feature + config.alias_example_group_to :xfeature, :capybara_feature, type: :feature, skip: 'Temporarily disabled with xfeature' + config.alias_example_group_to :ffeature, :capybara_feature, :focus, type: :feature config.alias_example_to :scenario config.alias_example_to :xscenario, skip: 'Temporarily disabled with xscenario' - config.alias_example_to :fscenario, focus: true + config.alias_example_to :fscenario, :focus end diff --git a/lib/capybara/rspec/matcher_proxies.rb b/lib/capybara/rspec/matcher_proxies.rb index 2691326e..9e679375 100644 --- a/lib/capybara/rspec/matcher_proxies.rb +++ b/lib/capybara/rspec/matcher_proxies.rb @@ -27,7 +27,9 @@ if RUBY_ENGINE == 'jruby' def included(base) warn 'including Capybara::DSL in the global scope is not recommended!' if base == Object - base.send(:include, ::Capybara::RSpecMatcherProxies) if defined?(::RSpec::Matchers) && base.include?(::RSpec::Matchers) + if defined?(::RSpec::Matchers) && base.include?(::RSpec::Matchers) + base.send(:include, ::Capybara::RSpecMatcherProxies) + end super end end diff --git a/lib/capybara/rspec/matchers.rb b/lib/capybara/rspec/matchers.rb index 63a53090..428aaccc 100644 --- a/lib/capybara/rspec/matchers.rb +++ b/lib/capybara/rspec/matchers.rb @@ -129,7 +129,8 @@ module Capybara Matchers::HaveStyle.new(styles, options) end - %w[selector css xpath text title current_path link button field checked_field unchecked_field select table].each do |matcher_type| + %w[selector css xpath text title current_path link button + field checked_field unchecked_field select table].each do |matcher_type| define_method "have_no_#{matcher_type}" do |*args, &optional_filter_block| Matchers::NegatedMatcher.new(send("have_#{matcher_type}", *args, &optional_filter_block)) end diff --git a/lib/capybara/selector.rb b/lib/capybara/selector.rb index 10a53305..d7b86c7b 100644 --- a/lib/capybara/selector.rb +++ b/lib/capybara/selector.rb @@ -278,7 +278,7 @@ Capybara.add_selector(:select) do expression_filter(:with_options) do |expr, options| options.inject(expr) do |xpath, option| - xpath[Capybara::Selector.all[:option].call(option)] + xpath[self.class.all[:option].call(option)] end end @@ -325,7 +325,7 @@ Capybara.add_selector(:datalist_input) do expression_filter(:with_options) do |expr, options| options.inject(expr) do |xpath, option| - xpath[XPath.attr(:list) == XPath.anywhere(:datalist)[Capybara::Selector.all[:datalist_option].call(option)].attr(:id)] + xpath[XPath.attr(:list) == XPath.anywhere(:datalist)[self.class.all[:datalist_option].call(option)].attr(:id)] end end diff --git a/lib/capybara/selector/css.rb b/lib/capybara/selector/css.rb index 4cead1bf..33ad1b53 100644 --- a/lib/capybara/selector/css.rb +++ b/lib/capybara/selector/css.rb @@ -21,11 +21,11 @@ module Capybara end S = '\u{80}-\u{D7FF}\u{E000}-\u{FFFD}\u{10000}-\u{10FFFF}' - H = /[0-9a-fA-F]/ - UNICODE = /\\#{H}{1,6}[ \t\r\n\f]?/ - NONASCII = /[#{S}]/ - ESCAPE = /#{UNICODE}|\\[ -~#{S}]/ - NMSTART = /[_a-zA-Z]|#{NONASCII}|#{ESCAPE}/ + H = /[0-9a-fA-F]/.freeze + UNICODE = /\\#{H}{1,6}[ \t\r\n\f]?/.freeze + NONASCII = /[#{S}]/.freeze + ESCAPE = /#{UNICODE}|\\[ -~#{S}]/.freeze + NMSTART = /[_a-zA-Z]|#{NONASCII}|#{ESCAPE}/.freeze class Splitter def split(css) diff --git a/lib/capybara/selector/selector.rb b/lib/capybara/selector/selector.rb index 2e8d4b55..973d7d36 100644 --- a/lib/capybara/selector/selector.rb +++ b/lib/capybara/selector/selector.rb @@ -430,15 +430,19 @@ module Capybara def describe_all_expression_filters(**opts) expression_filters.map do |ef_name, ef| if ef.matcher? - opts.keys.map do |key| - " with #{ef_name}[#{key} => #{opts[key]}]" if ef.handles_option?(key) && !::Capybara::Queries::SelectorQuery::VALID_KEYS.include?(key) - end.join + handled_custom_keys(ef, opts.keys).map { |key| " with #{ef_name}[#{key} => #{opts[key]}]" }.join elsif opts.key?(ef_name) " with #{ef_name} #{opts[ef_name]}" end end.join end + def handled_custom_keys(filter, keys) + keys.select do |key| + filter.handles_option?(key) && !::Capybara::Queries::SelectorQuery::VALID_KEYS.include?(key) + end + end + def find_by_attr(attribute, value) finder_name = "find_by_#{attribute}_attr" if respond_to?(finder_name, true) diff --git a/lib/capybara/selenium/driver.rb b/lib/capybara/selenium/driver.rb index 9abb6024..9178ad8a 100644 --- a/lib/capybara/selenium/driver.rb +++ b/lib/capybara/selenium/driver.rb @@ -352,11 +352,15 @@ private when :chrome extend ChromeDriver when :firefox - require 'capybara/selenium/patches/pause_duration_fix' if sel_driver.capabilities['moz:geckodriverVersion']&.start_with?('0.22.') + require 'capybara/selenium/patches/pause_duration_fix' if pause_broken?(sel_driver) extend MarionetteDriver if sel_driver.capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities) end end + def pause_broken?(driver) + driver.capabilities['moz:geckodriverVersion']&.start_with?('0.22.') + end + def setup_exit_handler main = Process.pid at_exit do diff --git a/lib/capybara/selenium/node.rb b/lib/capybara/selenium/node.rb index b589a69c..6c837741 100644 --- a/lib/capybara/selenium/node.rb +++ b/lib/capybara/selenium/node.rb @@ -170,22 +170,9 @@ class Capybara::Selenium::Node < Capybara::Driver::Node selector = node[:tagName] if node[:namespaceURI] != default_ns selector = XPath.child.where((XPath.local_name == selector) & (XPath.namespace_uri == node[:namespaceURI])).to_s - selector end - if parent - siblings = parent.find_xpath(selector) - selector += case siblings.size - when 0 - '[ERROR]' # IE doesn't support full XPath (namespace-uri, etc) - when 1 - '' # index not necessary when only one matching element - else - idx = siblings.index(node) - # Element may not be found in the siblings if it has gone away - idx.nil? ? '[ERROR]' : "[#{idx + 1}]" - end - end + selector += sibling_index(parent, node, selector) if parent result.push selector end @@ -203,6 +190,20 @@ protected private + def sibling_index(parent, node, selector) + siblings = parent.find_xpath(selector) + case siblings.size + when 0 + '[ERROR]' # IE doesn't support full XPath (namespace-uri, etc) + when 1 + '' # index not necessary when only one matching element + else + idx = siblings.index(node) + # Element may not be found in the siblings if it has gone away + idx.nil? ? '[ERROR]' : "[#{idx + 1}]" + end + end + def boolean_attr(val) val && (val != 'false') end diff --git a/lib/capybara/selenium/nodes/marionette_node.rb b/lib/capybara/selenium/nodes/marionette_node.rb index 803fa3d3..27fbdcd4 100644 --- a/lib/capybara/selenium/nodes/marionette_node.rb +++ b/lib/capybara/selenium/nodes/marionette_node.rb @@ -9,8 +9,9 @@ class Capybara::Selenium::MarionetteNode < Capybara::Selenium::Node super rescue ::Selenium::WebDriver::Error::ElementNotInteractableError if tag_name == 'tr' - warn 'You are attempting to click a table row which has issues in geckodriver/marionette - see https://github.com/mozilla/geckodriver/issues/1228. ' \ - 'Your test should probably be clicking on a table cell like a user would. Clicking the first cell in the row instead.' + warn 'You are attempting to click a table row which has issues in geckodriver/marionette - '\ + 'see https://github.com/mozilla/geckodriver/issues/1228. Your test should probably be '\ + 'clicking on a table cell like a user would. Clicking the first cell in the row instead.' return find_css('th:first-child,td:first-child')[0].click(keys, options) end raise @@ -26,7 +27,8 @@ class Capybara::Selenium::MarionetteNode < Capybara::Selenium::Node if %w[option optgroup].include? tag_name find_xpath('parent::*[self::optgroup or self::select]')[0].disabled? else - !find_xpath('parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]').empty? + !find_xpath(DISABLED_BY_FIELDSET_XPATH).empty? + # !find_xpath('parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]').empty? end end @@ -99,14 +101,27 @@ private return nil unless local_file raise ArgumentError, "You may only upload files: #{local_file.inspect}" unless File.file?(local_file) - result = bridge.http.call(:post, "session/#{bridge.session_id}/file", file: Selenium::WebDriver::Zipper.zip_file(local_file)) - result['value'] + file = ::Selenium::WebDriver::Zipper.zip_file(local_file) + bridge.http.call(:post, "session/#{bridge.session_id}/file", file: file)['value'] end def browser_version driver.browser.capabilities[:browser_version].to_f end + DISABLED_BY_FIELDSET_XPATH = XPath.generate do |x| + x.parent(:fieldset)[ + x.attr(:disabled) + ] + x.ancestor[ + ~x.self(:legned) | + x.preceding_sibling(:legend) + ][ + x.parent(:fieldset)[ + x.attr(:disabled) + ] + ] + end.to_s.freeze + class ModifierKeysStack def initialize @stack = [] diff --git a/lib/capybara/selenium/patches/pause_duration_fix.rb b/lib/capybara/selenium/patches/pause_duration_fix.rb index 8d979af4..f5a139d0 100644 --- a/lib/capybara/selenium/patches/pause_duration_fix.rb +++ b/lib/capybara/selenium/patches/pause_duration_fix.rb @@ -6,6 +6,4 @@ module PauseDurationFix end end -if defined?(::Selenium::WebDriver::Interactions::Pause) - ::Selenium::WebDriver::Interactions::Pause.prepend PauseDurationFix -end +::Selenium::WebDriver::Interactions::Pause.prepend PauseDurationFix diff --git a/lib/capybara/server.rb b/lib/capybara/server.rb index b0b335bf..73781010 100644 --- a/lib/capybara/server.rb +++ b/lib/capybara/server.rb @@ -18,7 +18,12 @@ module Capybara attr_reader :app, :port, :host - def initialize(app, *deprecated_options, port: Capybara.server_port, host: Capybara.server_host, reportable_errors: Capybara.server_errors, extra_middleware: []) + def initialize(app, + *deprecated_options, + port: Capybara.server_port, + host: Capybara.server_host, + reportable_errors: Capybara.server_errors, + extra_middleware: []) warn 'Positional arguments, other than the application, to Server#new are deprecated, please use keyword arguments' unless deprecated_options.empty? @app = app @extra_middleware = extra_middleware diff --git a/lib/capybara/server/animation_disabler.rb b/lib/capybara/server/animation_disabler.rb index 92f6bc47..001f81f4 100644 --- a/lib/capybara/server/animation_disabler.rb +++ b/lib/capybara/server/animation_disabler.rb @@ -16,7 +16,7 @@ module Capybara def initialize(app) @app = app - @disable_markup = format(DISABLE_MARKUP_TEMPLATE, selector: AnimationDisabler.selector_for(Capybara.disable_animation)) + @disable_markup = format(DISABLE_MARKUP_TEMPLATE, selector: self.class.selector_for(Capybara.disable_animation)) end def call(env) diff --git a/lib/capybara/session.rb b/lib/capybara/session.rb index 3d9a880a..0a90f41c 100644 --- a/lib/capybara/session.rb +++ b/lib/capybara/session.rb @@ -819,7 +819,9 @@ module Capybara end def prepare_path(path, extension) - File.expand_path(path || default_fn(extension), config.save_path).tap { |p_path| FileUtils.mkdir_p(File.dirname(p_path)) } + File.expand_path(path || default_fn(extension), config.save_path).tap do |p_path| + FileUtils.mkdir_p(File.dirname(p_path)) + end end def default_fn(extension) diff --git a/lib/capybara/spec/session/has_css_spec.rb b/lib/capybara/spec/session/has_css_spec.rb index 6d679360..70eea0f6 100644 --- a/lib/capybara/spec/session/has_css_spec.rb +++ b/lib/capybara/spec/session/has_css_spec.rb @@ -50,17 +50,17 @@ Capybara::SpecHelper.spec '#has_css?' do it 'should be able to generate an error message if the scope is a sibling' do el = @session.find(:css, '#first') @session.within el.sibling(:css, '#second') do - expect { + expect do expect(@session).to have_css('a#not_on_page') - }.to raise_error /there were no matches/ + end.to raise_error(/there were no matches/) end end it 'should be able to generate an error message if the scope is a sibling from XPath' do el = @session.find(:css, '#first').find(:xpath, './following-sibling::*[1]') do - expect { + expect do expect(el).to have_css('a#not_on_page') - }.to raise_error /there were no matches/ + end.to raise_error(/there were no matches/) end end end