howitzer 0.0.3 → 1.0.1

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 (81) hide show
  1. data/.gitignore +8 -0
  2. data/.rspec +3 -0
  3. data/.travis.yml +4 -0
  4. data/CHANGELOG.md +32 -0
  5. data/GETTING_STARTED.md +529 -0
  6. data/Gemfile +4 -2
  7. data/LICENSE +2 -2
  8. data/README.md +57 -13
  9. data/Rakefile +9 -2
  10. data/bin/howitzer +79 -31
  11. data/generators/base_generator.rb +87 -0
  12. data/generators/config/config_generator.rb +16 -20
  13. data/generators/config/templates/default.yml +26 -7
  14. data/generators/cucumber/cucumber_generator.rb +20 -26
  15. data/generators/{tasks → cucumber}/templates/cucumber.rake +0 -0
  16. data/generators/{config → cucumber}/templates/cucumber.yml +0 -0
  17. data/generators/emails/emails_generator.rb +11 -18
  18. data/generators/emails/templates/example_email.rb +1 -0
  19. data/generators/pages/pages_generator.rb +16 -18
  20. data/generators/pages/templates/example_menu.rb +1 -0
  21. data/generators/pages/templates/example_page.rb +1 -1
  22. data/generators/root/root_generator.rb +18 -20
  23. data/generators/root/templates/.gitignore +2 -1
  24. data/generators/root/templates/Gemfile +3 -2
  25. data/generators/root/templates/Rakefile +10 -0
  26. data/generators/root/templates/boot.rb +3 -9
  27. data/generators/rspec/rspec_generator.rb +23 -0
  28. data/generators/rspec/templates/example_spec.rb +7 -0
  29. data/generators/rspec/templates/rspec.rake +34 -0
  30. data/generators/rspec/templates/spec_helper.rb +56 -0
  31. data/generators/tasks/tasks_generator.rb +11 -17
  32. data/generators/tasks/templates/common.rake +15 -0
  33. data/howitzer.gemspec +13 -2
  34. data/lib/howitzer.rb +1 -0
  35. data/lib/howitzer/helpers.rb +87 -2
  36. data/lib/howitzer/init.rb +0 -1
  37. data/lib/howitzer/patches/rawler_patched.rb +86 -0
  38. data/lib/howitzer/settings.rb +27 -0
  39. data/lib/howitzer/utils.rb +3 -12
  40. data/lib/howitzer/utils/capybara_patched.rb +3 -2
  41. data/lib/howitzer/utils/capybara_settings.rb +158 -24
  42. data/lib/howitzer/utils/data_generator/data_storage.rb +35 -1
  43. data/lib/howitzer/utils/data_generator/gen.rb +45 -3
  44. data/lib/howitzer/utils/email/email.rb +44 -5
  45. data/lib/howitzer/utils/email/mail_client.rb +28 -22
  46. data/lib/howitzer/utils/email/mailgun_helper.rb +30 -4
  47. data/lib/howitzer/utils/locator_store.rb +111 -19
  48. data/lib/howitzer/utils/log.rb +137 -0
  49. data/lib/howitzer/utils/page_validator.rb +86 -0
  50. data/lib/howitzer/vendor/firebug-1.12.1-fx.xpi +0 -0
  51. data/lib/howitzer/vendor/firepath-0.9.7-fx.xpi +0 -0
  52. data/lib/howitzer/version.rb +2 -2
  53. data/lib/howitzer/web_page.rb +159 -19
  54. data/spec/active_resource.rb +0 -0
  55. data/spec/config/custom.yml +1 -0
  56. data/spec/config/default.yml +28 -0
  57. data/spec/spec_helper.rb +46 -1
  58. data/spec/support/boot_helper.rb +15 -0
  59. data/spec/support/generator_helper.rb +13 -0
  60. data/spec/support/logger_helper.rb +12 -0
  61. data/spec/unit/bin/howitzer_spec.rb +175 -0
  62. data/spec/unit/generators/generators_spec.rb +175 -0
  63. data/spec/unit/lib/capybara_settings_spec.rb +170 -0
  64. data/spec/unit/lib/helpers_spec.rb +619 -0
  65. data/spec/unit/lib/init_spec.rb +14 -0
  66. data/spec/unit/lib/settings_spec.rb +17 -0
  67. data/spec/unit/lib/utils/data_generator/data_storage_spec.rb +62 -0
  68. data/spec/unit/lib/utils/data_generator/gen_spec.rb +151 -0
  69. data/spec/unit/lib/utils/email/email_spec.rb +75 -0
  70. data/spec/unit/lib/utils/email/mail_client_spec.rb +115 -0
  71. data/spec/unit/lib/utils/email/mailgun_helper_spec.rb +95 -0
  72. data/spec/unit/lib/utils/locator_store_spec.rb +122 -0
  73. data/spec/unit/lib/utils/log_spec.rb +107 -0
  74. data/spec/unit/lib/utils/page_validator_spec.rb +142 -0
  75. data/spec/unit/lib/web_page_spec.rb +250 -0
  76. data/spec/unit/version_spec.rb +8 -0
  77. metadata +215 -15
  78. data/Gemfile.lock +0 -103
  79. data/History.md +0 -20
  80. data/lib/howitzer/utils/logger.rb +0 -108
  81. data/spec/howitzer/version_spec.rb +0 -8
