howitzer 0.0.3 → 1.0.1

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