module CapybaraTestHelpers::Selectors

Public: Adds aliasing for element selectors to make it easier to encapsulate how to find a particular kind of element in the UI.

Constants

SELECTOR_SEPARATOR

Public Class Methods

included(base) click to toggle source
# File lib/capybara_test_helpers/selectors.rb, line 38
def self.included(base)
  base.extend(ClassMethods)
end

Public Instance Methods

resolve_alias_for_selector_query(args, kwargs, filter_block) click to toggle source

Internal: Inspects the arguments for a SelectorQuery, and resolves a selector alias if provided.

Returns a pair of arguments and keywords to initialize a SelectorQuery.

# File lib/capybara_test_helpers/selectors.rb, line 97
def resolve_alias_for_selector_query(args, kwargs, filter_block)
  # Extract the explicitly provided selector, if any. Example: `find_button`.
  explicit_type = args.shift if selectors.key?(args[1])

  if selectors.key?(args[0])
    args, kwargs = locator_for(*args, **kwargs)

    # Remove the type provided by the alias, and add back the explicit one.
    if explicit_type
      args.shift if args[0].is_a?(Symbol)
      args.unshift(explicit_type)
    end
  end

  [args, kwargs, wrap_filter(filter_block)]
end
selectors() click to toggle source

Public: Returns the available selectors for the test helper, or an empty Hash if selectors are not defined.

# File lib/capybara_test_helpers/selectors.rb, line 89
def selectors
  self.class.selectors
end

Private Instance Methods

combine_css_selectors(selectors) click to toggle source

Internal: Combines parent and child classes to preserve the order.

# File lib/capybara_test_helpers/selectors.rb, line 168
def combine_css_selectors(selectors)
  return selectors unless selectors.size > 1 && selectors.all? { |selector| selector.is_a?(String) }

  selectors.reduce { |parent_selectors, children_selectors|
    parent_selectors.split(SELECTOR_SEPARATOR).flat_map { |parent_selector|
      children_selectors.split(SELECTOR_SEPARATOR).map { |children_selector|
        "#{ parent_selector }#{ children_selector }"
      }
    }.join(SELECTOR_SEPARATOR)
  }
end
combine_locator_fragments(fragments) click to toggle source

Internal: Resolves a complex locator alias, which might reference other locator aliases as well.

# File lib/capybara_test_helpers/selectors.rb, line 148
def combine_locator_fragments(fragments)
  return fragments unless fragments.any? { |fragment| fragment.is_a?(Symbol) }

  fragments = fragments.map { |fragment| resolve_locator_alias(fragment) }
  flat_fragments = fragments.flatten(1)
  type = flat_fragments.shift if flat_fragments.first.is_a?(Symbol)

  # Only flatten fragments if it's CSS or XPath
  if type.nil? || type == :css || type == :xpath
    fragments = flat_fragments
  else
    type = nil
  end

  options = fragments.pop if fragments.last.is_a?(Hash)

  [type, *combine_css_selectors(fragments), options].compact
end
locator_for(*args, **kwargs) click to toggle source

Internal: Returns the query locator defined under the specified alias.

NOTE: In most cases, the query locator is a simple CSS selector, but it also supports any of the built-in Capybara selectors.

Returns an Array with the Capybara locator arguments, and options if any.

# File lib/capybara_test_helpers/selectors.rb, line 127
def locator_for(*args, **kwargs)
  if args.size == 1
    args = [*Array.wrap(resolve_locator_alias(args.first))]
    kwargs = args.pop.deep_merge(kwargs) if args.last.is_a?(Hash)
  end
  [args, kwargs]
rescue KeyError => error
  raise NotImplementedError, "A selector in #{ self.class.name } is not defined, #{ error.message }"
end
registered_selector?(name) click to toggle source

Internal: Checks if there's a Capybara selector defined by that name.

# File lib/capybara_test_helpers/selectors.rb, line 117
def registered_selector?(name)
  Capybara::Selector.all.key?(name)
end
resolve_locator_alias(fragment) click to toggle source

Internal: Resolves one of the segments of a locator alias.

# File lib/capybara_test_helpers/selectors.rb, line 138
def resolve_locator_alias(fragment)
  return fragment unless fragment.is_a?(Symbol) && (selectors.key?(fragment) || !registered_selector?(fragment))

  locator = selectors.fetch(fragment)

  locator.is_a?(Array) ? combine_locator_fragments(locator) : locator
end