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.
Files changed (166) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +32 -0
  4. data/.travis.yml +1 -4
  5. data/.yardopts +1 -2
  6. data/CHANGELOG.md +28 -1
  7. data/Gemfile +6 -0
  8. data/LICENSE +1 -1
  9. data/README.md +55 -85
  10. data/Rakefile +7 -7
  11. data/bin/howitzer +56 -31
  12. data/features/cli_new.feature +162 -79
  13. data/features/cli_update.feature +114 -21
  14. data/features/step_definitions/common_steps.rb +29 -9
  15. data/features/support/env.rb +1 -1
  16. data/features/support/transformers.rb +2 -2
  17. data/generators/base_generator.rb +121 -49
  18. data/generators/config/config_generator.rb +8 -5
  19. data/generators/config/templates/boot.rb +14 -0
  20. data/generators/config/templates/capybara.rb +156 -0
  21. data/generators/config/templates/custom.yml +2 -3
  22. data/generators/config/templates/default.yml +50 -82
  23. data/generators/cucumber/cucumber_generator.rb +9 -9
  24. data/generators/cucumber/templates/common_steps.rb +4 -4
  25. data/generators/cucumber/templates/cucumber.rake +63 -54
  26. data/generators/cucumber/templates/env.rb +24 -23
  27. data/generators/cucumber/templates/transformers.rb +23 -15
  28. data/generators/emails/emails_generator.rb +4 -2
  29. data/generators/emails/templates/example_email.rb +4 -4
  30. data/generators/prerequisites/prerequisites_generator.rb +24 -0
  31. data/generators/prerequisites/templates/base.rb +22 -0
  32. data/generators/prerequisites/templates/factory_girl.rb +30 -0
  33. data/generators/prerequisites/templates/user.rb +7 -0
  34. data/generators/prerequisites/templates/users.rb +12 -0
  35. data/generators/root/root_generator.rb +11 -7
  36. data/generators/root/templates/.rubocop.yml +32 -0
  37. data/generators/root/templates/Gemfile.erb +27 -0
  38. data/generators/root/templates/Rakefile +6 -8
  39. data/generators/rspec/rspec_generator.rb +7 -7
  40. data/generators/rspec/templates/example_spec.rb +1 -1
  41. data/generators/rspec/templates/rspec.rake +48 -29
  42. data/generators/rspec/templates/spec_helper.rb +33 -32
  43. data/generators/tasks/tasks_generator.rb +4 -2
  44. data/generators/tasks/templates/common.rake +1 -16
  45. data/generators/turnip/templates/.rspec +1 -0
  46. data/generators/turnip/templates/common_steps.rb +25 -0
  47. data/generators/turnip/templates/example.feature +8 -0
  48. data/generators/turnip/templates/spec_helper.rb +56 -0
  49. data/generators/turnip/templates/turnip.rake +61 -0
  50. data/generators/turnip/templates/turnip_helper.rb +6 -0
  51. data/generators/turnip/turnip_generator.rb +26 -0
  52. data/generators/web/templates/example_page.rb +15 -0
  53. data/generators/web/templates/menu_section.rb +13 -0
  54. data/generators/web/web_generator.rb +22 -0
  55. data/howitzer.gemspec +14 -21
  56. data/lib/howitzer.rb +47 -7
  57. data/lib/howitzer/cache.rb +70 -0
  58. data/lib/howitzer/capybara_helpers.rb +194 -0
  59. data/lib/howitzer/email.rb +96 -104
  60. data/lib/howitzer/exceptions.rb +10 -6
  61. data/lib/howitzer/log.rb +120 -0
  62. data/lib/howitzer/mail_adapters.rb +7 -0
  63. data/lib/howitzer/mail_adapters/abstract.rb +84 -0
  64. data/lib/howitzer/mail_adapters/mailgun.rb +115 -0
  65. data/lib/howitzer/mailgun_api.rb +9 -0
  66. data/lib/howitzer/mailgun_api/client.rb +79 -0
  67. data/lib/howitzer/mailgun_api/connector.rb +37 -0
  68. data/lib/howitzer/mailgun_api/response.rb +28 -0
  69. data/lib/howitzer/tasks/framework.rake +2 -2
  70. data/lib/howitzer/utils.rb +7 -1
  71. data/lib/howitzer/utils/string_extensions.rb +66 -0
  72. data/lib/howitzer/version.rb +2 -1
  73. data/lib/howitzer/web.rb +11 -0
  74. data/lib/howitzer/web/base_section.rb +27 -0
  75. data/lib/howitzer/web/blank_page.rb +12 -0
  76. data/lib/howitzer/web/capybara_methods_proxy.rb +29 -0
  77. data/lib/howitzer/web/element_dsl.rb +109 -0
  78. data/lib/howitzer/web/iframe_dsl.rb +93 -0
  79. data/lib/howitzer/web/page.rb +173 -0
  80. data/lib/howitzer/web/page_dsl.rb +64 -0
  81. data/lib/howitzer/web/page_validator.rb +118 -0
  82. data/lib/howitzer/web/section.rb +27 -0
  83. data/lib/howitzer/web/section_dsl.rb +154 -0
  84. data/spec/config/custom.yml +10 -1
  85. data/spec/spec_helper.rb +37 -19
  86. data/spec/support/generator_helper.rb +12 -11
  87. data/spec/support/logger_helper.rb +10 -9
  88. data/spec/support/mailgun_unit_client.rb +52 -44
  89. data/spec/support/shared_examples/capybara_context_holder.rb +33 -0
  90. data/spec/support/shared_examples/capybara_methods_proxy.rb +94 -0
  91. data/spec/support/shared_examples/dynamic_section_methods.rb +35 -0
  92. data/spec/support/shared_examples/element_dsl.rb +119 -0
  93. data/spec/unit/generators/base_generator_spec.rb +64 -33
  94. data/spec/unit/generators/config_generator_spec.rb +11 -7
  95. data/spec/unit/generators/cucumber_generator_spec.rb +26 -17
  96. data/spec/unit/generators/emails_generator_spec.rb +10 -6
  97. data/spec/unit/generators/prerequisites_generator_spec.rb +53 -0
  98. data/spec/unit/generators/root_generator_spec.rb +50 -13
  99. data/spec/unit/generators/rspec_generator_spec.rb +9 -9
  100. data/spec/unit/generators/tasks_generator_spec.rb +6 -6
  101. data/spec/unit/generators/turnip_generator_spec.rb +52 -0
  102. data/spec/unit/generators/web_generator_spec.rb +52 -0
  103. data/spec/unit/lib/cache_spec.rb +85 -0
  104. data/spec/unit/lib/capybara_helpers_spec.rb +696 -0
  105. data/spec/unit/lib/email_spec.rb +104 -91
  106. data/spec/unit/lib/howitzer.rb +31 -0
  107. data/spec/unit/lib/init_spec.rb +0 -1
  108. data/spec/unit/lib/log_spec.rb +122 -0
  109. data/spec/unit/lib/mail_adapters/abstract_spec.rb +62 -0
  110. data/spec/unit/lib/mail_adapters/mailgun_spec.rb +163 -0
  111. data/spec/unit/lib/mailgun_api/client_spec.rb +58 -0
  112. data/spec/unit/lib/mailgun_api/connector_spec.rb +54 -0
  113. data/spec/unit/lib/mailgun_api/response_spec.rb +28 -0
  114. data/spec/unit/lib/utils/string_extensions_spec.rb +77 -0
  115. data/spec/unit/lib/web/base_section_spec.rb +41 -0
  116. data/spec/unit/lib/web/element_dsl_spec.rb +17 -0
  117. data/spec/unit/lib/web/iframe_dsl_spec.rb +99 -0
  118. data/spec/unit/lib/web/page_dsl_spec.rb +52 -0
  119. data/spec/unit/lib/web/page_spec.rb +304 -0
  120. data/spec/unit/lib/web/page_validator_spec.rb +218 -0
  121. data/spec/unit/lib/web/section_dsl_spec.rb +165 -0
  122. data/spec/unit/lib/web/section_spec.rb +61 -0
  123. data/spec/unit/version_spec.rb +1 -1
  124. metadata +116 -203
  125. data/GETTING_STARTED.md +0 -774
  126. data/generators/cucumber/templates/cucumber.yml +0 -10
  127. data/generators/pages/pages_generator.rb +0 -21
  128. data/generators/pages/templates/example_menu.rb +0 -15
  129. data/generators/pages/templates/example_page.rb +0 -15
  130. data/generators/root/templates/Gemfile +0 -7
  131. data/generators/root/templates/boot.rb +0 -10
  132. data/lib/howitzer/blank_page.rb +0 -6
  133. data/lib/howitzer/capybara/dsl_ex.rb +0 -15
  134. data/lib/howitzer/capybara/settings.rb +0 -343
  135. data/lib/howitzer/helpers.rb +0 -230
  136. data/lib/howitzer/init.rb +0 -1
  137. data/lib/howitzer/mailgun/client.rb +0 -65
  138. data/lib/howitzer/mailgun/connector.rb +0 -34
  139. data/lib/howitzer/mailgun/response.rb +0 -28
  140. data/lib/howitzer/patches/rawler_patched.rb +0 -86
  141. data/lib/howitzer/settings.rb +0 -27
  142. data/lib/howitzer/utils/data_generator/data_storage.rb +0 -88
  143. data/lib/howitzer/utils/data_generator/gen.rb +0 -135
  144. data/lib/howitzer/utils/locator_store.rb +0 -217
  145. data/lib/howitzer/utils/log.rb +0 -139
  146. data/lib/howitzer/utils/page_validator.rb +0 -133
  147. data/lib/howitzer/vendor/firebug-1.12.1-fx.xpi +0 -0
  148. data/lib/howitzer/vendor/firepath-0.9.7-fx.xpi +0 -0
  149. data/lib/howitzer/web_page.rb +0 -253
  150. data/spec/active_resource.rb +0 -0
  151. data/spec/config/default.yml +0 -26
  152. data/spec/support/boot_helper.rb +0 -15
  153. data/spec/unit/generators/pages_generator_spec.rb +0 -33
  154. data/spec/unit/lib/capybara/dsl_ex_spec.rb +0 -60
  155. data/spec/unit/lib/capybara/settings_spec.rb +0 -441
  156. data/spec/unit/lib/helpers_spec.rb +0 -1129
  157. data/spec/unit/lib/mailgun/client_spec.rb +0 -36
  158. data/spec/unit/lib/mailgun/connector_spec.rb +0 -70
  159. data/spec/unit/lib/mailgun/response_spec.rb +0 -28
  160. data/spec/unit/lib/settings_spec.rb +0 -17
  161. data/spec/unit/lib/utils/data_generator/data_storage_spec.rb +0 -80
  162. data/spec/unit/lib/utils/data_generator/gen_spec.rb +0 -90
  163. data/spec/unit/lib/utils/locator_store_spec.rb +0 -157
  164. data/spec/unit/lib/utils/log_spec.rb +0 -107
  165. data/spec/unit/lib/utils/page_validator_spec.rb +0 -265
  166. 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
- #This is for future usage of built-in tasks
3
- end
2
+ # This is for future usage of built-in tasks
3
+ end
@@ -2,4 +2,10 @@ require 'repeater'
2
2
  require 'active_support'
3
3
  require 'active_support/all'
4
4
 
5
- Dir[File.join(File.dirname(__FILE__), "./utils/**/*.rb")].each {|f| require f}
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
@@ -1,3 +1,4 @@
1
+ # This module holds howitzer version
1
2
  module Howitzer
2
- VERSION = "1.1.1"
3
+ VERSION = '2.0.0'.freeze
3
4
  end
@@ -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,12 @@
1
+ require 'howitzer/web/page'
2
+
3
+ module Howitzer
4
+ module Web
5
+ # This class represents standard blank page in browser
6
+ class BlankPage < Page
7
+ site ''
8
+ path 'about:blank'
9
+ validate :url, /^about:blank$/
10
+ end
11
+ end
12
+ 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