require 'howitzer/web/capybara_context_holder'
module Howitzer
module Web
# This module combines element dsl methods
module ElementDsl
# This module holds element helper methods
module Helpers
private
def lambda_args(*args, **keyword_args)
{
lambda_args: {
args: args,
keyword_args: keyword_args
}
}
end
end
include CapybaraContextHolder
include Helpers
def self.included(base) # :nodoc:
base.extend(ClassMethods)
end
def convert_arguments(args, options, block_args, block_options)
conv_args = args.map { |el| el.is_a?(Proc) ? proc_to_selector(el, block_args, block_options) : el }
args_options = pop_options_from_array(conv_args)
block_args_options = pop_options_from_array(block_args)
conv_options = [args_options, options, block_args_options, block_options].map do |el|
el.transform_keys(&:to_sym)
end.reduce(&:merge).except(:lambda_args)
[conv_args, conv_options]
end
def proc_to_selector(proc, block_args, block_options)
lambda_args = extract_lambda_args(block_args, block_options)
if lambda_args
if lambda_args[:keyword_args].present?
proc.call(*lambda_args[:args], **lambda_args[:keyword_args])
else
proc.call(*lambda_args[:args])
end
else
puts "WARNING! Passing lambda arguments with element options is deprecated.\n" \
"Please use 'lambda_args' method, for example: foo_element(lambda_args(title: 'Example'), wait: 10)"
proc.call(*block_args.shift(proc.arity))
end
end
def extract_lambda_args(block_args, block_options)
(block_args.first.is_a?(Hash) && block_args.first[:lambda_args]) || block_options[:lambda_args]
end
def pop_options_from_array(value)
if value.last.is_a?(Hash) && !value.last.key?(:lambda_args)
value.pop
else
{}
end
end
# This module holds element dsl methods
module ClassMethods
include Helpers
protected
# Creates a group of methods to interact with described HTML element(s) on page
# @note This method generates following dynamic methods:
#
# element_name_element - equals capybara #find(...) method
#
# element_name_elements - equals capybara #all(...) method
#
# element_name_elements.first - equals capybara #first(...) method
#
# wait_for_element_name_element - equals capybara #find(...) method but returns nil
#
# within_element_name_element - equals capybara #within(...) method
#
# has_element_name_element? - equals capybara #has_selector(...) method
#
# has_no_element_name_element? - equals capybara #has_no_selector(...) method
# @param name [Symbol, String] an unique element name
# @param args [Array] original Capybara arguments. For details, see `Capybara::Node::Finders#all`.
# @param options [Hash] original Capybara options. For details, see `Capybara::Node::Finders#all`.
# @example Using in a page class
# class HomePage < Howitzer::Web::Page
# element :top_panel, '.top'
# element :bottom_panel, '.bottom'
# element :new_button, :xpath, ".//*[@name='New']"
#
# def press_top_new_button
# within_top_panel_element do
# new_button_element.click
# end
# end
#
# def press_bottom_new_button
# within_bottom_panel_element do
# new_button_element.click
# end
# end
# end
#
# HomePage.on do
# is_expected.to have_top_panel_element
# press_top_new_element
# is_expected.to have_no_new_button_element(match: :first)
# end
# @example Using in a section class
# class MenuSection < Howitzer::Web::Section
# me '.main-menu'
# element :menu_item, '.item'
#
# def menu_items
# menu_item_elements.map(&:text)
# end
# end
# @raise [BadElementParamsError] if wrong element arguments
# @!visibility public
def element(name, *args, **options)
validate_arguments!(args)
define_element(name, args, options)
define_elements(name, args, options)
define_wait_for_element(name, args, options)
define_within_element(name, args, options)
define_has_element(name, args, options)
define_has_no_element(name, args, options)
end
private
def validate_arguments!(args)
return unless args.map(&:class).count(Proc) > 1
raise Howitzer::BadElementParamsError, 'Using more than 1 proc in arguments is forbidden'
end
def define_element(name, args, options)
define_method("#{name}_element") do |*block_args, **block_options|
conv_args, conv_options = convert_arguments(args, options, block_args, block_options)
if conv_options.present?
capybara_context.find(*conv_args, **conv_options)
else
capybara_context.find(*conv_args)
end
end
private "#{name}_element"
end
def define_elements(name, args, options)
define_method("#{name}_elements") do |*block_args, **block_options|
conv_args, conv_options = convert_arguments(args, options, block_args, block_options)
if conv_options.present?
capybara_context.all(*conv_args, **conv_options)
else
capybara_context.all(*conv_args)
end
end
private "#{name}_elements"
end
def define_wait_for_element(name, args, options)
define_method("wait_for_#{name}_element") do |*block_args, **block_options|
conv_args, conv_options = convert_arguments(args, options, block_args, block_options)
if conv_options.present?
capybara_context.find(*conv_args, **conv_options)
else
capybara_context.find(*conv_args)
end
return nil
end
private "wait_for_#{name}_element"
end
def define_within_element(name, args, options)
define_method("within_#{name}_element") do |*block_args, **block_options, &block|
conv_args, conv_options = convert_arguments(args, options, block_args, block_options)
new_scope = if conv_options.present?
capybara_context.find(*conv_args, **conv_options)
else
capybara_context.find(*conv_args)
end
begin
capybara_scopes.push(new_scope)
block.call
ensure
capybara_scopes.pop
end
end
end
def define_has_element(name, args, options)
define_method("has_#{name}_element?") do |*block_args, **block_options|
conv_args, conv_options = convert_arguments(args, options, block_args, block_options)
if conv_options.present?
capybara_context.has_selector?(*conv_args, **conv_options)
else
capybara_context.has_selector?(*conv_args)
end
end
end
def define_has_no_element(name, args, options)
define_method("has_no_#{name}_element?") do |*block_args, **block_options|
conv_args, conv_options = convert_arguments(args, options, block_args, block_options)
if conv_options.present?
capybara_context.has_no_selector?(*conv_args, **conv_options)
else
capybara_context.has_no_selector?(*conv_args)
end
end
end
end
end
end
end