howitzer 2.0.3 → 2.3.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 (139) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +69 -11
  3. data/LICENSE +1 -1
  4. data/README.md +21 -17
  5. data/bin/howitzer +7 -6
  6. data/generators/base_generator.rb +31 -17
  7. data/generators/config/config_generator.rb +11 -3
  8. data/generators/config/templates/boot.rb +3 -3
  9. data/generators/config/templates/capybara.rb +6 -131
  10. data/generators/config/templates/default.yml +34 -13
  11. data/generators/config/templates/drivers/appium.rb +25 -0
  12. data/generators/config/templates/drivers/browserstack.rb +23 -0
  13. data/generators/config/templates/drivers/crossbrowsertesting.rb +29 -0
  14. data/generators/config/templates/drivers/headless_chrome.rb +15 -0
  15. data/generators/config/templates/drivers/headless_firefox.rb +23 -0
  16. data/generators/config/templates/drivers/sauce.rb +25 -0
  17. data/generators/config/templates/drivers/selenium.rb +24 -0
  18. data/generators/config/templates/drivers/selenium_grid.rb +31 -0
  19. data/generators/config/templates/drivers/testingbot.rb +24 -0
  20. data/generators/cucumber/cucumber_generator.rb +2 -2
  21. data/generators/cucumber/templates/common_steps.rb +3 -3
  22. data/generators/cucumber/templates/cucumber.rake +5 -13
  23. data/generators/cucumber/templates/cuke_sniffer.rake +2 -2
  24. data/generators/cucumber/templates/env.rb +9 -1
  25. data/generators/cucumber/templates/hooks.rb +8 -2
  26. data/generators/cucumber/templates/transformers.rb +11 -25
  27. data/generators/emails/emails_generator.rb +2 -2
  28. data/generators/emails/templates/example_email.rb +1 -1
  29. data/generators/prerequisites/prerequisites_generator.rb +3 -3
  30. data/generators/prerequisites/templates/base.rb +1 -1
  31. data/generators/prerequisites/templates/{factory_girl.rb → factory_bot.rb} +7 -6
  32. data/generators/prerequisites/templates/users.rb +1 -1
  33. data/generators/root/root_generator.rb +3 -3
  34. data/generators/root/templates/Gemfile.erb +16 -22
  35. data/generators/rspec/rspec_generator.rb +2 -2
  36. data/generators/rspec/templates/rspec.rake +8 -8
  37. data/generators/rspec/templates/spec_helper.rb +6 -5
  38. data/generators/tasks/tasks_generator.rb +2 -2
  39. data/generators/turnip/templates/spec_helper.rb +6 -5
  40. data/generators/turnip/turnip_generator.rb +2 -2
  41. data/generators/web/templates/example_page.rb +1 -1
  42. data/generators/web/web_generator.rb +2 -2
  43. data/lib/howitzer/cache.rb +20 -19
  44. data/lib/howitzer/capybara_helpers.rb +58 -13
  45. data/lib/howitzer/email.rb +3 -2
  46. data/lib/howitzer/exceptions.rb +21 -20
  47. data/lib/howitzer/gmail_api/client.rb +31 -0
  48. data/lib/howitzer/gmail_api.rb +7 -0
  49. data/lib/howitzer/log.rb +6 -6
  50. data/lib/howitzer/mail_adapters/gmail.rb +96 -0
  51. data/lib/howitzer/mail_adapters/mailgun.rb +4 -2
  52. data/lib/howitzer/mail_adapters/mailtrap.rb +108 -0
  53. data/lib/howitzer/mailgun_api/client.rb +3 -2
  54. data/lib/howitzer/mailgun_api/connector.rb +1 -0
  55. data/lib/howitzer/mailgun_api/response.rb +1 -2
  56. data/lib/howitzer/mailtrap_api/client.rb +52 -0
  57. data/lib/howitzer/mailtrap_api.rb +7 -0
  58. data/lib/howitzer/meta/actions.rb +35 -0
  59. data/lib/howitzer/meta/element.rb +40 -0
  60. data/lib/howitzer/meta/entry.rb +62 -0
  61. data/lib/howitzer/meta/iframe.rb +41 -0
  62. data/lib/howitzer/meta/section.rb +30 -0
  63. data/lib/howitzer/meta.rb +11 -0
  64. data/lib/howitzer/utils/string_extensions.rb +6 -2
  65. data/lib/howitzer/version.rb +1 -1
  66. data/lib/howitzer/web/base_section.rb +1 -1
  67. data/lib/howitzer/web/capybara_context_holder.rb +1 -0
  68. data/lib/howitzer/web/capybara_methods_proxy.rb +15 -6
  69. data/lib/howitzer/web/element_dsl.rb +104 -44
  70. data/lib/howitzer/web/iframe_dsl.rb +23 -3
  71. data/lib/howitzer/web/page.rb +16 -4
  72. data/lib/howitzer/web/page_dsl.rb +19 -7
  73. data/lib/howitzer/web/page_validator.rb +27 -26
  74. data/lib/howitzer/web/section.rb +13 -2
  75. data/lib/howitzer/web/section_dsl.rb +65 -30
  76. data/lib/howitzer.rb +66 -10
  77. metadata +67 -133
  78. data/.coveralls.yml +0 -1
  79. data/.gitignore +0 -14
  80. data/.rspec +0 -3
  81. data/.rubocop.yml +0 -44
  82. data/.ruby-gemset +0 -1
  83. data/.travis.yml +0 -7
  84. data/Gemfile +0 -13
  85. data/MAINTENANCE.md +0 -32
  86. data/Rakefile +0 -22
  87. data/features/cli_help.feature +0 -31
  88. data/features/cli_new.feature +0 -349
  89. data/features/cli_unknown.feature +0 -17
  90. data/features/cli_update.feature +0 -178
  91. data/features/cli_version.feature +0 -14
  92. data/features/step_definitions/common_steps.rb +0 -29
  93. data/features/support/env.rb +0 -1
  94. data/features/support/transformers.rb +0 -3
  95. data/generators/root/templates/.gitignore +0 -21
  96. data/generators/root/templates/.rubocop.yml +0 -35
  97. data/generators/turnip/templates/.rspec +0 -1
  98. data/howitzer.gemspec +0 -37
  99. data/lib/howitzer/mail_adapters/debugmail.rb +0 -0
  100. data/spec/config/custom.yml +0 -9
  101. data/spec/spec_helper.rb +0 -72
  102. data/spec/support/generator_helper.rb +0 -21
  103. data/spec/support/logger_helper.rb +0 -13
  104. data/spec/support/mailgun_unit_client.rb +0 -68
  105. data/spec/support/shared_examples/capybara_context_holder.rb +0 -33
  106. data/spec/support/shared_examples/capybara_methods_proxy.rb +0 -94
  107. data/spec/support/shared_examples/dynamic_section_methods.rb +0 -35
  108. data/spec/support/shared_examples/element_dsl.rb +0 -242
  109. data/spec/unit/generators/base_generator_spec.rb +0 -272
  110. data/spec/unit/generators/config_generator_spec.rb +0 -38
  111. data/spec/unit/generators/cucumber_generator_spec.rb +0 -62
  112. data/spec/unit/generators/emails_generator_spec.rb +0 -35
  113. data/spec/unit/generators/prerequisites_generator_spec.rb +0 -53
  114. data/spec/unit/generators/root_generator_spec.rb +0 -72
  115. data/spec/unit/generators/rspec_generator_spec.rb +0 -36
  116. data/spec/unit/generators/tasks_generator_spec.rb +0 -31
  117. data/spec/unit/generators/turnip_generator_spec.rb +0 -52
  118. data/spec/unit/generators/web_generator_spec.rb +0 -52
  119. data/spec/unit/lib/cache_spec.rb +0 -85
  120. data/spec/unit/lib/capybara_helpers_spec.rb +0 -696
  121. data/spec/unit/lib/email_spec.rb +0 -186
  122. data/spec/unit/lib/howitzer_spec.rb +0 -40
  123. data/spec/unit/lib/init_spec.rb +0 -2
  124. data/spec/unit/lib/log_spec.rb +0 -122
  125. data/spec/unit/lib/mail_adapters/abstract_spec.rb +0 -62
  126. data/spec/unit/lib/mail_adapters/mailgun_spec.rb +0 -163
  127. data/spec/unit/lib/mailgun_api/client_spec.rb +0 -58
  128. data/spec/unit/lib/mailgun_api/connector_spec.rb +0 -54
  129. data/spec/unit/lib/mailgun_api/response_spec.rb +0 -28
  130. data/spec/unit/lib/utils/string_extensions_spec.rb +0 -77
  131. data/spec/unit/lib/web/base_section_spec.rb +0 -43
  132. data/spec/unit/lib/web/element_dsl_spec.rb +0 -22
  133. data/spec/unit/lib/web/iframe_dsl_spec.rb +0 -144
  134. data/spec/unit/lib/web/page_dsl_spec.rb +0 -74
  135. data/spec/unit/lib/web/page_spec.rb +0 -349
  136. data/spec/unit/lib/web/page_validator_spec.rb +0 -276
  137. data/spec/unit/lib/web/section_dsl_spec.rb +0 -165
  138. data/spec/unit/lib/web/section_spec.rb +0 -63
  139. data/spec/unit/version_spec.rb +0 -8
