howitzer 1.0.1 → 1.0.2
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 +7 -0
- data/.gitignore +4 -1
- data/.travis.yml +3 -2
- data/CHANGELOG.md +25 -5
- data/GETTING_STARTED.md +158 -13
- data/README.md +49 -32
- data/Rakefile +10 -1
- data/bin/howitzer +49 -78
- data/features/cli_help.feature +30 -0
- data/features/cli_new.feature +263 -0
- data/features/cli_unknown.feature +17 -0
- data/features/cli_version.feature +14 -0
- data/features/step_definitions/common_steps.rb +1 -0
- data/features/support/env.rb +1 -0
- data/features/support/transformers.rb +3 -0
- data/generators/base_generator.rb +2 -0
- data/generators/config/config_generator.rb +1 -1
- data/generators/config/templates/default.yml +4 -14
- data/generators/cucumber/cucumber_generator.rb +1 -1
- data/generators/cucumber/templates/cucumber.yml +1 -2
- data/generators/cucumber/templates/env.rb +8 -7
- data/generators/emails/emails_generator.rb +1 -1
- data/generators/pages/pages_generator.rb +1 -1
- data/generators/pages/templates/example_page.rb +2 -2
- data/generators/root/root_generator.rb +1 -1
- data/generators/root/templates/Gemfile +1 -1
- data/generators/rspec/rspec_generator.rb +1 -1
- data/generators/rspec/templates/example_spec.rb +2 -2
- data/generators/rspec/templates/rspec.rake +1 -1
- data/generators/rspec/templates/spec_helper.rb +6 -5
- data/generators/tasks/tasks_generator.rb +1 -1
- data/generators/tasks/templates/common.rake +1 -0
- data/howitzer.gemspec +8 -8
- data/lib/howitzer.rb +4 -1
- data/lib/howitzer/blank_page.rb +6 -0
- data/lib/howitzer/capybara/dsl_ex.rb +15 -0
- data/lib/howitzer/capybara/settings.rb +267 -0
- data/lib/howitzer/email.rb +134 -0
- data/lib/howitzer/exceptions.rb +18 -0
- data/lib/howitzer/helpers.rb +34 -23
- data/lib/howitzer/init.rb +1 -4
- data/lib/howitzer/mailgun/client.rb +48 -0
- data/lib/howitzer/mailgun/connector.rb +34 -0
- data/lib/howitzer/mailgun/response.rb +28 -0
- data/lib/howitzer/utils.rb +2 -2
- data/lib/howitzer/utils/data_generator/data_storage.rb +15 -2
- data/lib/howitzer/utils/data_generator/gen.rb +14 -10
- data/lib/howitzer/utils/locator_store.rb +14 -7
- data/lib/howitzer/utils/log.rb +2 -0
- data/lib/howitzer/utils/page_validator.rb +74 -27
- data/lib/howitzer/version.rb +1 -1
- data/lib/howitzer/web_page.rb +83 -32
- data/spec/config/default.yml +10 -12
- data/spec/spec_helper.rb +12 -0
- data/spec/support/mailgun_unit_client.rb +60 -0
- data/spec/unit/generators/generators_spec.rb +7 -7
- data/spec/unit/lib/capybara/dsl_ex_spec.rb +60 -0
- data/spec/unit/lib/{capybara_settings_spec.rb → capybara/settings_spec.rb} +16 -10
- data/spec/unit/lib/email_spec.rb +129 -0
- data/spec/unit/lib/helpers_spec.rb +160 -34
- data/spec/unit/lib/init_spec.rb +1 -12
- data/spec/unit/lib/mailgun/client_spec.rb +36 -0
- data/spec/unit/lib/mailgun/connector_spec.rb +70 -0
- data/spec/unit/lib/mailgun/response_spec.rb +29 -0
- data/spec/unit/lib/utils/data_generator/data_storage_spec.rb +23 -5
- data/spec/unit/lib/utils/data_generator/gen_spec.rb +2 -63
- data/spec/unit/lib/utils/locator_store_spec.rb +41 -6
- data/spec/unit/lib/utils/log_spec.rb +1 -1
- data/spec/unit/lib/utils/page_validator_spec.rb +149 -25
- data/spec/unit/lib/web_page_spec.rb +127 -53
- metadata +102 -142
- data/lib/howitzer/utils/capybara_patched.rb +0 -23
- data/lib/howitzer/utils/capybara_settings.rb +0 -247
- data/lib/howitzer/utils/email/email.rb +0 -85
- data/lib/howitzer/utils/email/mail_client.rb +0 -132
- data/lib/howitzer/utils/email/mailgun.rb +0 -175
- data/lib/howitzer/utils/email/mailgun_helper.rb +0 -61
- data/spec/unit/bin/howitzer_spec.rb +0 -175
- data/spec/unit/lib/utils/email/email_spec.rb +0 -75
- data/spec/unit/lib/utils/email/mail_client_spec.rb +0 -115
- data/spec/unit/lib/utils/email/mailgun_helper_spec.rb +0 -95
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'howitzer/exceptions'
|
2
|
+
|
1
3
|
#The following are locator aliases:
|
2
4
|
#
|
3
5
|
#1) locator
|
@@ -18,8 +20,6 @@
|
|
18
20
|
|
19
21
|
|
20
22
|
module LocatorStore
|
21
|
-
BadLocatorParamsError = Class.new(StandardError)
|
22
|
-
LocatorNotDefinedError = Class.new(StandardError)
|
23
23
|
|
24
24
|
def self.included(base)
|
25
25
|
base.extend(ClassMethods)
|
@@ -27,8 +27,6 @@ module LocatorStore
|
|
27
27
|
|
28
28
|
module ClassMethods
|
29
29
|
LOCATOR_TYPES = [:base, :link, :field, :button]
|
30
|
-
class BadLocatorParamsError < StandardError; end
|
31
|
-
class LocatorNotSpecifiedError < StandardError; end
|
32
30
|
|
33
31
|
##
|
34
32
|
#
|
@@ -157,6 +155,15 @@ module LocatorStore
|
|
157
155
|
end
|
158
156
|
end
|
159
157
|
|
158
|
+
def first_element(name)
|
159
|
+
type, locator = find_locator(name)
|
160
|
+
if type == :base
|
161
|
+
send :first, locator
|
162
|
+
else
|
163
|
+
send :first, type, locator
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
160
167
|
protected
|
161
168
|
|
162
169
|
def find_locator(name)
|
@@ -164,7 +171,7 @@ module LocatorStore
|
|
164
171
|
LOCATOR_TYPES.each do|type|
|
165
172
|
return [type, locator_by_type(type, name)] if (@locators || {}).fetch(self.name, {}).fetch(type, {})[name]
|
166
173
|
end
|
167
|
-
|
174
|
+
log.error(Howitzer::LocatorNotDefinedError, name)
|
168
175
|
end
|
169
176
|
|
170
177
|
# looks up locator in current and all super classes
|
@@ -180,7 +187,7 @@ module LocatorStore
|
|
180
187
|
|
181
188
|
def locator_by_type(type, name)
|
182
189
|
locator = parent_locator(type, name)
|
183
|
-
|
190
|
+
log.error(Howitzer::LocatorNotDefinedError, name) if locator.nil?
|
184
191
|
locator
|
185
192
|
end
|
186
193
|
|
@@ -188,7 +195,7 @@ module LocatorStore
|
|
188
195
|
@locators ||= {}
|
189
196
|
@locators[self.name] ||= {}
|
190
197
|
@locators[self.name][type] ||= {}
|
191
|
-
|
198
|
+
log.error Howitzer::BadLocatorParamsError, args.inspect if params.nil? || (!params.is_a?(Proc) && params.empty?)
|
192
199
|
case params.class.name
|
193
200
|
when 'Hash'
|
194
201
|
@locators[self.name][type][name] = [params.keys.first.to_sym, params.values.first.to_s]
|
data/lib/howitzer/utils/log.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
|
+
require 'howitzer/exceptions'
|
2
|
+
|
1
3
|
module Howitzer
|
2
4
|
module Utils
|
3
5
|
module PageValidator
|
4
|
-
WrongOptionError = Class.new(StandardError)
|
5
|
-
NoValidationError = Class.new(StandardError)
|
6
|
-
UnknownValidationName = Class.new(StandardError)
|
7
6
|
@validations = {}
|
8
7
|
|
8
|
+
def self.included(base) #:nodoc:
|
9
|
+
base.extend(ClassMethods)
|
10
|
+
end
|
11
|
+
|
9
12
|
##
|
10
13
|
#
|
11
14
|
# Returns validation list
|
@@ -16,20 +19,40 @@ module Howitzer
|
|
16
19
|
@validations
|
17
20
|
end
|
18
21
|
|
19
|
-
|
20
|
-
|
22
|
+
##
|
23
|
+
#
|
24
|
+
# Returns page list
|
25
|
+
#
|
26
|
+
# @return [Array]
|
27
|
+
#
|
28
|
+
def self.pages
|
29
|
+
@pages ||= []
|
21
30
|
end
|
22
31
|
|
23
32
|
##
|
33
|
+
# Check if any validations are defined, if no, tries to find old style, else raise error
|
24
34
|
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
# @raise [Howitzer::Utils::PageValidator::NoValidationError] If no validation was specified
|
35
|
+
# @raise [Howitzer::NoValidationError] If no one validation is defined for page
|
28
36
|
#
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
37
|
+
|
38
|
+
def check_validations_are_defined!
|
39
|
+
if validations.nil? && !old_url_validation_present?
|
40
|
+
log.error Howitzer::NoValidationError, "No any page validation was found for '#{self.class.name}' page"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def validations
|
47
|
+
PageValidator.validations[self.class.name]
|
48
|
+
end
|
49
|
+
|
50
|
+
def old_url_validation_present?
|
51
|
+
if self.class.const_defined?("URL_PATTERN")
|
52
|
+
self.class.validates :url, pattern: self.class.const_get("URL_PATTERN")
|
53
|
+
warn "[Deprecated] Old style page validation is using. Please use new style:\n\t validates :url, pattern: URL_PATTERN"
|
54
|
+
true
|
55
|
+
end
|
33
56
|
end
|
34
57
|
|
35
58
|
module ClassMethods
|
@@ -42,41 +65,65 @@ module Howitzer
|
|
42
65
|
# @option options [Hash] Validation options
|
43
66
|
# :pattern => [Regexp] For :url and :title validation types
|
44
67
|
# :locator => [String] For :element_presence (Existing locator name)
|
45
|
-
# @raise [Howitzer::
|
68
|
+
# @raise [Howitzer::UnknownValidationError] If unknown validation type was passed
|
46
69
|
#
|
47
70
|
def validates(name, options)
|
48
|
-
|
71
|
+
log.error TypeError, "Expected options to be Hash, actual is '#{options.class}'" unless options.class == Hash
|
49
72
|
PageValidator.validations[self.name] ||= {}
|
50
73
|
case name.to_sym
|
51
74
|
when :url
|
52
|
-
|
75
|
+
validate_by_pattern(:url, options)
|
53
76
|
when :element_presence
|
54
77
|
validate_element options
|
55
78
|
when :title
|
56
|
-
|
79
|
+
validate_by_pattern(:title, options)
|
57
80
|
else
|
58
|
-
|
81
|
+
log.error Howitzer::UnknownValidationError, "unknown '#{name}' validation name"
|
59
82
|
end
|
60
83
|
end
|
61
84
|
|
62
|
-
|
85
|
+
##
|
86
|
+
# Check whether page is opened or no
|
87
|
+
#
|
88
|
+
# @raise [Howitzer::NoValidationError] If no one validation is defined for page
|
89
|
+
#
|
90
|
+
# *Returns:*
|
91
|
+
# * +boolean+
|
92
|
+
#
|
63
93
|
|
64
|
-
def
|
65
|
-
|
66
|
-
|
67
|
-
|
94
|
+
def opened?
|
95
|
+
validation_list = PageValidator.validations[self.name]
|
96
|
+
if validation_list.blank?
|
97
|
+
log.error Howitzer::NoValidationError, "No any page validation was found for '#{self.name}' page"
|
98
|
+
else
|
99
|
+
!validation_list.any? {|(_, validation)| !validation.call(self)}
|
100
|
+
end
|
68
101
|
end
|
69
102
|
|
103
|
+
##
|
104
|
+
#
|
105
|
+
# Finds all matched pages which are satisfy of defined validations
|
106
|
+
#
|
107
|
+
# *Returns:*
|
108
|
+
# * +array+ - page names
|
109
|
+
#
|
110
|
+
|
111
|
+
def matched_pages
|
112
|
+
PageValidator.pages.select{|klass| klass.opened? }
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
70
117
|
def validate_element(options)
|
71
118
|
locator = options[:locator] || options["locator"]
|
72
|
-
|
73
|
-
PageValidator.validations[self.name][:element_presence] = lambda { |web_page| web_page.
|
119
|
+
log.error Howitzer::WrongOptionError, "Please specify ':locator' option as one of page locator names" if locator.nil? || locator.empty?
|
120
|
+
PageValidator.validations[self.name][:element_presence] = lambda { |web_page| web_page.first_element(locator) }
|
74
121
|
end
|
75
122
|
|
76
|
-
def
|
123
|
+
def validate_by_pattern(name, options)
|
77
124
|
pattern = options[:pattern] || options["pattern"]
|
78
|
-
|
79
|
-
PageValidator.validations[self.name][
|
125
|
+
log.error Howitzer::WrongOptionError, "Please specify ':pattern' option as Regexp object" if pattern.nil? || !pattern.is_a?(Regexp)
|
126
|
+
PageValidator.validations[self.name][name] = lambda { |web_page| pattern === web_page.send(name) }
|
80
127
|
end
|
81
128
|
|
82
129
|
end
|
data/lib/howitzer/version.rb
CHANGED
data/lib/howitzer/web_page.rb
CHANGED
@@ -1,22 +1,25 @@
|
|
1
|
+
require "singleton"
|
1
2
|
require "rspec/expectations"
|
2
3
|
require "howitzer/utils/locator_store"
|
3
4
|
require "howitzer/utils/page_validator"
|
4
|
-
require "
|
5
|
+
require "howitzer/capybara/dsl_ex"
|
6
|
+
require 'howitzer/exceptions'
|
5
7
|
|
6
8
|
class WebPage
|
7
9
|
|
8
|
-
BLANK_PAGE = 'about:blank'
|
9
|
-
|
10
|
+
BLANK_PAGE = 'about:blank' # @deprecated , use BlankPage instead
|
11
|
+
UnknownPage = Class.new
|
10
12
|
|
11
13
|
include LocatorStore
|
12
14
|
include Howitzer::Utils::PageValidator
|
13
15
|
include RSpec::Matchers
|
14
|
-
include Capybara::
|
15
|
-
extend Capybara::
|
16
|
+
include Howitzer::Capybara::DslEx
|
17
|
+
extend Howitzer::Capybara::DslEx
|
16
18
|
include Singleton
|
17
19
|
|
18
20
|
def self.inherited(subclass)
|
19
21
|
subclass.class_eval { include Singleton }
|
22
|
+
Howitzer::Utils::PageValidator.pages << subclass
|
20
23
|
end
|
21
24
|
|
22
25
|
##
|
@@ -30,7 +33,7 @@ class WebPage
|
|
30
33
|
# * +WebPage+ - New instance of current class
|
31
34
|
#
|
32
35
|
|
33
|
-
def self.open(url="#{app_url}#{self::URL}")
|
36
|
+
def self.open(url = "#{app_url unless self == BlankPage}#{self::URL}")
|
34
37
|
log.info "Open #{self.name} page by '#{url}' url"
|
35
38
|
retryable(tries: 2, logger: log, trace: true, on: Exception) do |retries|
|
36
39
|
log.info "Retry..." unless retries.zero?
|
@@ -48,7 +51,72 @@ class WebPage
|
|
48
51
|
#
|
49
52
|
|
50
53
|
def self.given
|
51
|
-
|
54
|
+
wait_for_opened
|
55
|
+
self.instance
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
#
|
60
|
+
# Returns current url
|
61
|
+
#
|
62
|
+
# *Returns:*
|
63
|
+
# * +string+ - Current url
|
64
|
+
#
|
65
|
+
|
66
|
+
def self.url
|
67
|
+
self.current_url
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
#
|
72
|
+
# Returns body text of html page
|
73
|
+
#
|
74
|
+
# *Returns:*
|
75
|
+
# * +string+ - Body text
|
76
|
+
#
|
77
|
+
|
78
|
+
def self.text
|
79
|
+
page.find('body').text
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
#
|
84
|
+
# Tries to identify current page name or raise error if ambiguous page matching
|
85
|
+
#
|
86
|
+
# *Returns:*
|
87
|
+
# * +string+ - page name
|
88
|
+
#
|
89
|
+
|
90
|
+
def self.current_page
|
91
|
+
page_list = matched_pages
|
92
|
+
if page_list.count.zero?
|
93
|
+
UnknownPage
|
94
|
+
elsif page_list.count > 1
|
95
|
+
log.error Howitzer::AmbiguousPageMatchingError,
|
96
|
+
"Current page matches more that one page class (#{page_list.join(', ')}).\n\tCurrent url: #{current_url}\n\tCurrent title: #{title}"
|
97
|
+
elsif page_list.count == 1
|
98
|
+
page_list.first
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
##
|
103
|
+
#
|
104
|
+
# Waits until web page is not opened, or raise error after timeout
|
105
|
+
#
|
106
|
+
# *Parameters:*
|
107
|
+
# * +time_out+ - Seconds that will be waiting for web page to be loaded
|
108
|
+
#
|
109
|
+
|
110
|
+
def self.wait_for_opened(timeout=settings.timeout_small)
|
111
|
+
end_time = ::Time.now + timeout
|
112
|
+
until ::Time.now > end_time
|
113
|
+
self.opened? ? return : sleep(0.5)
|
114
|
+
end
|
115
|
+
log.error Howitzer::IncorrectPageError, "Current page: #{self.current_page}, expected: #{self}.\n\tCurrent url: #{current_url}\n\tCurrent title: #{title}"
|
116
|
+
end
|
117
|
+
|
118
|
+
def initialize
|
119
|
+
check_validations_are_defined!
|
52
120
|
end
|
53
121
|
|
54
122
|
##
|
@@ -109,6 +177,7 @@ class WebPage
|
|
109
177
|
|
110
178
|
# @deprecated
|
111
179
|
# With Capybara 2.x it is extra
|
180
|
+
#:nocov:
|
112
181
|
def wait_for_ajax(timeout=settings.timeout_small, message=nil)
|
113
182
|
end_time = ::Time.now + timeout
|
114
183
|
until ::Time.now > end_time
|
@@ -117,8 +186,10 @@ class WebPage
|
|
117
186
|
end
|
118
187
|
log.error message || "Timed out waiting for ajax requests to complete"
|
119
188
|
end
|
189
|
+
#:nocov:
|
120
190
|
|
121
191
|
##
|
192
|
+
# @deprecated
|
122
193
|
#
|
123
194
|
# Waits until web page is loaded
|
124
195
|
#
|
@@ -128,15 +199,17 @@ class WebPage
|
|
128
199
|
#
|
129
200
|
|
130
201
|
def wait_for_url(expected_url, timeout=settings.timeout_small)
|
202
|
+
warn "[Deprecated] This method is deprecated, and will be removed in next version of Howitzer"
|
131
203
|
end_time = ::Time.now + timeout
|
132
204
|
until ::Time.now > end_time
|
133
205
|
operator = expected_url.is_a?(Regexp) ? :=~ : :==
|
134
206
|
return true if current_url.send(operator, expected_url).tap{|res| sleep 1 unless res}
|
135
207
|
end
|
136
|
-
log.error IncorrectPageError, "Current url: #{current_url}, expected: #{expected_url}"
|
208
|
+
log.error Howitzer::IncorrectPageError, "Current url: #{current_url}, expected: #{expected_url}"
|
137
209
|
end
|
138
210
|
|
139
211
|
##
|
212
|
+
# @deprecated
|
140
213
|
#
|
141
214
|
# Waits until web is loaded with expected title
|
142
215
|
#
|
@@ -146,12 +219,13 @@ class WebPage
|
|
146
219
|
#
|
147
220
|
|
148
221
|
def wait_for_title(expected_title, timeout=settings.timeout_small)
|
222
|
+
warn "[Deprecated] This method is deprecated, and will be removed in next version of Howitzer"
|
149
223
|
end_time = ::Time.now + timeout
|
150
224
|
until ::Time.now > end_time
|
151
225
|
operator = expected_title.is_a?(Regexp) ? :=~ : :==
|
152
226
|
return true if title.send(operator, expected_title).tap{|res| sleep 1 unless res}
|
153
227
|
end
|
154
|
-
log.error IncorrectPageError, "Current title: #{title}, expected: #{expected_title}"
|
228
|
+
log.error Howitzer::IncorrectPageError, "Current title: #{title}, expected: #{expected_title}"
|
155
229
|
end
|
156
230
|
|
157
231
|
##
|
@@ -164,18 +238,6 @@ class WebPage
|
|
164
238
|
visit current_url
|
165
239
|
end
|
166
240
|
|
167
|
-
##
|
168
|
-
#
|
169
|
-
# Returns current url
|
170
|
-
#
|
171
|
-
# *Returns:*
|
172
|
-
# * +string+ - Current url
|
173
|
-
#
|
174
|
-
|
175
|
-
def self.current_url
|
176
|
-
page.current_url
|
177
|
-
end
|
178
|
-
|
179
241
|
##
|
180
242
|
#
|
181
243
|
# Returns Page title
|
@@ -188,15 +250,4 @@ class WebPage
|
|
188
250
|
page.title
|
189
251
|
end
|
190
252
|
|
191
|
-
##
|
192
|
-
#
|
193
|
-
# Returns body text of html page
|
194
|
-
#
|
195
|
-
# *Returns:*
|
196
|
-
# * +string+ - Body text
|
197
|
-
#
|
198
|
-
|
199
|
-
def self.text
|
200
|
-
page.find('body').text
|
201
|
-
end
|
202
253
|
end
|
data/spec/config/default.yml
CHANGED
@@ -12,17 +12,15 @@ hide_datetime_from_log: true
|
|
12
12
|
driver: selenium
|
13
13
|
|
14
14
|
###########################################################
|
15
|
-
#
|
15
|
+
# MAILGUN SETTINGS #
|
16
16
|
###########################################################
|
17
|
-
|
18
|
-
|
19
|
-
mail_smtp_domain: demo.com
|
20
|
-
mail_smtp_user_name: test_user
|
21
|
-
mail_smtp_user_pass: mypass
|
17
|
+
mailgun_key: mailgun_account_private_key
|
18
|
+
mailgun_domain: mailgun@test.domain
|
22
19
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
20
|
+
############################################################
|
21
|
+
# TIMEOUTS #
|
22
|
+
############################################################
|
23
|
+
timeout_tiny: 1
|
24
|
+
timeout_short: 2
|
25
|
+
timeout_small: 5
|
26
|
+
timeout_medium: 15
|