@@ -0,0 +1,137 @@
1
+ require 'log4r'
2
+ require 'fileutils'
3
+
4
+ module Howitzer
5
+ class Log
6
+ include Singleton
7
+ include Log4r
8
+
9
+ [:debug, :info, :warn, :fatal].each do |method_name|
10
+ define_method method_name do |text|
11
+ @logger.send(method_name, text)
12
+ end
13
+ end
14
+
15
+ ##
16
+ #
17
+ # Prints log entry about error with ERROR severity
18
+ # *Examples:*
19
+ # log.error MyException, 'Some error text', caller
20
+ # log.error 'Some error text', caller
21
+ # log.error MyException, 'Some caller text'
22
+ # log.error 'Some error text'
23
+ # log.error err_object
24
+ #
25
+ # *Parameters:*
26
+ # * +args+ - see example
27
+ #
28
+
29
+ def error(*args)
30
+ object = if args.first.nil?
31
+ $!
32
+ else
33
+ case args.size
34
+ when 1
35
+ args.first.is_a?(Exception) ? args.first : RuntimeError.new(args.first)
36
+ when 2
37
+ if args.first.is_a?(Class) && args.first < Exception
38
+ args.first.new(args.last)
39
+ else
40
+ exception = RuntimeError.new(args.first)
41
+ exception.set_backtrace(args.last)
42
+ exception
43
+ end
44
+ when 3
45
+ exception = args.first.new(args[1])
46
+ exception.set_backtrace(args.last)
47
+ exception
48
+ else nil
49
+ end
50
+ end
51
+ err_backtrace = object.backtrace ? "\n\t#{object.backtrace.join("\n\t")}" : nil
52
+ @logger.error("[#{object.class}] #{object.message}#{err_backtrace}")
53
+ fail(object)
54
+ end
55
+
56
+ ##
57
+ #
58
+ # Prints feature name into log with INFO severity
59
+ #
60
+ # *Parameters:*
61
+ # * +text+ - Feature name
62
+ #
63
+
64
+ def print_feature_name(text)
65
+ log_without_formatting{ info "*** Feature: #{text.upcase} ***" }
66
+ end
67
+
68
+ ##
69
+ #
70
+ # Returns formatted howitzer settings
71
+ #
72
+
73
+ def settings_as_formatted_text
74
+ log_without_formatting{ info settings.as_formatted_text }
75
+ end
76
+
77
+ ##
78
+ #
79
+ # Prints scenario name into log with INFO severity
80
+ #
81
+ # *Parameters:*
82
+ # * +text+ - Scenario name
83
+ #
84
+ def print_scenario_name(text)
85
+ log_without_formatting{ info " => Scenario: #{text}" }
86
+ end
87
+
88
+ private
89
+
90
+ def initialize
91
+ @logger = Logger.new("ruby_log")
92
+ @logger.add(console_log)
93
+ @logger.add(error_log)
94
+ @logger.add(txt_log)
95
+ self.base_formatter = default_formatter
96
+ Logger["ruby_log"].level = settings.debug_mode ? ALL : INFO
97
+ Logger["ruby_log"].trace = true
98
+ end
99
+
100
+ def log_without_formatting
101
+ self.base_formatter = blank_formatter
102
+ yield
103
+ self.base_formatter = default_formatter
104
+ end
105
+
106
+ def console_log
107
+ StdoutOutputter.new(:console).tap{|o| o.only_at(INFO, DEBUG, WARN)}
108
+ end
109
+
110
+ def error_log
111
+ StderrOutputter.new(:error, 'level' => ERROR)
112
+ end
113
+
114
+ def txt_log
115
+ FileUtils.mkdir_p(settings.log_dir) unless File.exists?(settings.log_dir)
116
+ filename = File.join(settings.log_dir, settings.txt_log)
117
+ FileOutputter.new(:txt_log, :filename => filename, :trunc => true)
118
+ end
119
+
120
+ def blank_formatter
121
+ PatternFormatter.new(:pattern => "%m")
122
+ end
123
+
124
+ def default_formatter
125
+ if settings.hide_datetime_from_log
126
+ params = {pattern: "[%l] %m"}
127
+ else
128
+ params = {pattern: "%d [%l] :: %m", date_pattern: "%Y/%m/%d %H:%M:%S"}
129
+ end
130
+ PatternFormatter.new(params)
131
+ end
132
+
133
+ def base_formatter=(formatter)
134
+ @logger.outputters.each {|outputter| outputter.formatter = formatter}
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,86 @@
1
+ module Howitzer
2
+ module Utils
3
+ module PageValidator
4
+ WrongOptionError = Class.new(StandardError)
5
+ NoValidationError = Class.new(StandardError)
6
+ UnknownValidationName = Class.new(StandardError)
7
+ @validations = {}
8
+
9
+ ##
10
+ #
11
+ # Returns validation list
12
+ #
13
+ # @return [Hash]
14
+ #
15
+ def self.validations
16
+ @validations
17
+ end
18
+
19
+ def self.included(base) #:nodoc:
20
+ base.extend(ClassMethods)
21
+ end
22
+
23
+ ##
24
+ #
25
+ # Checks that correct page has been loaded
26
+ #
27
+ # @raise [Howitzer::Utils::PageValidator::NoValidationError] If no validation was specified
28
+ #
29
+ def check_correct_page_loaded
30
+ validations = PageValidator.validations[self.class.name]
31
+ raise NoValidationError, "No any page validation was found" if validations.nil?
32
+ validations.each {|(_, validation)| validation.call(self)}
33
+ end
34
+
35
+ module ClassMethods
36
+
37
+ ##
38
+ #
39
+ # Adds validation to validation list
40
+ #
41
+ # @param [Symbol or String] name Which validation type. Possible values [:url, :element_presence, :title]
42
+ # @option options [Hash] Validation options
43
+ # :pattern => [Regexp] For :url and :title validation types
44
+ # :locator => [String] For :element_presence (Existing locator name)
45
+ # @raise [Howitzer::Utils::PageValidator::UnknownValidationName] If unknown validation type was passed
46
+ #
47
+ def validates(name, options)
48
+ raise TypeError, "Expected options to be Hash, actual is '#{options.class}'" unless options.class == Hash
49
+ PageValidator.validations[self.name] ||= {}
50
+ case name.to_sym
51
+ when :url
52
+ validate_url options
53
+ when :element_presence
54
+ validate_element options
55
+ when :title
56
+ validate_title options
57
+ else
58
+ raise UnknownValidationName, "unknown '#{name}' validation name"
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def validate_url(options)
65
+ pattern = options[:pattern] || options["pattern"]
66
+ raise WrongOptionError, "Please specify ':pattern' option as Regexp object" if pattern.nil? || !pattern.is_a?(Regexp)
67
+ PageValidator.validations[self.name][:url] = lambda { |web_page| web_page.wait_for_url(pattern) }
68
+ end
69
+
70
+ def validate_element(options)
71
+ locator = options[:locator] || options["locator"]
72
+ raise WrongOptionError, "Please specify ':locator' option as one of page locator names" if locator.nil? || locator.empty?
73
+ PageValidator.validations[self.name][:element_presence] = lambda { |web_page| web_page.find_element(locator) }
74
+ end
75
+
76
+ def validate_title(options)
77
+ pattern = options[:pattern] || options["pattern"]
78
+ raise WrongOptionError, "Please specify ':pattern' option as Regexp object" if pattern.nil? || !pattern.is_a?(Regexp)
79
+ PageValidator.validations[self.name][:title] = lambda { |web_page| web_page.wait_for_title(pattern) }
80
+ end
81
+
82
+ end
83
+ end
84
+
85
+ end
86
+ end
@@ -1,3 +1,3 @@
1
1
  module Howitzer