@@ -3,39 +3,68 @@ module Howitzer
3
3
  module Web
4
4
  # This module combines element dsl methods
5
5
  module ElementDsl
6
+ # This module holds element helper methods
7
+ module Helpers
8
+ private
9
+
10
+ def lambda_args(*args, **keyword_args)
11
+ {
12
+ lambda_args: {
13
+ args: args,
14
+ keyword_args: keyword_args
15
+ }
16
+ }
17
+ end
18
+ end
19
+
6
20
  include CapybaraContextHolder
21
+ include Helpers
7
22
 
8
- def self.included(base) #:nodoc:
23
+ def self.included(base) # :nodoc:
9
24
  base.extend(ClassMethods)
10
25
  end
11
26
 
12
- private
27
+ def convert_arguments(args, options, block_args, block_options)
28
+ conv_args = args.map { |el| el.is_a?(Proc) ? proc_to_selector(el, block_args, block_options) : el }
29
+ args_options = pop_options_from_array(conv_args)
30
+ block_args_options = pop_options_from_array(block_args)
31
+ conv_options = [args_options, options, block_args_options, block_options].map do |el|
32
+ el.transform_keys(&:to_sym)
33
+ end.reduce(&:merge).except(:lambda_args)
34
+ [conv_args, conv_options]
35
+ end
13
36
 
