diff --git a/.codeclimate.yml b/.codeclimate.yml index 8c03a952..9766a106 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -6,6 +6,9 @@ checks: file-lines: config: threshold: 500 + method-complexity: + config: + threshold: 10 engines: bundler-audit: enabled: false @@ -17,6 +20,7 @@ engines: - "lib/capybara/selector.rb" - "lib/capybara/minitest.rb" - "lib/capybara/selector/definition/" + - "lib/capybara/rspec/matchers/" config: languages: ruby: diff --git a/lib/capybara/selector/builders/css_builder.rb b/lib/capybara/selector/builders/css_builder.rb index 44290125..1f921aee 100644 --- a/lib/capybara/selector/builders/css_builder.rb +++ b/lib/capybara/selector/builders/css_builder.rb @@ -17,11 +17,7 @@ module Capybara conditions = if name == :class class_conditions(value) elsif value.is_a? Regexp - Selector::RegexpDisassembler.new(value).alternated_substrings.map do |strs| - strs.map do |str| - "[#{name}*='#{str}'#{' i' if value.casefold?}]" - end.join - end + regexp_conditions(name, value) else [attribute_conditions(name => value)] end @@ -36,6 +32,14 @@ module Capybara private + def regexp_conditions(name, value) + Selector::RegexpDisassembler.new(value).alternated_substrings.map do |strs| + strs.map do |str| + "[#{name}*='#{str}'#{' i' if value.casefold?}]" + end.join + end + end + def attribute_conditions(attributes) attributes.map do |attribute, value| case value @@ -70,7 +74,7 @@ module Capybara end.join end else - cls = Array(classes).group_by { |cl| cl.start_with?('!') && !cl.start_with?('!!!') } + cls = Array(classes).group_by { |cl| cl.match?(/^!(?!!!)/) } [(cls[false].to_a.map { |cl| ".#{Capybara::Selector::CSS.escape(cl.sub(/^!!/, ''))}" } + cls[true].to_a.map { |cl| ":not(.#{Capybara::Selector::CSS.escape(cl.slice(1..-1))})" }).join] end diff --git a/lib/capybara/selector/builders/xpath_builder.rb b/lib/capybara/selector/builders/xpath_builder.rb index 810af21e..47b262e7 100644 --- a/lib/capybara/selector/builders/xpath_builder.rb +++ b/lib/capybara/selector/builders/xpath_builder.rb @@ -48,7 +48,7 @@ module Capybara attribute_conditions(class: classes) else Array(classes).map do |klass| - if klass.start_with?('!') && !klass.start_with?('!!!') + if klass.match?(/^!(?!!!)/) !XPath.attr(:class).contains_word(klass.slice(1..-1)) else XPath.attr(:class).contains_word(klass.sub(/^!!/, '')) diff --git a/lib/capybara/selenium/driver.rb b/lib/capybara/selenium/driver.rb index 02e22984..e3713fda 100644 --- a/lib/capybara/selenium/driver.rb +++ b/lib/capybara/selenium/driver.rb @@ -300,13 +300,10 @@ private end def unhandled_alert_errors - @unhandled_alert_errors ||= [Selenium::WebDriver::Error::UnexpectedAlertOpenError].tap do |errors| - unless selenium_4? - ::Selenium::WebDriver.logger.suppress_deprecations do - errors << Selenium::WebDriver::Error::UnhandledAlertError - end - end - end + @unhandled_alert_errors ||= with_legacy_error( + [Selenium::WebDriver::Error::UnexpectedAlertOpenError], + 'UnhandledAlertError' + ) end def delete_all_cookies @@ -387,10 +384,14 @@ private end def find_modal_errors - @find_modal_errors ||= [Selenium::WebDriver::Error::TimeoutError].tap do |errors| + @find_modal_errors ||= with_legacy_error([Selenium::WebDriver::Error::TimeoutError], 'TimeOutError') + end + + def with_legacy_error(errors, legacy_error) + errors.tap do |errs| unless selenium_4? ::Selenium::WebDriver.logger.suppress_deprecations do - errors << Selenium::WebDriver::Error::TimeOutError + errs << Selenium::WebDriver::Error.const_get(legacy_error) end end end diff --git a/lib/capybara/selenium/extensions/find.rb b/lib/capybara/selenium/extensions/find.rb index 72daa3c6..8519b837 100644 --- a/lib/capybara/selenium/extensions/find.rb +++ b/lib/capybara/selenium/extensions/find.rb @@ -18,27 +18,28 @@ module Capybara hints = [] if (els.size > 2) && !ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS'] - begin - els = filter_by_text(els, texts) unless texts.empty? - hints_js, functions = build_hints_js(uses_visibility, styles) - - unless functions.empty? - hints = es_context.execute_script(hints_js, els).map! do |results| - hint = {} - hint[:style] = results.pop if functions.include?(:style_func) - hint[:visible] = results.pop if functions.include?(:vis_func) - hint - end - end - rescue ::Selenium::WebDriver::Error::StaleElementReferenceError, - ::Capybara::NotSupportedByDriverError - # warn 'Unexpected Stale Element Error - skipping optimization' - hints = [] - end + els = filter_by_text(els, texts) unless texts.empty? + hints = gather_hints(els, uses_visibility: uses_visibility, styles: styles) end els.map.with_index { |el, idx| build_node(el, hints[idx] || {}) } end + def gather_hints(elements, uses_visibility:, styles:) + hints_js, functions = build_hints_js(uses_visibility, styles) + return [] unless functions.any? + + es_context.execute_script(hints_js, elements).map! do |results| + hint = {} + hint[:style] = results.pop if functions.include?(:style_func) + hint[:visible] = results.pop if functions.include?(:vis_func) + hint + end + rescue ::Selenium::WebDriver::Error::StaleElementReferenceError, + ::Capybara::NotSupportedByDriverError + # warn 'Unexpected Stale Element Error - skipping optimization' + [] + end + def filter_by_text(elements, texts) es_context.execute_script <<~JS, elements, texts var texts = arguments[1]; diff --git a/lib/capybara/selenium/extensions/modifier_keys_stack.rb b/lib/capybara/selenium/extensions/modifier_keys_stack.rb new file mode 100644 index 00000000..5748215e --- /dev/null +++ b/lib/capybara/selenium/extensions/modifier_keys_stack.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +class Capybara::Selenium::Node + # + # @api private + # + class ModifierKeysStack + def initialize + @stack = [] + end + + def include?(key) + @stack.flatten.include?(key) + end + + def press(key) + @stack.last.push(key) + end + + def push + @stack.push [] + end + + def pop + @stack.pop + end + end +end diff --git a/lib/capybara/selenium/nodes/firefox_node.rb b/lib/capybara/selenium/nodes/firefox_node.rb index 492cb0f4..ff947b96 100644 --- a/lib/capybara/selenium/nodes/firefox_node.rb +++ b/lib/capybara/selenium/nodes/firefox_node.rb @@ -114,27 +114,4 @@ private def browser_version driver.browser.capabilities[:browser_version].to_f end - - class ModifierKeysStack - def initialize - @stack = [] - end - - def include?(key) - @stack.flatten.include?(key) - end - - def press(key) - @stack.last.push(key) - end - - def push - @stack.push [] - end - - def pop - @stack.pop - end - end - private_constant :ModifierKeysStack end diff --git a/lib/capybara/selenium/nodes/safari_node.rb b/lib/capybara/selenium/nodes/safari_node.rb index da985459..745aa54e 100644 --- a/lib/capybara/selenium/nodes/safari_node.rb +++ b/lib/capybara/selenium/nodes/safari_node.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true # require 'capybara/selenium/extensions/html5_drag' +require 'capybara/selenium/extensions/modifier_keys_stack' class Capybara::Selenium::SafariNode < Capybara::Selenium::Node # include Html5Drag @@ -118,27 +119,4 @@ private shift left_shift right_shift meta left_meta right_meta command].freeze - - class ModifierKeysStack - def initialize - @stack = [] - end - - def include?(key) - @stack.flatten.include?(key) - end - - def press(key) - @stack.last.push(key) - end - - def push - @stack.push [] - end - - def pop - @stack.pop - end - end - private_constant :ModifierKeysStack end diff --git a/lib/capybara/spec/views/with_html.erb b/lib/capybara/spec/views/with_html.erb index 8ff022f9..f23d2211 100644 --- a/lib/capybara/spec/views/with_html.erb +++ b/lib/capybara/spec/views/with_html.erb @@ -138,6 +138,9 @@ banana Ancestor
Child
+
+ ASibling +