# frozen_string_literal: truerequire'capybara/selector/filter_set'require'capybara/selector/css'require'xpath'# Patch XPath to allow a nil condition in wheremoduleXPathclassRendererundef:whereifmethod_defined?(:where)defwhere(on,condition)condition=condition.to_sif!condition.empty?"#{on}[#{condition}]"elseon.to_sendendendendmoduleCapybaraclassSelectorattr_reader:name,:formatclass<<selfdefall@selectors||={}# rubocop:disable Naming/MemoizedInstanceVariableNameenddefadd(name,&block)all[name.to_sym]=Capybara::Selector.new(name.to_sym,&block)enddefupdate(name,&block)all[name.to_sym].instance_eval(&block)enddefremove(name)all.delete(name.to_sym)endenddefinitialize(name,&block)@name=name@filter_set=FilterSet.add(name){}@match=nil@label=nil@failure_message=nil@description=nil@format=nil@expression=nil@expression_filters={}@default_visibility=nilinstance_eval(&block)enddefcustom_filters@filter_set.filtersenddefnode_filters@filter_set.node_filtersenddefexpression_filters@filter_set.expression_filtersend#### Define a selector by an xpath expression## @overload xpath(*expression_filters, &block)# @param [Array<Symbol>] expression_filters ([]) Names of filters that can be implemented via this expression# @yield [locator, options] The block to use to generate the XPath expression# @yieldparam [String] locator The locator string passed to the query# @yieldparam [Hash] options The options hash passed to the query# @yieldreturn [#to_xpath, #to_s] An object that can produce an xpath expression## @overload xpath()# @return [#call] The block that will be called to generate the XPath expression#defxpath(*expression_filters,&block)ifblock@format,@expression=:xpath,blockexpression_filters.flatten.each{|ef|custom_filters[ef]=Filters::IdentityExpressionFilter.new}endformat==:xpath?@expression:nilend#### Define a selector by a CSS selector## @overload css(*expression_filters, &block)# @param [Array<Symbol>] expression_filters ([]) Names of filters that can be implemented via this CSS selector# @yield [locator, options] The block to use to generate the CSS selector# @yieldparam [String] locator The locator string passed to the query# @yieldparam [Hash] options The options hash passed to the query# @yieldreturn [#to_s] An object that can produce a CSS selector## @overload css()# @return [#call] The block that will be called to generate the CSS selector#defcss(*expression_filters,&block)ifblock@format,@expression=:css,blockexpression_filters.flatten.each{|ef|custom_filters[ef]=nil}endformat==:css?@expression:nilend#### Automatic selector detection## @yield [locator] This block takes the passed in locator string and returns whether or not it matches the selector# @yieldparam [String], locator The locator string used to determin if it matches the selector# @yieldreturn [Boolean] Whether this selector matches the locator string# @return [#call] The block that will be used to detect selector match#defmatch(&block)@match=blockifblock@matchend#### Set/get a descriptive label for the selector## @overload label(label)# @param [String] label A descriptive label for this selector - used in error messages# @overload label()# @return [String] The currently set label#deflabel(label=nil)@label=labeliflabel@labelend#### Description of the selector## @param [Hash] options The options of the query used to generate the description# @return [String] Description of the selector when used with the options passed#defdescription(**options)@filter_set.description(options)enddefcall(locator,**options)ifformat@expression.call(locator,options)elsewarn"Selector has no format"endend#### Should this selector be used for the passed in locator## This is used by the automatic selector selection mechanism when no selector type is passed to a selector query## @param [String] locator The locator passed to the query# @return [Boolean] Whether or not to use this selector#defmatch?(locator)@matchand@match.call(locator)end#### Define a non-expression filter for use with this selector## @overload filter(name, *types, options={}, &block)# @param [Symbol] name The filter name# @param [Array<Symbol>] types The types of the filter - currently valid types are [:boolean]# @param [Hash] options ({}) Options of the filter# @option options [Array<>] :valid_values Valid values for this filter# @option options :default The default value of the filter (if any)# @option options :skip_if Value of the filter that will cause it to be skipped#deffilter(name,*types_and_options,&block)add_filter(name,Filters::NodeFilter,*types_and_options,&block)enddefexpression_filter(name,*types_and_options,&block)add_filter(name,Filters::ExpressionFilter,*types_and_options,&block)enddeffilter_set(name,filters_to_use=nil)f_set=FilterSet.all[name]f_set.filters.eachdo|n,filter|custom_filters[n]=filteriffilters_to_use.nil?||filters_to_use.include?(n)endf_set.descriptions.each{|desc|@filter_set.describe(&desc)}enddefdescribe(&block)@filter_set.describe(&block)end#### Set the default visibility mode that shouble be used if no visibile option is passed when using the selector.# If not specified will default to the behavior indicated by Capybara.ignore_hidden_elements## @param [Symbol] default_visibility Only find elements with the specified visibility:# * :all - finds visible and invisible elements.# * :hidden - only finds invisible elements.# * :visible - only finds visible elements.defvisible(default_visibility)@default_visibility=default_visibilityenddefdefault_visibility(fallback=Capybara.ignore_hidden_elements)if@default_visibility.nil?fallbackelse@default_visibilityendendprivatedefadd_filter(name,filter_class,*types,**options,&block)types.each{|k|options[k]=true}custom_filters[name]=filter_class.new(name,block,options)enddeflocate_field(xpath,locator,enable_aria_label: false,**_options)locate_xpath=xpath# Need to save original xpath for the label wrapiflocatorlocator=locator.to_sattr_matchers=[XPath.attr(:id)==locator,XPath.attr(:name)==locator,XPath.attr(:placeholder)==locator,XPath.attr(:id)==XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for)].reduce(:|)attr_matchers|=XPath.attr(:'aria-label').is(locator)ifenable_aria_labellocate_xpath=locate_xpath[attr_matchers]locate_xpath=locate_xpath.union(XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath))end# locate_xpath = [:name, :placeholder].inject(locate_xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }locate_xpathenddefdescribe_all_expression_filters(**opts)expression_filters.map{|ef|" with #{ef}#{opts[ef]}"ifopts.key?(ef)}.joinenddeffind_by_attr(attribute,value)finder_name="find_by_#{attribute}_attr"ifrespond_to?(finder_name,true)send(finder_name,value)elsevalue?XPath.attr(attribute)==value:nilendenddeffind_by_class_attr(classes)Array(classes).map{|klass|XPath.attr(:class).contains_word(klass)}.reduce(:&)endendend