14
- def convert_arguments(args, params)
15
- args, params, options = merge_element_options(args, params)
16
- args = args.map do |el|
17
- next(el) unless el.is_a?(Proc)
18
- el.call(*params.shift(el.arity))
37
+ def proc_to_selector(proc, block_args, block_options)
38
+ lambda_args = extract_lambda_args(block_args, block_options)
39
+ if lambda_args
40
+ if lambda_args[:keyword_args].present?
41
+ proc.call(*lambda_args[:args], **lambda_args[:keyword_args])
42
+ else
43
+ proc.call(*lambda_args[:args])
44
+ end
45
+ else
46
+ puts "WARNING! Passing lambda arguments with element options is deprecated.\n" \
47
+ "Please use 'lambda_args' method, for example: foo_element(lambda_args(title: 'Example'), wait: 10)"
48
+ proc.call(*block_args.shift(proc.arity))
19
49
  end
20
- args << options unless options.blank?
21
- args
22
50
  end
23
51
 
24
- def merge_element_options(args, params)
25
- new_args, args_hash = extract_element_options(args)
26
- new_params, params_hash = extract_element_options(params)
27
- [new_args, new_params, args_hash.merge(params_hash)]
52
+ def extract_lambda_args(block_args, block_options)
53
+ (block_args.first.is_a?(Hash) && block_args.first[:lambda_args]) || block_options[:lambda_args]
28
54
  end
29
55
 
30
- def extract_element_options(args)
31
- new_args = args.deep_dup
32
- args_hash = {}
33
- args_hash = new_args.pop if new_args.last.is_a?(Hash)
34
- [new_args, args_hash]
56
+ def pop_options_from_array(value)
57
+ if value.last.is_a?(Hash) && !value.last.key?(:lambda_args)
58
+ value.pop
59
+ else
60
+ {}
61
+ end
35
62
  end
36
63
 
37
- # This module holds element dsl methods methods
64
+ # This module holds element dsl methods
38
65
  module ClassMethods
66
+ include Helpers
67
+
39
68
  protected
40
69
 
41
70
  # Creates a group of methods to interact with described HTML element(s) on page
