howitzer 1.1.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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