lib/watir/locators/element/selector_builder.rb in watir-6.14.0 vs lib/watir/locators/element/selector_builder.rb in watir-6.15.0
- old
+ new
@@ -1,81 +1,116 @@
module Watir
module Locators
class Element
class SelectorBuilder
+ include Exception
attr_reader :custom_attributes
- VALID_WHATS = [Array, String, Regexp, TrueClass, FalseClass, ::Symbol].freeze
+ VALID_WHATS = [String, Regexp, TrueClass, FalseClass].freeze
WILDCARD_ATTRIBUTE = /^(aria|data)_(.+)$/
- def initialize(query_scope, selector, valid_attributes)
- @query_scope = query_scope # either element or browser
- @selector = selector
+ def initialize(valid_attributes)
@valid_attributes = valid_attributes
@custom_attributes = []
end
- def normalized_selector
- selector = {}
+ def build(selector)
+ inspected = selector.inspect
+ @selector = selector
+ normalize_selector
- @selector.each do |how, what|
- check_type(how, what)
+ xpath_css = (@selector.keys & %i[xpath css]).each_with_object({}) do |key, hash|
+ hash[key] = @selector.delete(key)
+ end
- how, what = normalize_selector(how, what)
- selector[how] = what
+ built = if xpath_css.empty?
+ build_wd_selector(@selector)
+ else
+ process_xpath_css(xpath_css)
+ xpath_css
+ end
+
+ @selector.delete(:index) if @selector[:index]&.zero?
+
+ Watir.logger.debug "Converted #{inspected} to #{built}, with #{@selector.inspect} to match"
+ [built, @selector]
+ end
+
+ def normalize_selector
+ if @selector.key?(:class) && @selector.key?(:class_name)
+ raise LocatorException, 'Can not use both :class and :class_name locators'
end
- selector
+ if @selector[:adjacent] == :ancestor && @selector.key?(:text)
+ raise LocatorException, 'Can not find parent element with text locator'
+ end
+
+ @selector.keys.each do |key|
+ check_type(key, @selector[key])
+
+ how, what = normalize_locator(key, @selector.delete(key))
+ @selector[how] = what
+ end
end
def check_type(how, what)
case how
+ when :adjacent
+ return raise_unless(what, ::Symbol)
+ when :xpath, :css
+ return raise_unless(what, String)
when :index
- raise_unless_int(what)
+ return raise_unless(what, Integer)
when :visible
- raise_unless_boolean(what)
- when :visible_text
- raise_unless_str_regex(what)
- else
- if what.is_a?(Array) && !%i[class class_name].include?(how)
- raise TypeError, 'Only :class locator can have a value of an Array'
- end
- raise TypeError, 'Symbol is not a valid value' if what.is_a?(Symbol) && how != :adjacent
- return if VALID_WHATS.any? { |t| what.is_a? t }
+ return raise_unless(what, :boolean)
+ when :tag_name
+ return raise_unless(what, :string_or_regexp_or_symbol)
+ when :visible_text, :text
+ return raise_unless(what, :string_or_regexp)
+ when :class, :class_name
+ if what.is_a?(Array)
+ raise LocatorException, 'Can not locate elements with an empty Array for :class' if what.empty?
- raise TypeError, "expected one of #{VALID_WHATS.inspect}, got #{what.inspect}:#{what.class}"
+ what.each do |klass|
+ raise_unless(klass, :string_or_regexp)
+ end
+ return
+ end
end
+
+ return if VALID_WHATS.any? { |t| what.is_a? t }
+
+ raise TypeError, "expected one of #{VALID_WHATS.inspect}, got #{what.inspect}:#{what.class}"
end
def should_use_label_element?
!valid_attribute?(:label)
end
- def build(selector)
- inspect = selector.inspect
- return given_xpath_or_css(selector) if selector.key?(:xpath) || selector.key?(:css)
-
- built = build_wd_selector(selector)
- Watir.logger.debug "Converted #{inspect} to #{built}"
- built
- end
-
- def xpath_builder
- @xpath_builder ||= xpath_builder_class.new(should_use_label_element?)
- end
-
private
- def normalize_selector(how, what)
+ def normalize_locator(how, what)
case how
- when :tag_name, :text, :xpath, :index, :class, :label, :css, :visible, :visible_text, :adjacent
- # include :class since the valid attribute is 'class_name'
- # include :for since the valid attribute is 'html_for'
+ when 'text'
+ Watir.logger.deprecate "String 'text' as a locator", 'Symbol :text', ids: ['text_string']
+ [:text, what]
+ when :tag_name
+ what = what.to_s if what.is_a?(::Symbol)
[how, what]
+ when :text, :xpath, :index, :class, :css, :visible, :visible_text, :adjacent
+ [how, what]
+ when :label
+ if should_use_label_element?
+ ["#{how}_element".to_sym, what]
+ else
+ [how, what]
+ end
when :class_name
[:class, what]
when :caption
+ # This allows any element to be located with 'caption' instead of 'text'
+ Watir.logger.deprecate('Locating elements with :caption', ':text locator', ids: [:caption])
[:text, what]
else
check_custom_attribute how
[how, what]
end
@@ -85,68 +120,52 @@
return if valid_attribute?(attribute) || attribute.to_s =~ WILDCARD_ATTRIBUTE
@custom_attributes << attribute.to_s
end
- def given_xpath_or_css(selector)
- locator = {}
- locator[:xpath] = selector.delete(:xpath) if selector.key?(:xpath)
- locator[:css] = selector.delete(:css) if selector.key?(:css)
+ def process_xpath_css(xpath_css)
+ raise LocatorException, ":xpath and :css cannot be combined (#{xpath_css})" if xpath_css.size > 1
- return if locator.empty?
- raise ArgumentError, ":xpath and :css cannot be combined (#{selector.inspect})" if locator.size > 1
+ return if combine_with_xpath_or_css?(@selector)
- return locator.first unless selector.any? && !can_be_combined_with_xpath_or_css?(selector)
-
- msg = "#{locator.keys.first} cannot be combined with other selectors (#{selector.inspect})"
- raise ArgumentError, msg
+ msg = "#{xpath_css.keys.first} cannot be combined with all of these locators (#{@selector.inspect})"
+ raise LocatorException, msg
end
- def build_wd_selector(selectors)
- return if selectors.values.any? { |e| e.is_a? Regexp }
-
- build_xpath(selectors)
+ # Implement this method when creating a different selector builder
+ def build_wd_selector(selector)
+ Kernel.const_get("#{self.class.name}::XPath").new.build(selector)
end
def valid_attribute?(attribute)
@valid_attributes&.include?(attribute)
end
- def can_be_combined_with_xpath_or_css?(selector)
+ def combine_with_xpath_or_css?(selector)
keys = selector.keys
- return true if keys == [:tag_name]
-
- return keys.sort == %i[tag_name type] if selector[:tag_name] == 'input'
-
- false
+ keys.reject! { |key| %i[visible visible_text index].include? key }
+ if (keys - [:tag_name]).empty?
+ true
+ elsif selector[:tag_name] == 'input' && keys == %i[tag_name type]
+ true
+ else
+ false
+ end
end
- def build_xpath(selectors)
- xpath_builder.build(selectors)
- end
+ def raise_unless(what, type)
+ valid = if type == :boolean
+ [TrueClass, FalseClass].include?(what.class)
+ elsif type == :string_or_regexp
+ [String, Regexp].include?(what.class)
+ elsif type == :string_or_regexp_or_symbol
+ [String, Regexp, ::Symbol].include?(what.class)
+ else
+ what.is_a?(type)
+ end
+ return if valid
- def xpath_builder_class
- Kernel.const_get("#{self.class.name}::XPath")
- rescue StandardError
- XPath
- end
-
- def raise_unless_int(what)
- return if what.is_a?(Integer)
-
- raise TypeError, "expected Integer, got #{what.inspect}:#{what.class}"
- end
-
- def raise_unless_boolean(what)
- return if what.is_a?(TrueClass) || what.is_a?(FalseClass)
-
- raise TypeError, "expected TrueClass or FalseClass, got #{what.inspect}:#{what.class}"
- end
-
- def raise_unless_str_regex(what)
- return if what.is_a?(String) || what.is_a?(Regexp)
-
- raise TypeError, "expected String or Regexp, got #{what.inspect}:#{what.class}"
+ raise TypeError, "expected #{type}, got #{what.inspect}:#{what.class}"
end
end
end
end
end