@@ -56,6 +85,7 @@ module Howitzer
56
85
  # <b>has_no_<em>element_name</em>_element?</b> - equals capybara #has_no_selector(...) method
57
86
  # @param name [Symbol, String] an unique element name
58
87
  # @param args [Array] original Capybara arguments. For details, see `Capybara::Node::Finders#all`.
88
+ # @param options [Hash] original Capybara options. For details, see `Capybara::Node::Finders#all`.
59
89
  # @example Using in a page class
60
90
  # class HomePage < Howitzer::Web::Page
61
91
  # element :top_panel, '.top'
@@ -92,14 +122,14 @@ module Howitzer
92
122
  # @raise [BadElementParamsError] if wrong element arguments
93
123
  # @!visibility public
94
124
 
95
- def element(name, *args)
125
+ def element(name, *args, **options)
96
126
  validate_arguments!(args)
97
- define_element(name, args)
98
- define_elements(name, args)
99
- define_wait_for_element(name, args)
100
- define_within_element(name, args)
101
- define_has_element(name, args)
102
- define_has_no_element(name, args)
127
+ define_element(name, args, options)
128
+ define_elements(name, args, options)
129
+ define_wait_for_element(name, args, options)
130
+ define_within_element(name, args, options)
131
+ define_has_element(name, args, options)
132
+ define_has_no_element(name, args, options)
103
133
  end
104
134
 
105
135
  private
@@ -110,31 +140,51 @@ module Howitzer
110
140
  raise Howitzer::BadElementParamsError, 'Using more than 1 proc in arguments is forbidden'
111
141
  end
112
142
 
113
- def define_element(name, args)
114
- define_method("#{name}_element") do |*block_args|
115
- capybara_context.find(*convert_arguments(args, block_args))
143
+ def define_element(name, args, options)
144
+ define_method("#{name}_element") do |*block_args, **block_options|
145
+ conv_args, conv_options = convert_arguments(args, options, block_args, block_options)
146
+ if conv_options.present?
147
+ capybara_context.find(*conv_args, **conv_options)
148
+ else
149
+ capybara_context.find(*conv_args)
150
+ end
116
151
  end
117
152
  private "#{name}_element"
118
153
  end
119
154
 
120
- def define_elements(name, args)
121
- define_method("#{name}_elements") do |*block_args|
122
- capybara_context.all(*convert_arguments(args, block_args))
155
+ def define_elements(name, args, options)
156
+ define_method("#{name}_elements") do |*block_args, **block_options|
157
+ conv_args, conv_options = convert_arguments(args, options, block_args, block_options)
158
+ if conv_options.present?
159
+ capybara_context.all(*conv_args, **conv_options)
160
+ else
161
+ capybara_context.all(*conv_args)
162
+ end
123
163
  end
124
164
  private "#{name}_elements"
125
165
  end
126
166
 
127
- def define_wait_for_element(name, args)
128
- define_method("wait_for_#{name}_element") do |*block_args|
129
- capybara_context.find(*convert_arguments(args, block_args))
167
+ def define_wait_for_element(name, args, options)
168
+ define_method("wait_for_#{name}_element") do |*block_args, **block_options|
169
+ conv_args, conv_options = convert_arguments(args, options, block_args, block_options)
170
+ if conv_options.present?
171
+ capybara_context.find(*conv_args, **conv_options)
172
+ else
173
+ capybara_context.find(*conv_args)
174
+ end
130
175
  return nil
131
176
  end
132
177
  private "wait_for_#{name}_element"
133
178
  end
134
179
 
135
- def define_within_element(name, args)
136
- define_method("within_#{name}_element") do |*block_args, &block|
137
- new_scope = capybara_context.find(*convert_arguments(args, block_args))
180
+ def define_within_element(name, args, options)
181
+ define_method("within_#{name}_element") do |*block_args, **block_options, &block|
182
+ conv_args, conv_options = convert_arguments(args, options, block_args, block_options)
183
+ new_scope = if conv_options.present?
184
+ capybara_context.find(*conv_args, **conv_options)
185
+ else
186
+ capybara_context.find(*conv_args)
187
+ end
138
188
  begin
139
189
  capybara_scopes.push(new_scope)
140
190
  block.call
@@ -144,15 +194,25 @@ module Howitzer
144
194
  end