2
- VERSION = "0.0.3"
3
- end
2
+ VERSION = "1.0.1"
3
+ end
@@ -1,62 +1,202 @@
1
1
  require "rspec/expectations"
2
- require 'howitzer/utils/locator_store'
2
+ require "howitzer/utils/locator_store"
3
+ require "howitzer/utils/page_validator"
4
+ require "singleton"
3
5
 
4
6
  class WebPage
5
7
 
6
8
  BLANK_PAGE = 'about:blank'
9
+ IncorrectPageError = Class.new(StandardError)
7
10
 
8
- #TODO include Capybara DSL here
9
11
  include LocatorStore
12
+ include Howitzer::Utils::PageValidator
10
13
  include RSpec::Matchers
14
+ include Capybara::DSL
15
+ extend Capybara::DSL
16
+ include Singleton
11
17
 
12
- def self.open(url="#{settings.app_url}#{self::URL}")
18
+ def self.inherited(subclass)
19
+ subclass.class_eval { include Singleton }
20
+ end
21
+
22
+ ##
23
+ #
24
+ # Opens web-site by given url
25
+ #
26
+ # *Parameters:*
27
+ # * +url+ - Url string that will be opened
28
+ #
29
+ # *Returns:*
30
+ # * +WebPage+ - New instance of current class
31
+ #
32
+
33
+ def self.open(url="#{app_url}#{self::URL}")
13
34
  log.info "Open #{self.name} page by '#{url}' url"
