lib/howitzer/web/page_dsl.rb
module Howitzer
module Web
# This module combines page dsl methods
module PageDsl
# This class is for private usage only
class PageScope
include RSpec::Matchers
include RSpec::Wait
def initialize(page_klass, &block)
self.page_klass = page_klass
self.outer_context = eval('self', block.binding) if block.present?
instance_eval(&block)
end
# Makes current page as a subject for Rspec expectations
# @example
# HomePage.on { expect(HomePage.given).to have_menu_section } # Bad
# HomePage.on { is_expected.to have_menu_section } # Good
def is_expected # rubocop:disable Naming/PredicateName
expect(page_klass.given)
end
# Proxies all methods to a page instance.
#
# @note There are some exceptions:
# * Methods with `be_` and `have_` prefixes are excluded
# * `out` method extracts an instance variable from an original context if starts from @.
# Otherwise it executes a method from an original context
def method_missing(name, *args, **options, &block)
return super if name =~ /\A(?:be|have)_/
return eval_in_out_context(*args, **options, &block) if name == :out
if options.present?
page_klass.given.send(name, *args, **options, &block)
else
page_klass.given.send(name, *args, &block)
end
end
# Makes proxied methods to be evaludated and returned as a proc
# @see #method_missing
def respond_to_missing?(name, include_private = false)
name !~ /\A(?:be|have)_/ || super
end
private
def eval_in_out_context(*args, **options, &block)
return nil if args.empty?
name = args.shift
return get_outer_instance_variable(name) if name.to_s.start_with?('@')
if options.present?
outer_context.send(name, *args, **options, &block)
else
outer_context.send(name, *args, &block)
end
end
def get_outer_instance_variable(name)
outer_context.instance_variable_get(name)
end
attr_accessor :page_klass, :outer_context
end
def self.included(base) # :nodoc:
base.extend(ClassMethods)
end
# This module holds page dsl class methods
module ClassMethods
# Allows to execute page methods in context of the page.
# @note It additionally checks the page is really displayed
# on each method call, otherwise it raises error
# @example Standard case
# LoginPage.open
# LoginPage.on do
# fill_form(name: 'John', email: 'jkarpensky@gmail.com')
# submit_form
# end
# @example More complex case with outer context
# @name = 'John'
# def email(domain = 'gmail.com')
# "jkarpensky@#{domain}"
# end
# LoginPage.open
# LoginPage.on do
# fill_form(name: out(:@name), email: out(:email, 'yahoo.com'))
# submit_form
# end
def on(&block)
PageScope.new(self, &block)
nil
end
end
end
end
end