145
195
  end
146
196
 
147
- def define_has_element(name, args)
148
- define_method("has_#{name}_element?") do |*block_args|
149
- capybara_context.has_selector?(*convert_arguments(args, block_args))
197
+ def define_has_element(name, args, options)
198
+ define_method("has_#{name}_element?") do |*block_args, **block_options|
199
+ conv_args, conv_options = convert_arguments(args, options, block_args, block_options)
200
+ if conv_options.present?
201
+ capybara_context.has_selector?(*conv_args, **conv_options)
202
+ else
203
+ capybara_context.has_selector?(*conv_args)
204
+ end
150
205
  end
151
206
  end
152
207
 
153
- def define_has_no_element(name, args)
154
- define_method("has_no_#{name}_element?") do |*block_args|
155
- capybara_context.has_no_selector?(*convert_arguments(args, block_args))
208
+ def define_has_no_element(name, args, options)
209
+ define_method("has_no_#{name}_element?") do |*block_args, **block_options|
210
+ conv_args, conv_options = convert_arguments(args, options, block_args, block_options)
211
+ if conv_options.present?
212
+ capybara_context.has_no_selector?(*conv_args, **conv_options)
213
+ else
214
+ capybara_context.has_no_selector?(*conv_args)
215
+ end
156
216
  end
157
217
  end
158
218
  end
@@ -5,7 +5,7 @@ module Howitzer
5
5
  module IframeDsl
6
6
  include CapybaraContextHolder
7
7
 
8
- def self.included(base) #:nodoc:
8
+ def self.included(base) # :nodoc:
9
9
  base.extend(ClassMethods)
10
10
  end
11
11
 
@@ -26,7 +26,7 @@ module Howitzer
26
26
 
27
27
  def convert_iframe_arguments(args, params)
28
28
  new_args = args.deep_dup
29
- hash = new_args.pop.merge(params) if new_args.last.is_a?(Hash)
29
+ hash = new_args.pop.transform_keys(&:to_sym).merge(params.transform_keys(&:to_sym)) if new_args.last.is_a?(Hash)
30
30
  new_args << hash if hash.present?
31
31
  new_args
32
32
  end
@@ -45,6 +45,7 @@ module Howitzer
45
45
  # <b>has_no_<em>frame_name</em>_iframe?</b> - equals capybara #has_no_selector(...) method
46
46
  # @param name [Symbol, String] an unique iframe name
47
47
  # @param args [Array] original Capybara arguments. For details, see `Capybara::Session#within_frame`.
48
+ # @raise [NameError] if page class can not be found
48
49
  # @example Using in a page class
49
50
  # class FbPage < Howitzer::Web::Page
50
51
  # element :like, :xpath, ".//*[text()='Like']"
@@ -57,8 +58,19 @@ module Howitzer
57
58
  # end
58
59
  # end
59
60
  #
61
+ # module Utils
62
+ # class GroupFbPage < Howitzer::Web::Page
63
+ # end
64
+ # end
65
+ #
60
66
  # class HomePage < Howitzer::Web::Page
61
67
  # iframe :fb, 1
68
+ #
69
+ # # frame with explicit class declaration
70
+ # # iframe :fb, FbPage, 1
71
+ #
72
+ # # frame with namespace
73
+ # iframe :utils_group_fb
62
74
  # end
63
75
  #
64
76
  # HomePage.on do
@@ -72,7 +84,10 @@ module Howitzer
72
84
 
73
85
  def iframe(name, *args)
74
86
  raise ArgumentError, 'iframe selector arguments must be specified' if args.blank?
75
- klass = "#{name}_page".classify.constantize
87
+
88
+ klass = args.first.is_a?(Class) ? args.shift : find_matching_class(name)
89
+ raise NameError, "class can not be found for #{name} iframe" if klass.blank?
90
+
76
91
  define_iframe(klass, name, args)
77
92
  define_has_iframe(name, args)
78
93
  define_has_no_iframe(name, args)
@@ -100,6 +115,11 @@ module Howitzer
100
115
  capybara_context.has_no_selector?(*iframe_element_selector(args, params))
101
116
  end
102
117
  end