14
35
  retryable(tries: 2, logger: log, trace: true, on: Exception) do |retries|
15
36
  log.info "Retry..." unless retries.zero?
16
37
  visit url
17
38
  end
18
- new
39
+ given
19
40
  end
20
41
 
42
+ ##
43
+ #
44
+ # Returns singleton instance of current web page
45
+ #
46
+ # *Returns:*
47
+ # * +WebPage+ - Singleton instance
48
+ #
49
+
21
50
  def self.given
22
- new
51
+ self.instance.tap{ |page| page.check_correct_page_loaded }
23
52
  end
24
53
 
54
+ ##
55
+ #
56
+ # Fills in field that using Tinymce API
57
+ #
58
+ # *Parameters:*
59
+ # * +name+ - Frame name that contains Tinymce field
60
+ # * +Hash+ - Not required options
61
+ #
62
+
63
+ def tinymce_fill_in(name, options = {})
64
+ if %w[selenium selenium_dev sauce].include? settings.driver
65
+ page.driver.browser.switch_to.frame("#{name}_ifr")
66
+ page.find(:css, '#tinymce').native.send_keys(options[:with])
67
+ page.driver.browser.switch_to.default_content
68
+ else
69
+ page.execute_script("tinyMCE.get('#{name}').setContent('#{options[:with]}')")
70
+ end
71
+ end
72
+
73
+ ##
74
+ #
75
+ # Accepts or declines JS alert box by given flag
76
+ #
77
+ # *Parameters:*
78
+ # * +flag+ [TrueClass,FalseClass] - Determines accept or decline alert box
79
+ #
80
+
81
+ def click_alert_box(flag)
82
+ if %w[selenium selenium_dev sauce].include? settings.driver
83
+ if flag
84
+ page.driver.browser.switch_to.alert.accept
85
+ else
86
+ page.driver.browser.switch_to.alert.dismiss
87
+ end
88
+ else
89
+ if flag
90
+ page.evaluate_script('window.confirm = function() { return true; }')
91
+ else
92
+ page.evaluate_script('window.confirm = function() { return false; }')
93
+ end
94
+ end
95
+ end
96
+
97
+ ##
98
+ #
99
+ # Clicks on button or link using JS event call
100
+ #
101
+ # *Parameters:*
102
+ # * +css_locator+ - Css locator of link or button
103
+ #
104
+
105
+ def js_click(css_locator)
106
+ page.execute_script("$('#{css_locator}').trigger('click')")
107
+ sleep settings.timeout_tiny
108
+ end
109
+
110
+ # @deprecated
111
+ # With Capybara 2.x it is extra
25
112
  def wait_for_ajax(timeout=settings.timeout_small, message=nil)
