howitzer 1.1.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +32 -0
- data/.travis.yml +1 -4
- data/.yardopts +1 -2
- data/CHANGELOG.md +28 -1
- data/Gemfile +6 -0
- data/LICENSE +1 -1
- data/README.md +55 -85
- data/Rakefile +7 -7
- data/bin/howitzer +56 -31
- data/features/cli_new.feature +162 -79
- data/features/cli_update.feature +114 -21
- data/features/step_definitions/common_steps.rb +29 -9
- data/features/support/env.rb +1 -1
- data/features/support/transformers.rb +2 -2
- data/generators/base_generator.rb +121 -49
- data/generators/config/config_generator.rb +8 -5
- data/generators/config/templates/boot.rb +14 -0
- data/generators/config/templates/capybara.rb +156 -0
- data/generators/config/templates/custom.yml +2 -3
- data/generators/config/templates/default.yml +50 -82
- data/generators/cucumber/cucumber_generator.rb +9 -9
- data/generators/cucumber/templates/common_steps.rb +4 -4
- data/generators/cucumber/templates/cucumber.rake +63 -54
- data/generators/cucumber/templates/env.rb +24 -23
- data/generators/cucumber/templates/transformers.rb +23 -15
- data/generators/emails/emails_generator.rb +4 -2
- data/generators/emails/templates/example_email.rb +4 -4
- data/generators/prerequisites/prerequisites_generator.rb +24 -0
- data/generators/prerequisites/templates/base.rb +22 -0
- data/generators/prerequisites/templates/factory_girl.rb +30 -0
- data/generators/prerequisites/templates/user.rb +7 -0
- data/generators/prerequisites/templates/users.rb +12 -0
- data/generators/root/root_generator.rb +11 -7
- data/generators/root/templates/.rubocop.yml +32 -0
- data/generators/root/templates/Gemfile.erb +27 -0
- data/generators/root/templates/Rakefile +6 -8
- data/generators/rspec/rspec_generator.rb +7 -7
- data/generators/rspec/templates/example_spec.rb +1 -1
- data/generators/rspec/templates/rspec.rake +48 -29
- data/generators/rspec/templates/spec_helper.rb +33 -32
- data/generators/tasks/tasks_generator.rb +4 -2
- data/generators/tasks/templates/common.rake +1 -16
- data/generators/turnip/templates/.rspec +1 -0
- data/generators/turnip/templates/common_steps.rb +25 -0
- data/generators/turnip/templates/example.feature +8 -0
- data/generators/turnip/templates/spec_helper.rb +56 -0
- data/generators/turnip/templates/turnip.rake +61 -0
- data/generators/turnip/templates/turnip_helper.rb +6 -0
- data/generators/turnip/turnip_generator.rb +26 -0
- data/generators/web/templates/example_page.rb +15 -0
- data/generators/web/templates/menu_section.rb +13 -0
- data/generators/web/web_generator.rb +22 -0
- data/howitzer.gemspec +14 -21
- data/lib/howitzer.rb +47 -7
- data/lib/howitzer/cache.rb +70 -0
- data/lib/howitzer/capybara_helpers.rb +194 -0
- data/lib/howitzer/email.rb +96 -104
- data/lib/howitzer/exceptions.rb +10 -6
- data/lib/howitzer/log.rb +120 -0
- data/lib/howitzer/mail_adapters.rb +7 -0
- data/lib/howitzer/mail_adapters/abstract.rb +84 -0
- data/lib/howitzer/mail_adapters/mailgun.rb +115 -0
- data/lib/howitzer/mailgun_api.rb +9 -0
- data/lib/howitzer/mailgun_api/client.rb +79 -0
- data/lib/howitzer/mailgun_api/connector.rb +37 -0
- data/lib/howitzer/mailgun_api/response.rb +28 -0
- data/lib/howitzer/tasks/framework.rake +2 -2
- data/lib/howitzer/utils.rb +7 -1
- data/lib/howitzer/utils/string_extensions.rb +66 -0
- data/lib/howitzer/version.rb +2 -1
- data/lib/howitzer/web.rb +11 -0
- data/lib/howitzer/web/base_section.rb +27 -0
- data/lib/howitzer/web/blank_page.rb +12 -0
- data/lib/howitzer/web/capybara_methods_proxy.rb +29 -0
- data/lib/howitzer/web/element_dsl.rb +109 -0
- data/lib/howitzer/web/iframe_dsl.rb +93 -0
- data/lib/howitzer/web/page.rb +173 -0
- data/lib/howitzer/web/page_dsl.rb +64 -0
- data/lib/howitzer/web/page_validator.rb +118 -0
- data/lib/howitzer/web/section.rb +27 -0
- data/lib/howitzer/web/section_dsl.rb +154 -0
- data/spec/config/custom.yml +10 -1
- data/spec/spec_helper.rb +37 -19
- data/spec/support/generator_helper.rb +12 -11
- data/spec/support/logger_helper.rb +10 -9
- data/spec/support/mailgun_unit_client.rb +52 -44
- data/spec/support/shared_examples/capybara_context_holder.rb +33 -0
- data/spec/support/shared_examples/capybara_methods_proxy.rb +94 -0
- data/spec/support/shared_examples/dynamic_section_methods.rb +35 -0
- data/spec/support/shared_examples/element_dsl.rb +119 -0
- data/spec/unit/generators/base_generator_spec.rb +64 -33
- data/spec/unit/generators/config_generator_spec.rb +11 -7
- data/spec/unit/generators/cucumber_generator_spec.rb +26 -17
- data/spec/unit/generators/emails_generator_spec.rb +10 -6
- data/spec/unit/generators/prerequisites_generator_spec.rb +53 -0
- data/spec/unit/generators/root_generator_spec.rb +50 -13
- data/spec/unit/generators/rspec_generator_spec.rb +9 -9
- data/spec/unit/generators/tasks_generator_spec.rb +6 -6
- data/spec/unit/generators/turnip_generator_spec.rb +52 -0
- data/spec/unit/generators/web_generator_spec.rb +52 -0
- data/spec/unit/lib/cache_spec.rb +85 -0
- data/spec/unit/lib/capybara_helpers_spec.rb +696 -0
- data/spec/unit/lib/email_spec.rb +104 -91
- data/spec/unit/lib/howitzer.rb +31 -0
- data/spec/unit/lib/init_spec.rb +0 -1
- data/spec/unit/lib/log_spec.rb +122 -0
- data/spec/unit/lib/mail_adapters/abstract_spec.rb +62 -0
- data/spec/unit/lib/mail_adapters/mailgun_spec.rb +163 -0
- data/spec/unit/lib/mailgun_api/client_spec.rb +58 -0
- data/spec/unit/lib/mailgun_api/connector_spec.rb +54 -0
- data/spec/unit/lib/mailgun_api/response_spec.rb +28 -0
- data/spec/unit/lib/utils/string_extensions_spec.rb +77 -0
- data/spec/unit/lib/web/base_section_spec.rb +41 -0
- data/spec/unit/lib/web/element_dsl_spec.rb +17 -0
- data/spec/unit/lib/web/iframe_dsl_spec.rb +99 -0
- data/spec/unit/lib/web/page_dsl_spec.rb +52 -0
- data/spec/unit/lib/web/page_spec.rb +304 -0
- data/spec/unit/lib/web/page_validator_spec.rb +218 -0
- data/spec/unit/lib/web/section_dsl_spec.rb +165 -0
- data/spec/unit/lib/web/section_spec.rb +61 -0
- data/spec/unit/version_spec.rb +1 -1
- metadata +116 -203
- data/GETTING_STARTED.md +0 -774
- data/generators/cucumber/templates/cucumber.yml +0 -10
- data/generators/pages/pages_generator.rb +0 -21
- data/generators/pages/templates/example_menu.rb +0 -15
- data/generators/pages/templates/example_page.rb +0 -15
- data/generators/root/templates/Gemfile +0 -7
- data/generators/root/templates/boot.rb +0 -10
- data/lib/howitzer/blank_page.rb +0 -6
- data/lib/howitzer/capybara/dsl_ex.rb +0 -15
- data/lib/howitzer/capybara/settings.rb +0 -343
- data/lib/howitzer/helpers.rb +0 -230
- data/lib/howitzer/init.rb +0 -1
- data/lib/howitzer/mailgun/client.rb +0 -65
- data/lib/howitzer/mailgun/connector.rb +0 -34
- data/lib/howitzer/mailgun/response.rb +0 -28
- data/lib/howitzer/patches/rawler_patched.rb +0 -86
- data/lib/howitzer/settings.rb +0 -27
- data/lib/howitzer/utils/data_generator/data_storage.rb +0 -88
- data/lib/howitzer/utils/data_generator/gen.rb +0 -135
- data/lib/howitzer/utils/locator_store.rb +0 -217
- data/lib/howitzer/utils/log.rb +0 -139
- data/lib/howitzer/utils/page_validator.rb +0 -133
- data/lib/howitzer/vendor/firebug-1.12.1-fx.xpi +0 -0
- data/lib/howitzer/vendor/firepath-0.9.7-fx.xpi +0 -0
- data/lib/howitzer/web_page.rb +0 -253
- data/spec/active_resource.rb +0 -0
- data/spec/config/default.yml +0 -26
- data/spec/support/boot_helper.rb +0 -15
- data/spec/unit/generators/pages_generator_spec.rb +0 -33
- data/spec/unit/lib/capybara/dsl_ex_spec.rb +0 -60
- data/spec/unit/lib/capybara/settings_spec.rb +0 -441
- data/spec/unit/lib/helpers_spec.rb +0 -1129
- data/spec/unit/lib/mailgun/client_spec.rb +0 -36
- data/spec/unit/lib/mailgun/connector_spec.rb +0 -70
- data/spec/unit/lib/mailgun/response_spec.rb +0 -28
- data/spec/unit/lib/settings_spec.rb +0 -17
- data/spec/unit/lib/utils/data_generator/data_storage_spec.rb +0 -80
- data/spec/unit/lib/utils/data_generator/gen_spec.rb +0 -90
- data/spec/unit/lib/utils/locator_store_spec.rb +0 -157
- data/spec/unit/lib/utils/log_spec.rb +0 -107
- data/spec/unit/lib/utils/page_validator_spec.rb +0 -265
- data/spec/unit/lib/web_page_spec.rb +0 -346
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'howitzer/exceptions'
|
|
2
|
+
|
|
3
|
+
module Howitzer
|
|
4
|
+
module MailgunApi
|
|
5
|
+
# A MailgunApi::Response object is instantiated for each response generated
|
|
6
|
+
# by the Client request. The Response object supports deserialization of
|
|
7
|
+
# the JSON result.
|
|
8
|
+
class Response
|
|
9
|
+
attr_accessor :body
|
|
10
|
+
attr_accessor :code
|
|
11
|
+
|
|
12
|
+
def initialize(response)
|
|
13
|
+
@body = response.body
|
|
14
|
+
@code = response.code
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Return a response as a Ruby Hash
|
|
18
|
+
# @raise [ParseError] in case of an json parsing error
|
|
19
|
+
# @return [Hash] HTTP result as hash.
|
|
20
|
+
|
|
21
|
+
def to_h
|
|
22
|
+
JSON.parse(@body)
|
|
23
|
+
rescue StandardError => e
|
|
24
|
+
raise ParseError, e.message
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
namespace :howitzer do
|
|
2
|
-
|
|
3
|
-
end
|
|
2
|
+
# This is for future usage of built-in tasks
|
|
3
|
+
end
|
data/lib/howitzer/utils.rb
CHANGED
|
@@ -2,4 +2,10 @@ require 'repeater'
|
|
|
2
2
|
require 'active_support'
|
|
3
3
|
require 'active_support/all'
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
module Howitzer
|
|
6
|
+
# This module holds different util staff
|
|
7
|
+
module Utils
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
require 'howitzer/utils/string_extensions'
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module Howitzer
|
|
2
|
+
module Utils
|
|
3
|
+
# This module extends standard String class with useful methods for Cucumber step definitions
|
|
4
|
+
module StringExtensions
|
|
5
|
+
# Opens a page by name
|
|
6
|
+
# @example
|
|
7
|
+
# 'home'.open #=> HomePage.open
|
|
8
|
+
# @see Howitzer::Web::Page.open
|
|
9
|
+
|
|
10
|
+
def open(*args)
|
|
11
|
+
as_page_class.open(*args)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Returns an instantiated page by name
|
|
15
|
+
# @example
|
|
16
|
+
# 'home'.given #=> HomePage.given
|
|
17
|
+
# @see Howitzer::Web::Page.given
|
|
18
|
+
|
|
19
|
+
def given
|
|
20
|
+
as_page_class.given
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Waits until a page is opened or raises error
|
|
24
|
+
# @example
|
|
25
|
+
# 'home'.displayed? #=> HomePage.displayed?
|
|
26
|
+
# @see Howitzer::Web::Page.displayed?
|
|
27
|
+
|
|
28
|
+
def displayed?
|
|
29
|
+
as_page_class.displayed?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Returns a page class by name
|
|
33
|
+
# @example
|
|
34
|
+
# 'home'.as_page_class #=> HomePage
|
|
35
|
+
# @see Howitzer::Web::Page
|
|
36
|
+
|
|
37
|
+
def as_page_class
|
|
38
|
+
as_class('Page')
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Returns an email class by name
|
|
42
|
+
# @example
|
|
43
|
+
# 'Reset Password'.as_email_class #=> ResetPasswordEmail
|
|
44
|
+
# @see Howitzer::Email
|
|
45
|
+
|
|
46
|
+
def as_email_class
|
|
47
|
+
as_class('Email')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Executes code in context of the page
|
|
51
|
+
# @example
|
|
52
|
+
# 'home'.on { puts 1 } #=> HomePage.on { puts 1 }
|
|
53
|
+
# @see Howitzer::Web::Page.on
|
|
54
|
+
|
|
55
|
+
def on(&block)
|
|
56
|
+
as_page_class.on(&block)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def as_class(type)
|
|
62
|
+
"#{gsub(/\s/, '_').camelize}#{type}".constantize
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
data/lib/howitzer/version.rb
CHANGED
data/lib/howitzer/web.rb
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module Howitzer
|
|
2
|
+
# This class holds everything related with web GUI
|
|
3
|
+
module Web
|
|
4
|
+
end
|
|
5
|
+
end
|
|
6
|
+
require 'howitzer/web/page_validator'
|
|
7
|
+
require 'howitzer/web/element_dsl'
|
|
8
|
+
require 'howitzer/web/section_dsl'
|
|
9
|
+
require 'howitzer/web/section'
|
|
10
|
+
require 'howitzer/web/page'
|
|
11
|
+
require 'howitzer/web/blank_page'
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'howitzer/web/capybara_methods_proxy'
|
|
2
|
+
require 'howitzer/web/element_dsl'
|
|
3
|
+
require 'howitzer/web/iframe_dsl'
|
|
4
|
+
require 'howitzer/web/section_dsl'
|
|
5
|
+
|
|
6
|
+
module Howitzer
|
|
7
|
+
module Web
|
|
8
|
+
# This class holds base functinality for sections
|
|
9
|
+
class BaseSection
|
|
10
|
+
include CapybaraMethodsProxy
|
|
11
|
+
include ElementDsl
|
|
12
|
+
include SectionDsl
|
|
13
|
+
include IframeDsl
|
|
14
|
+
|
|
15
|
+
attr_reader :parent, :capybara_context
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
attr_reader :default_finder_args
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize(parent, context)
|
|
22
|
+
@parent = parent
|
|
23
|
+
@capybara_context = context
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'capybara'
|
|
2
|
+
|
|
3
|
+
module Howitzer
|
|
4
|
+
# This module proxies required original capybara methods to recipient
|
|
5
|
+
module CapybaraMethodsProxy
|
|
6
|
+
PROXIED_CAPYBARA_METHODS = Capybara::Session::SESSION_METHODS +
|
|
7
|
+
Capybara::Session::MODAL_METHODS +
|
|
8
|
+
[:driver, :text]
|
|
9
|
+
|
|
10
|
+
# Capybara form dsl methods are not compatible with page object pattern and Howitzer gem.
|
|
11
|
+
# Instead of including Capybara::DSL module, we proxy most interesting Capybara methods and
|
|
12
|
+
# prevent using extra methods which can potentially broke main principles and framework concept
|
|
13
|
+
PROXIED_CAPYBARA_METHODS.each do |method|
|
|
14
|
+
define_method(method) { |*args, &block| Capybara.current_session.send(method, *args, &block) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Accepts or declines JS alert box by given flag
|
|
18
|
+
# @param flag [Boolean] Determines accept or decline alert box
|
|
19
|
+
|
|
20
|
+
def click_alert_box(flag)
|
|
21
|
+
if %w(selenium sauce).include? Howitzer.driver
|
|
22
|
+
alert = driver.browser.switch_to.alert
|
|
23
|
+
flag ? alert.accept : alert.dismiss
|
|
24
|
+
else
|
|
25
|
+
evaluate_script("window.confirm = function() { return #{flag}; }")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
module Howitzer
|
|
2
|
+
module Web
|
|
3
|
+
# This module combines element dsl methods
|
|
4
|
+
module ElementDsl
|
|
5
|
+
def self.included(base) #:nodoc:
|
|
6
|
+
base.extend(ClassMethods)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Returns capybara context. For example, capybara session, parent element, etc.
|
|
10
|
+
# @abstract should be defined in parent context
|
|
11
|
+
|
|
12
|
+
def capybara_context
|
|
13
|
+
raise NotImplementedError, "Please define 'capybara_context' method for class holder"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def convert_arguments(args, params)
|
|
19
|
+
args.map do |arg|
|
|
20
|
+
arg.is_a?(Proc) ? arg.call(*params) : arg
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# This module holds element dsl methods methods
|
|
25
|
+
module ClassMethods
|
|
26
|
+
protected
|
|
27
|
+
|
|
28
|
+
# Creates a group of methods to interact with described HTML element(s) on page
|
|
29
|
+
# @note This method generates following dynamic methods:
|
|
30
|
+
#
|
|
31
|
+
# <b><em>element_name</em>_element</b> - equals capybara #find(...) method
|
|
32
|
+
#
|
|
33
|
+
# <b><em>element_name</em>_elements</b> - equals capybara #all(...) method
|
|
34
|
+
#
|
|
35
|
+
# <b><em>element_name</em>_elements.first</b> - equals capybara #first(...) method
|
|
36
|
+
#
|
|
37
|
+
# <b>has_<em>element_name</em>_element?</b> - equals capybara #has_selector(...) method
|
|
38
|
+
#
|
|
39
|
+
# <b>has_no_<em>element_name</em>_element?</b> - equals capybara #has_no_selector(...) method
|
|
40
|
+
# @param name [Symbol, String] an unique element name
|
|
41
|
+
# @param args [Array] original Capybara arguments. For details, see `Capybara::Node::Finders#all.
|
|
42
|
+
# @example Using in a page class
|
|
43
|
+
# class HomePage < Howitzer::Web::Page
|
|
44
|
+
# element :new_button, :xpath, ".//*[@name='New']"
|
|
45
|
+
#
|
|
46
|
+
# def press_new_button
|
|
47
|
+
# new_button_element.click
|
|
48
|
+
# end
|
|
49
|
+
# end
|
|
50
|
+
#
|
|
51
|
+
# HomePage.on { is_expected.to have_new_button_element }
|
|
52
|
+
# HomePage.on { is_expected.to have_no_new_button_element }
|
|
53
|
+
# @example Using in a section class
|
|
54
|
+
# class MenuSection < Howitzer::Web::Section
|
|
55
|
+
# me '.main-menu'
|
|
56
|
+
# element :menu_item, '.item'
|
|
57
|
+
#
|
|
58
|
+
# def menu_items
|
|
59
|
+
# menu_item_elements.map(&:text)
|
|
60
|
+
# end
|
|
61
|
+
# end
|
|
62
|
+
# @raise [BadElementParamsError] if wrong element arguments
|
|
63
|
+
# @!visibility public
|
|
64
|
+
|
|
65
|
+
def element(name, *args)
|
|
66
|
+
validate_arguments!(args)
|
|
67
|
+
define_element(name, args)
|
|
68
|
+
define_elements(name, args)
|
|
69
|
+
define_has_element(name, args)
|
|
70
|
+
define_has_no_element(name, args)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def validate_arguments!(args)
|
|
76
|
+
return unless args.map(&:class).count(Proc) > 1
|
|
77
|
+
|
|
78
|
+
raise Howitzer::BadElementParamsError, 'Using more than 1 proc in arguments is forbidden'
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def define_element(name, args)
|
|
82
|
+
define_method("#{name}_element") do |*block_args|
|
|
83
|
+
capybara_context.find(*convert_arguments(args, block_args))
|
|
84
|
+
end
|
|
85
|
+
private "#{name}_element"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def define_elements(name, args)
|
|
89
|
+
define_method("#{name}_elements") do |*block_args|
|
|
90
|
+
capybara_context.all(*convert_arguments(args, block_args))
|
|
91
|
+
end
|
|
92
|
+
private "#{name}_elements"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def define_has_element(name, args)
|
|
96
|
+
define_method("has_#{name}_element?") do |*block_args|
|
|
97
|
+
capybara_context.has_selector?(*convert_arguments(args, block_args))
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def define_has_no_element(name, args)
|
|
102
|
+
define_method("has_no_#{name}_element?") do |*block_args|
|
|
103
|
+
capybara_context.has_no_selector?(*convert_arguments(args, block_args))
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
module Howitzer
|
|
2
|
+
module Web
|
|
3
|
+
# This module combines iframe dsl methods
|
|
4
|
+
module IframeDsl
|
|
5
|
+
def self.included(base) #:nodoc:
|
|
6
|
+
base.extend(ClassMethods)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Returns capybara context. For example, capybara session, parent element, etc.
|
|
10
|
+
# @abstract should be defined in parent context
|
|
11
|
+
|
|
12
|
+
def capybara_context
|
|
13
|
+
raise NotImplementedError, "Please define 'capybara_context' method for class holder"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def iframe_element_selector(selector)
|
|
19
|
+
selector.is_a?(Integer) ? ["iframe:nth-of-type(#{selector + 1})"] : [:frame, selector]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# This module holds frame dsl class methods
|
|
23
|
+
module ClassMethods
|
|
24
|
+
protected
|
|
25
|
+
|
|
26
|
+
# Creates a group of methods to interact with described HTML frame on page
|
|
27
|
+
# @note This method generates following dynamic methods:
|
|
28
|
+
#
|
|
29
|
+
# <b><em>frame_name</em>_iframe</b> - equals capybara #within_frame(...) method
|
|
30
|
+
#
|
|
31
|
+
# <b>has_<em>frame_name</em>_iframe?</b> - equals capybara #has_selector(...) method
|
|
32
|
+
#
|
|
33
|
+
# <b>has_no_<em>frame_name</em>_iframe?</b> - equals capybara #has_no_selector(...) method
|
|
34
|
+
# @param name [Symbol, String] an unique iframe name
|
|
35
|
+
# @param selector [Integer, String] frame name/id or index. Possible to specify id as #some_id
|
|
36
|
+
# @example Using in a page class
|
|
37
|
+
# class FbPage < Howitzer::Web::Page
|
|
38
|
+
# element :like, :xpath, ".//*[text()='Like']"
|
|
39
|
+
# def like
|
|
40
|
+
# like_element.click
|
|
41
|
+
# end
|
|
42
|
+
#
|
|
43
|
+
# def liked?
|
|
44
|
+
# #some code here
|
|
45
|
+
# end
|
|
46
|
+
# end
|
|
47
|
+
#
|
|
48
|
+
# class HomePage < Howitzer::Web::Page
|
|
49
|
+
# iframe :fb, 1
|
|
50
|
+
# end
|
|
51
|
+
#
|
|
52
|
+
# HomePage.on do
|
|
53
|
+
# fb_iframe do |frame|
|
|
54
|
+
# frame.like
|
|
55
|
+
# expect(frame).to be_liked
|
|
56
|
+
# end
|
|
57
|
+
# end
|
|
58
|
+
# HomePage.on { is_expected.to have_fb_iframe }
|
|
59
|
+
# @!visibility public
|
|
60
|
+
|
|
61
|
+
def iframe(name, selector)
|
|
62
|
+
klass = "#{name}_page".classify.constantize
|
|
63
|
+
define_iframe(klass, name, selector)
|
|
64
|
+
define_has_iframe(name, selector)
|
|
65
|
+
define_has_no_iframe(name, selector)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def define_iframe(klass, name, selector)
|
|
71
|
+
define_method "#{name}_iframe" do |&block|
|
|
72
|
+
iframe_selector = selector.is_a?(Integer) ? selector : selector.split('#').last
|
|
73
|
+
capybara_context.within_frame(iframe_selector) do
|
|
74
|
+
block.call klass.instance
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def define_has_iframe(name, selector)
|
|
80
|
+
define_method("has_#{name}_iframe?") do
|
|
81
|
+
capybara_context.has_selector?(*iframe_element_selector(selector))
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def define_has_no_iframe(name, selector)
|
|
86
|
+
define_method("has_no_#{name}_iframe?") do
|
|
87
|
+
capybara_context.has_no_selector?(*iframe_element_selector(selector))
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
require 'singleton'
|
|
2
|
+
require 'capybara'
|
|
3
|
+
require 'rspec/expectations'
|
|
4
|
+
require 'addressable/template'
|
|
5
|
+
require 'howitzer/web/capybara_methods_proxy'
|
|
6
|
+
require 'howitzer/web/page_validator'
|
|
7
|
+
require 'howitzer/web/element_dsl'
|
|
8
|
+
require 'howitzer/web/iframe_dsl'
|
|
9
|
+
require 'howitzer/web/page_dsl'
|
|
10
|
+
require 'howitzer/web/section_dsl'
|
|
11
|
+
require 'howitzer/exceptions'
|
|
12
|
+
|
|
13
|
+
module Howitzer
|
|
14
|
+
module Web
|
|
15
|
+
# This class represents a single web page. This is a parent class for all web pages
|
|
16
|
+
class Page
|
|
17
|
+
UnknownPage = Class.new
|
|
18
|
+
include Singleton
|
|
19
|
+
include CapybaraMethodsProxy
|
|
20
|
+
include ElementDsl
|
|
21
|
+
include IframeDsl
|
|
22
|
+
include PageDsl
|
|
23
|
+
include SectionDsl
|
|
24
|
+
include PageValidator
|
|
25
|
+
include ::RSpec::Matchers
|
|
26
|
+
|
|
27
|
+
# This Ruby callback makes all inherited classes as singleton classes.
|
|
28
|
+
# In additional it addes current page to page validator pages in case
|
|
29
|
+
# if it has any defined validations.
|
|
30
|
+
|
|
31
|
+
def self.inherited(subclass)
|
|
32
|
+
subclass.class_eval { include Singleton }
|
|
33
|
+
PageValidator.pages << subclass if subclass.validations.present?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Opens a web page in browser
|
|
37
|
+
# @note It tries to open the page twice and then raises the error if a validation is failed
|
|
38
|
+
# @param validate [Boolean] if fase will skip current page validation (is opened)
|
|
39
|
+
# @param params [Array] placeholder names and their values
|
|
40
|
+
# @return [Page]
|
|
41
|
+
|
|
42
|
+
def self.open(validate: true, **params)
|
|
43
|
+
url = expanded_url(params)
|
|
44
|
+
Howitzer::Log.info "Open #{name} page by '#{url}' url"
|
|
45
|
+
retryable(tries: 2, logger: Howitzer::Log, trace: true, on: Exception) do |retries|
|
|
46
|
+
Howitzer::Log.info 'Retry...' unless retries.zero?
|
|
47
|
+
Capybara.current_session.visit(url)
|
|
48
|
+
end
|
|
49
|
+
given if validate
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Returns a singleton instance of the web page
|
|
53
|
+
# @return [Page]
|
|
54
|
+
|
|
55
|
+
def self.given
|
|
56
|
+
displayed?
|
|
57
|
+
instance
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Tries to identify current page name or raise the error if ambiguous page matching
|
|
61
|
+
# @return [String] a page name
|
|
62
|
+
# @raise [UnknownPage] when no any matched pages
|
|
63
|
+
# @raise [AmbiguousPageMatchingError] when matched more than 1 page
|
|
64
|
+
|
|
65
|
+
def self.current_page
|
|
66
|
+
page_list = matched_pages
|
|
67
|
+
return UnknownPage if page_list.count.zero?
|
|
68
|
+
return page_list.first if page_list.count == 1
|
|
69
|
+
raise Howitzer::AmbiguousPageMatchingError, ambiguous_page_msg(page_list)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Waits until a web page is opened
|
|
73
|
+
# @param time_out [Integer] time in seconds a required web page to be loaded
|
|
74
|
+
# @return [Boolean]
|
|
75
|
+
# @raise [IncorrectPageError] when timeout expired and the page is not displayed
|
|
76
|
+
|
|
77
|
+
def self.displayed?(timeout = Howitzer.page_load_idle_timeout)
|
|
78
|
+
end_time = ::Time.now + timeout
|
|
79
|
+
until ::Time.now > end_time
|
|
80
|
+
return true if opened?
|
|
81
|
+
sleep(0.5)
|
|
82
|
+
end
|
|
83
|
+
raise Howitzer::IncorrectPageError, incorrect_page_msg
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @return [String] current page url from browser
|
|
87
|
+
|
|
88
|
+
def self.current_url
|
|
89
|
+
Capybara.current_session.current_url
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Returns an expanded page url for the page opening
|
|
93
|
+
# @param params [Array] placeholders and their values
|
|
94
|
+
# @return [String]
|
|
95
|
+
# @raise [NoPathForPageError] if an url is not specified for the page
|
|
96
|
+
|
|
97
|
+
def self.expanded_url(params = {})
|
|
98
|
+
return "#{app_host}#{Addressable::Template.new(path_template).expand(params)}" unless path_template.nil?
|
|
99
|
+
raise Howitzer::NoPathForPageError, "Please specify path for '#{self}' page. Example: path '/home'"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
class << self
|
|
103
|
+
protected
|
|
104
|
+
|
|
105
|
+
# DSL to specify an relative path pattern for the page opening
|
|
106
|
+
# @param value [String] a path pattern, for details please see Addressable gem
|
|
107
|
+
# @see .site
|
|
108
|
+
# @example
|
|
109
|
+
# class ArticlePage < Howitzer::Web::Page
|
|
110
|
+
# url '/articles/:id'
|
|
111
|
+
# end
|
|
112
|
+
# ArticlePage.open(id: 10)
|
|
113
|
+
# @!visibility public
|
|
114
|
+
|
|
115
|
+
def path(value)
|
|
116
|
+
@path_template = value.to_s
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# DSL to specify a site for the page opening
|
|
120
|
+
# @note By default it specifies Howitzer.app_uri.site as a site
|
|
121
|
+
# @param value [String] a site as combination of protocol, host and port
|
|
122
|
+
# @example
|
|
123
|
+
# class AuthPage < Howitzer::Web::Page
|
|
124
|
+
# site 'https:/example.com'
|
|
125
|
+
# end
|
|
126
|
+
#
|
|
127
|
+
# class LoginPage < AuthPage
|
|
128
|
+
# path '/login'
|
|
129
|
+
# end
|
|
130
|
+
# @!visibility public
|
|
131
|
+
|
|
132
|
+
def site(value)
|
|
133
|
+
define_singleton_method(:app_host) { value }
|
|
134
|
+
private_class_method :app_host
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
attr_reader :path_template
|
|
140
|
+
|
|
141
|
+
def incorrect_page_msg
|
|
142
|
+
"Current page: #{current_page}, expected: #{self}.\n" \
|
|
143
|
+
"\tCurrent url: #{current_url}\n\tCurrent title: #{instance.title}"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def ambiguous_page_msg(page_list)
|
|
147
|
+
"Current page matches more that one page class (#{page_list.join(', ')}).\n" \
|
|
148
|
+
"\tCurrent url: #{current_url}\n\tCurrent title: #{instance.title}"
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
site Howitzer.app_uri.site
|
|
153
|
+
|
|
154
|
+
def initialize
|
|
155
|
+
check_validations_are_defined!
|
|
156
|
+
current_window.maximize if Howitzer.maximized_window
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Reloads current page in a browser
|
|
160
|
+
|
|
161
|
+
def reload
|
|
162
|
+
Howitzer::Log.info "Reload '#{current_url}'"
|
|
163
|
+
visit current_url
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Returns capybara context as current session
|
|
167
|
+
|
|
168
|
+
def capybara_context
|
|
169
|
+
Capybara.current_session
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|