118
+
119
+ def find_matching_class(name)
120
+ Howitzer::Web::Page.descendants.select { |el| el.name.underscore.tr('/', '_') == "#{name}_page" }
121
+ .max_by { |el| el.name.count('::') }
122
+ end
103
123
  end
104
124
  end
105
125
  end
@@ -1,6 +1,7 @@
1
1
  require 'singleton'
2
2
  require 'rspec/expectations'
3
3
  require 'addressable/template'
4
+ require 'howitzer/meta'
4
5
  require 'howitzer/web/capybara_methods_proxy'
5
6
  require 'howitzer/web/page_validator'
6
7
  require 'howitzer/web/element_dsl'
@@ -13,7 +14,7 @@ module Howitzer
13
14
  module Web
14
15
  # This class represents a single web page. This is a parent class for all web pages
15
16
  class Page
16
- UnknownPage = Class.new #:nodoc:
17
+ UnknownPage = Class.new # :nodoc:
17
18
  include Singleton
18
19
  include CapybaraMethodsProxy
19
20
  include ElementDsl
@@ -27,6 +28,7 @@ module Howitzer
27
28
  # This Ruby callback makes all inherited classes as singleton classes.
28
29
 
29
30
  def self.inherited(subclass)
31
+ super
30
32
  subclass.class_eval { include Singleton }
31
33
  end
32
34
 
@@ -64,6 +66,7 @@ module Howitzer
64
66
  page_list = matched_pages
65
67
  return UnknownPage if page_list.count.zero?
66
68
  return page_list.first if page_list.count == 1
69
+
67
70
  raise Howitzer::AmbiguousPageMatchingError, ambiguous_page_msg(page_list)
68
71
  end
69
72
 
@@ -76,6 +79,7 @@ module Howitzer
76
79
  end_time = ::Time.now + timeout
77
80
  until ::Time.now > end_time
78
81
  return true if opened?
82
+
79
83
  sleep(0.5)
80
84
  end
81
85
  raise Howitzer::IncorrectPageError, incorrect_page_msg
@@ -97,9 +101,16 @@ module Howitzer
97
101
  if defined?(path_value)
98
102
  return "#{site_value}#{Addressable::Template.new(path_value).expand(params, url_processor)}"
99
103
  end
104
+
100
105
  raise Howitzer::NoPathForPageError, "Please specify path for '#{self}' page. Example: path '/home'"
101
106
  end
102
107
 
108
+ # Provides access to meta information about entities on the page
109
+ # @return [Meta::Entry]
110
+ def meta
111
+ @meta ||= Meta::Entry.new(self)
112
+ end
113
+
103
114
  class << self
104
115
  protected
105
116
 
@@ -140,12 +151,12 @@ module Howitzer
140
151
 
141
152
  def incorrect_page_msg
142
153
  "Current page: #{current_page}, expected: #{self}.\n" \
143
- "\tCurrent url: #{current_url}\n\tCurrent title: #{instance.title}"
154
+ "\tCurrent url: #{current_url}\n\tCurrent title: #{instance.title}"
144
155
  end
145
156
 
146
157
  def ambiguous_page_msg(page_list)
147
158
  "Current page matches more that one page class (#{page_list.join(', ')}).\n" \
148
- "\tCurrent url: #{current_url}\n\tCurrent title: #{instance.title}"
159
+ "\tCurrent url: #{current_url}\n\tCurrent title: #{instance.title}"
149
160
  end
150
161
  end
151
162
 
@@ -153,7 +164,8 @@ module Howitzer
153
164
 
154
165
  def initialize
155
166
  check_validations_are_defined!
156
- current_window.maximize if Howitzer.maximized_window && Howitzer.driver != 'headless_chrome'
167
+ current_window.maximize if Howitzer.maximized_window &&
168
+ !%w[chrome headless_chrome].include?(Capybara.current_driver)
157
169
  end
158
170
 
159
171
  # Reloads current page in a browser
@@ -18,7 +18,7 @@ module Howitzer
18
18
  # HomePage.on { expect(HomePage.given).to have_menu_section } # Bad
19
19
  # HomePage.on { is_expected.to have_menu_section } # Good
20
20
 
21
- def is_expected # rubocop:disable Style/PredicateName
21
+ def is_expected # rubocop:disable Naming/PredicateName
22
22
  expect(page_klass.given)
23
23
  end
24
24
 