26
113
  end_time = ::Time.now + timeout
27
114
  until ::Time.now > end_time
28
- return if page.evaluate_script('$.active') == 0
115
+ return true if page.evaluate_script('$.active') == 0
29
116
  sleep 0.25
30
- log.info "#{Time.now}"
31
117
  end
32
118
  log.error message || "Timed out waiting for ajax requests to complete"
33
-
34
119
  end
35
120
 
36
- def wait_for_url(expected_url, time_out=settings.timeout_small)
37
- wait_until(time_out) do
121
+ ##
122
+ #
123
+ # Waits until web page is loaded
124
+ #
125
+ # *Parameters:*
126
+ # * +expected_url+ - Url that will be waiting for
127
+ # * +time_out+ - Seconds that will be waiting for web-site to be loaded until raise error
128
+ #
129
+
130
+ def wait_for_url(expected_url, timeout=settings.timeout_small)
131
+ end_time = ::Time.now + timeout
132
+ until ::Time.now > end_time
38
133
  operator = expected_url.is_a?(Regexp) ? :=~ : :==
39
- current_url.send(operator, expected_url).tap{|res| sleep 1 unless res}
134
+ return true if current_url.send(operator, expected_url).tap{|res| sleep 1 unless res}
135
+ end
136
+ log.error IncorrectPageError, "Current url: #{current_url}, expected: #{expected_url}"
137
+ end
138
+
139
+ ##
140
+ #
141
+ # Waits until web is loaded with expected title
142
+ #
143
+ # *Parameters:*
144
+ # * +expected_title+ - Page title that will be waited for
145
+ # * +time_out+ - Seconds that will be waiting for web-site to be loaded until raise error
146
+ #
147
+
148
+ def wait_for_title(expected_title, timeout=settings.timeout_small)
149
+ end_time = ::Time.now + timeout
150
+ until ::Time.now > end_time
151
+ operator = expected_title.is_a?(Regexp) ? :=~ : :==
152
+ return true if title.send(operator, expected_title).tap{|res| sleep 1 unless res}
40
153
  end
41
- rescue
42
- log.error "Current url: #{current_url}, expected: #{expected_url}"
154
+ log.error IncorrectPageError, "Current title: #{title}, expected: #{expected_title}"
43
155
  end
44
156
 
157
+ ##
158
+ #
159
+ # Reloads current page
160
+ #
161
+
45
162
  def reload
46
163
  log.info "Reload '#{current_url}'"
47
164
  visit current_url
48
165
  end
49
166
 
167
+ ##
168
+ #
169
+ # Returns current url
170
+ #
171
+ # *Returns:*
172
+ # * +string+ - Current url
173
+ #
174
+
50
175
  def self.current_url
51
176
  page.current_url
52
177
  end
53
178
 
54
- def self.text
55
- page.find('body').text
179
+ ##
180
+ #
181
+ # Returns Page title
182
+ #
183
+ # *Returns:*
184
+ # * +string+ - Page title
185
+ #
186
+
187
+ def title
188
+ page.title
56
189
  end
57
190
 
58
- private
59
- def initialize
60
- wait_for_url(self.class::URL_PATTERN)
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
61
201
  end
62
- end
202
+ end