@@ -29,10 +29,15 @@ module Howitzer
29
29
  # * `out` method extracts an instance variable from an original context if starts from @.
30
30
  # Otherwise it executes a method from an original context
31
31
 
32
- def method_missing(name, *args, &block)
32
+ def method_missing(name, *args, **options, &block)
33
33
  return super if name =~ /\A(?:be|have)_/
34
- return eval_in_out_context(*args, &block) if name == :out
35
- page_klass.given.send(name, *args, &block)
34
+ return eval_in_out_context(*args, **options, &block) if name == :out
35
+
36
+ if options.present?
37
+ page_klass.given.send(name, *args, **options, &block)
38
+ else
39
+ page_klass.given.send(name, *args, &block)
40
+ end
36
41
  end
37
42
 
38
43
  # Makes proxied methods to be evaludated and returned as a proc
@@ -44,11 +49,17 @@ module Howitzer
44
49
 
45
50
  private
46
51
 
47
- def eval_in_out_context(*args, &block)
52
+ def eval_in_out_context(*args, **options, &block)
48
53
  return nil if args.size.zero?
54
+
49
55
  name = args.shift
50
56
  return get_outer_instance_variable(name) if name.to_s.start_with?('@')
51
- outer_context.send(name, *args, &block)
57
+
58
+ if options.present?
59
+ outer_context.send(name, *args, **options, &block)
60
+ else
61
+ outer_context.send(name, *args, &block)
62
+ end
52
63
  end
53
64
 
54
65
  def get_outer_instance_variable(name)
@@ -58,9 +69,10 @@ module Howitzer
58
69
  attr_accessor :page_klass, :outer_context
59
70
  end
60
71
 
61
- def self.included(base) #:nodoc:
72
+ def self.included(base) # :nodoc:
62
73
  base.extend(ClassMethods)
63
74
  end
75
+
64
76
  # This module holds page dsl class methods
65
77
  module ClassMethods
66
78
  # Allows to execute page methods in context of the page.
@@ -6,7 +6,7 @@ module Howitzer
6
6
  module PageValidator
7
7
  @validations = {}
8
8
 
9
- def self.included(base) #:nodoc:
9
+ def self.included(base) # :nodoc:
10
10
  base.extend(ClassMethods)
11
11
  end
12
12
 
@@ -21,17 +21,19 @@ module Howitzer
21
21
 
22
22
  def check_validations_are_defined!
23
23
  return if self.class.validations.present?
24
+
24
25
  raise Howitzer::NoValidationError, "No any page validation was found for '#{self.class.name}' page"
25
26
  end
26
27
 
27
28
  # This module holds page validation class methods
28
29
  module ClassMethods
29
30
  # Adds validation to validation list for current page
30
- # @param name [Symbol, String] a validation type. Possible values [:url, :element_presence, :title]
31
- # @param value [Symbol, String, Regexp]
31
+ # @param type [Symbol, String] a validation type. Possible values [:url, :element_presence, :title]
32
+ # @param pattern_or_element_name [Symbol, String, Regexp]
32
33
  # For :url and :title validation types must be <b>Regexp</b>
33
- # For :element_presence must be one of element names described for page
34
- # @param additional_value [Object, nil] any value required to pass for a labmda selector
34
+ # For :element_presence must be one of element names described for the page
35
+ # @param args [Array] any arguments required to pass for a lambda selector (:element_presence type only)
36
+ # @param options [Hash] keyword arguments required to pass for a lambda selector (:element_presence type only)
35
37
  # @raise [Howitzer::UnknownValidationError] if unknown validation type
36
38
  # @raise [Howitzer::UndefinedElementError] if :element_presence validations refers to undefined element name
37
39
  # @example
@@ -44,12 +46,23 @@ module Howitzer
44
46
  # end
45
47
  # @example
46
48
  # class HomePage < Howitzer::Web::Page
47
- # validate :element_presence, :menu_item, 'Logout'
48
- # element :menu_item, :xpath, ->(name) { ".//a[.='#{name}']" }
49
+ # validate :element_presence, :menu_item, lambda_args(text: 'Logout')
50
+ # element :menu_item, :xpath, ->(text:) { ".//a[.='#{text}']" }
49
51
  # end
50
52
 
51
- def validate(name, value, additional_value = nil)
52
- validate_by_type(name, value, additional_value)
53
+ def validate(type, pattern_or_element_name, *args, **options)
54
+ case type.to_s.to_sym
55
+ when :url, :title
56
+ if args.present? || options.present?
57
+ raise ArgumentError, "Additional arguments and options are not supported by '#{type}' the validator"
58
+ end
59
+
60
+ send("validate_by_#{type}", pattern_or_element_name)
61
+ when :element_presence
62
+ validate_by_element_presence(pattern_or_element_name, *args, **options)
63
+ else
64
+ raise Howitzer::UnknownValidationError, "unknown '#{type}' validation type"
65
+ end
53
66
  end
54
67
 
55
68
  # Check whether current page is opened or no
@@ -60,6 +73,7 @@ module Howitzer
60
73
 
61
74
  def opened?(sync: true)
62
75
  return validations.all? { |(_, validation)| validation.call(self, sync) } if validations.present?
76
+
63
77
  raise Howitzer::NoValidationError, "No any page validation was found for '#{name}' page"
64
78
  end
65
79
 
@@ -78,17 +92,17 @@ module Howitzer
78
92
 
79
93
  private
80
94
 
81
- def validate_element(element_name, value = nil)
95
+ def validate_by_element_presence(element_name, *args, **options)
82
96
  validations[:element_presence] =
83
97
  lambda do |web_page, sync|
84
98
  if element_name.present? && !private_method_defined?("#{element_name}_element")
85
99
  raise(Howitzer::UndefinedElementError, ':element_presence validation refers to ' \
86
- "undefined '#{element_name}' element on '#{name}' page.")
100
+ "undefined '#{element_name}' element on '#{name}' page.")
87
101
  end
88
102
  if sync
89
- web_page.instance.public_send(*["has_#{element_name}_element?", value].compact)
103
+ web_page.instance.public_send("has_#{element_name}_element?", *args, **options)
90
104
  else
91
- !web_page.instance.public_send(*["has_no_#{element_name}_element?", value].compact)
105
+ !web_page.instance.public_send("has_no_#{element_name}_element?", *args, **options)
92
106
  end
93
107
  end
94
108
  end
@@ -102,19 +116,6 @@ module Howitzer
102
116
  validations[:title] =
103
117
  ->(web_page, sync) { sync ? web_page.instance.has_title?(pattern) : pattern === web_page.instance.title }
104
118
  end
105
-
106
- def validate_by_type(type, value, additional_value)
107
- case type.to_s.to_sym
108
- when :url
109
- validate_by_url(value)
110
- when :element_presence
111
- validate_element(value, additional_value)
112
- when :title
113
- validate_by_title(value)
114
- else
115
- raise Howitzer::UnknownValidationError, "unknown '#{type}' validation type"
116
- end
117
- end
118
119
  end
119
120
  end
120
121
  end
@@ -1,25 +1,36 @@
1
1
  require 'howitzer/web/base_section'
2
+ require 'howitzer/meta'
2
3
 
3
4
  module Howitzer
4
5
  module Web
5
6
  # This class uses for named sections which possible to reuse in different pages
6
7
  class Section < BaseSection
8
+ # Provides access to meta information about entities in section
9
+ # @return [Meta::Entry]
10
+ def meta
11
+ @meta ||= Meta::Entry.new(self)
12
+ end
13
+
7
14
  class << self
8
15
  protected
9
16
 
10
17
  # DSL method which specifies section container selector represented by HTML element.
11
18
  # Any elements described in sections will start in this HTML element.
12
19
  # @param args [Array] original Capybara arguments. For details, see `Capybara::Node::Finders#all.
20
+ # @param options [Array] original Capybara options. For details, see `Capybara::Node::Finders#all.
13
21
  # @raise [ArgumentError] if no arguments were passed
14
22
  # @example
15
23
  # class MenuSection < Howitzer::Web::Section
16
- # me :xpath, ".//*[@id='panel']"
24
+ # me :xpath, ".//*[@id='panel']",
17
25
  # end
18
26
  # @!visibility public
19
27
 
20
- def me(*args)
28
+ def me(*args, **options)
21
29
  raise ArgumentError, 'Finder arguments are missing' if args.blank?
30
+
22
31
  @default_finder_args = args
32
+ @default_finder_options = options
33
+ self
23
34
  end
24
35
  end
25
36
  end