howitzer 2.0.3 → 2.1.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -2
  3. data/.travis.yml +3 -3
  4. data/CHANGELOG.md +27 -11
  5. data/Gemfile +1 -0
  6. data/ISSUE_TEMPLATE.md +16 -0
  7. data/README.md +2 -1
  8. data/Rakefile +18 -2
  9. data/bin/howitzer +7 -6
  10. data/features/cli_help.feature +1 -1
  11. data/features/cli_new.feature +51 -11
  12. data/features/cli_update.feature +44 -4
  13. data/features/cli_version.feature +1 -1
  14. data/features/step_definitions/common_steps.rb +5 -0
  15. data/generators/base_generator.rb +30 -16
  16. data/generators/config/config_generator.rb +13 -3
  17. data/generators/config/templates/boot.rb +1 -1
  18. data/generators/config/templates/capybara.rb +2 -128
  19. data/generators/config/templates/default.yml +20 -3
  20. data/generators/config/templates/drivers/browserstack.rb +19 -0
  21. data/generators/config/templates/drivers/crossbrowsertesting.rb +25 -0
  22. data/generators/config/templates/drivers/headless_chrome.rb +16 -0
  23. data/generators/config/templates/drivers/phantomjs.rb +20 -0
  24. data/generators/config/templates/drivers/poltergeist.rb +11 -0
  25. data/generators/config/templates/drivers/sauce.rb +21 -0
  26. data/generators/config/templates/drivers/selenium.rb +24 -0
  27. data/generators/config/templates/drivers/selenium_grid.rb +27 -0
  28. data/generators/config/templates/drivers/testingbot.rb +20 -0
  29. data/generators/config/templates/drivers/webkit.rb +6 -0
  30. data/generators/cucumber/cucumber_generator.rb +2 -2
  31. data/generators/cucumber/templates/cucumber.rake +0 -8
  32. data/generators/cucumber/templates/env.rb +1 -1
  33. data/generators/cucumber/templates/transformers.rb +11 -25
  34. data/generators/emails/emails_generator.rb +2 -2
  35. data/generators/emails/templates/example_email.rb +1 -1
  36. data/generators/prerequisites/prerequisites_generator.rb +3 -3
  37. data/generators/prerequisites/templates/base.rb +1 -1
  38. data/generators/prerequisites/templates/{factory_girl.rb → factory_bot.rb} +6 -6
  39. data/generators/prerequisites/templates/users.rb +1 -1
  40. data/generators/root/root_generator.rb +2 -2
  41. data/generators/root/templates/.rubocop.yml +2 -2
  42. data/generators/root/templates/Gemfile.erb +14 -18
  43. data/generators/rspec/rspec_generator.rb +2 -2
  44. data/generators/rspec/templates/spec_helper.rb +1 -1
  45. data/generators/tasks/tasks_generator.rb +2 -2
  46. data/generators/turnip/templates/spec_helper.rb +1 -1
  47. data/generators/turnip/turnip_generator.rb +2 -2
  48. data/generators/web/web_generator.rb +2 -2
  49. data/howitzer.gemspec +3 -1
  50. data/lib/howitzer/capybara_helpers.rb +25 -8
  51. data/lib/howitzer/gmail_api.rb +7 -0
  52. data/lib/howitzer/gmail_api/client.rb +22 -0
  53. data/lib/howitzer/mail_adapters/gmail.rb +93 -0
  54. data/lib/howitzer/mail_adapters/mailgun.rb +2 -2
  55. data/lib/howitzer/mail_adapters/mailtrap.rb +105 -0
  56. data/lib/howitzer/mailtrap_api.rb +7 -0
  57. data/lib/howitzer/mailtrap_api/client.rb +52 -0
  58. data/lib/howitzer/version.rb +1 -1
  59. data/lib/howitzer/web/page.rb +13 -1
  60. data/lib/howitzer/web/page_dsl.rb +1 -1
  61. data/spec/config/custom.yml +1 -1
  62. data/spec/spec_helper.rb +1 -0
  63. data/spec/unit/generators/base_generator_spec.rb +23 -12
  64. data/spec/unit/generators/config_generator_spec.rb +27 -6
  65. data/spec/unit/generators/cucumber_generator_spec.rb +8 -8
  66. data/spec/unit/generators/emails_generator_spec.rb +2 -2
  67. data/spec/unit/generators/prerequisites_generator_spec.rb +7 -7
  68. data/spec/unit/generators/root_generator_spec.rb +25 -22
  69. data/spec/unit/generators/rspec_generator_spec.rb +4 -4
  70. data/spec/unit/generators/tasks_generator_spec.rb +2 -2
  71. data/spec/unit/generators/turnip_generator_spec.rb +7 -7
  72. data/spec/unit/generators/web_generator_spec.rb +3 -3
  73. data/spec/unit/lib/capybara_helpers_spec.rb +2 -1
  74. data/spec/unit/lib/gmail_api/client_spec.rb +26 -0
  75. data/spec/unit/lib/mail_adapters/gmail_spec.rb +128 -0
  76. data/spec/unit/lib/mail_adapters/mailgun_spec.rb +13 -18
  77. data/spec/unit/lib/mail_adapters/mailtrap_spec.rb +130 -0
  78. data/spec/unit/lib/mailgun_api/client_spec.rb +31 -9
  79. data/spec/unit/lib/mailtrap_api/client_spec.rb +67 -0
  80. data/spec/unit/lib/web/page_spec.rb +30 -1
  81. metadata +63 -8
  82. data/features/support/transformers.rb +0 -3
  83. data/spec/support/mailgun_unit_client.rb +0 -68
@@ -20,14 +20,16 @@ Gem::Specification.new do |gem|
20
20
 
21
21
  gem.add_runtime_dependency 'activesupport', '~>5.0'
22
22
  gem.add_runtime_dependency 'capybara', ['>= 2.1', '< 3.0']
23
+ gem.add_runtime_dependency 'colorize'
23
24
  gem.add_runtime_dependency 'gli'
25
+ gem.add_runtime_dependency 'gmail'
24
26
  gem.add_runtime_dependency 'launchy'
25
27
  gem.add_runtime_dependency 'log4r', '~>1.1.10'
26
28
  gem.add_runtime_dependency 'nokogiri', '~> 1.6' if gem.platform.to_s =~ /mswin|mingw/
27
29
  gem.add_runtime_dependency 'rake'
28
30
  gem.add_runtime_dependency 'rspec', '~>3.2'
29
31
  gem.add_runtime_dependency 'rspec-wait'
30
- gem.add_runtime_dependency 'selenium-webdriver', '< 4.0'
32
+ gem.add_runtime_dependency 'selenium-webdriver', ['>= 3.4.1', '< 4.0']
31
33
  gem.add_runtime_dependency 'sexy_settings'
32
34
 
33
35
  gem.add_development_dependency('aruba')
@@ -5,12 +5,28 @@ module Howitzer
5
5
  # This module holds capybara helpers methods
6
6
  module CapybaraHelpers
7
7
  CHECK_YOUR_SETTINGS_MSG = 'Please check your settings'.freeze #:nodoc:
8
+ HOWITZER_KNOWN_BROWSERS = [ #:nodoc:
9
+ CLOUD_BROWSERS = [
10
+ SAUCE = :sauce,
11
+ TESTINGBOT = :testingbot,
12
+ BROWSERSTACK = :browserstack,
13
+ CROSSBROWSERTESTING = :crossbrowsertesting
14
+ ].freeze,
15
+ LOCAL_BROWSERS = [
16
+ HEADLESS_CHROME = :headless_chrome,
17
+ PHANTOMJS = :phantomjs,
18
+ POLTERGEIST = :poltergeist,
19
+ SELENIUM = :selenium,
20
+ SELENIUM_GRID = :selenium_grid,
21
+ WEBKIT = :webkit
22
+ ].freeze
23
+ ].freeze
8
24
 
9
25
  # @return [Boolean] true if current driver related with SauceLab,
10
26
  # Testingbot or Browserstack cloud service
11
27
 
12
28
  def cloud_driver?
13
- %i[sauce testingbot browserstack].include?(Howitzer.driver.to_sym)
29
+ CLOUD_BROWSERS.include?(Howitzer.driver.to_sym)
14
30
  end
15
31
 
16
32
  # @return [Boolean] whether or not current browser is
@@ -35,7 +51,7 @@ module Howitzer
35
51
  # @raise [SelBrowserNotSpecifiedError] if selenium driver and missing browser name
36
52
 
37
53
  def chrome_browser?
38
- browser? :chrome
54
+ browser?(:chrome) || Howitzer.driver == HEADLESS_CHROME.to_s
39
55
  end
40
56
 
41
57
  # @return [Boolean] whether or not current browser is Safari.
@@ -64,7 +80,7 @@ module Howitzer
64
80
 
65
81
  def update_cloud_job_status(json_data = {})
66
82
  case Howitzer.driver.to_sym
67
- when :sauce then update_sauce_job_status(json_data)
83
+ when SAUCE then update_sauce_job_status(json_data)
68
84
  else
69
85
  '[NOT IMPLEMENTED]'
70
86
  end
@@ -102,7 +118,8 @@ module Howitzer
102
118
 
103
119
  def cloud_driver(app, caps, url)
104
120
  http_client = ::Selenium::WebDriver::Remote::Http::Default.new
105
- http_client.timeout = Howitzer.cloud_http_idle_timeout
121
+ http_client.read_timeout = Howitzer.cloud_http_idle_timeout
122
+ http_client.open_timeout = Howitzer.cloud_http_idle_timeout
106
123
 
107
124
  options = {
108
125
  url: url,
@@ -121,7 +138,7 @@ module Howitzer
121
138
 
122
139
  def cloud_resource_path(kind)
123
140
  case Howitzer.driver.to_sym
124
- when :sauce then sauce_resource_path(kind)
141
+ when SAUCE then sauce_resource_path(kind)
125
142
  else
126
143
  '[NOT IMPLEMENTED]'
127
144
  end
@@ -136,7 +153,7 @@ module Howitzer
136
153
 
137
154
  def cloud_browser?(*browser_aliases)
138
155
  unless Howitzer.cloud_browser_name.nil?
139
- return browser_aliases.include?(Howitzer.cloud_browser_name.to_s.to_sym)
156
+ return browser_aliases.include?(Howitzer.cloud_browser_name.to_s.downcase.to_sym)
140
157
  end
141
158
  raise Howitzer::CloudBrowserNotSpecifiedError, CHECK_YOUR_SETTINGS_MSG
142
159
  end
@@ -149,11 +166,11 @@ module Howitzer
149
166
  end
150
167
 
151
168
  def selenium_driver?
152
- Howitzer.driver.to_sym == :selenium
169
+ Howitzer.driver.to_sym == SELENIUM
153
170
  end
154
171
 
155
172
  def selenium_grid_driver?
156
- Howitzer.driver.to_sym == :selenium_grid
173
+ Howitzer.driver.to_sym == SELENIUM_GRID
157
174
  end
158
175
 
159
176
  def prefix_name
@@ -0,0 +1,7 @@
1
+ module Howitzer
2
+ # This module holds simple implementation of gmail api client
3
+ module GmailApi
4
+ end
5
+ end
6
+
7
+ require 'howitzer/gmail_api/client'
@@ -0,0 +1,22 @@
1
+ require 'gmail'
2
+
3
+ module Howitzer
4
+ module GmailApi
5
+ # A GmailApi::Client object is used to communicate with the Gmail API.
6
+ class Client
7
+ def initialize
8
+ @client = Gmail.connect(Howitzer.gmail_login, Howitzer.gmail_password)
9
+ end
10
+
11
+ # Finds message according to given parameters
12
+ #
13
+ # @param recipient [String] this is recipient mail address for message filtering
14
+ # @param subject [String] this is subject of the message to filter particular message
15
+ # @return [Gmail::Message] gmail message object
16
+
17
+ def find_message(recipient, subject)
18
+ @client.inbox.emails(to: recipient, subject: subject).last
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,93 @@
1
+ require 'howitzer/exceptions'
2
+ require 'howitzer/mail_adapters/abstract'
3
+ require 'howitzer/gmail_api/client'
4
+
5
+ module Howitzer
6
+ module MailAdapters
7
+ # This class represents Gmail mail adapter
8
+ class Gmail < Abstract
9
+ # Finds an email in storage
10
+ # @param recipient [String] an email
11
+ # @param subject [String]
12
+ # @param wait [Integer] how much time is required to wait an email
13
+ # @raise [EmailNotFoundError] if blank message
14
+
15
+ def self.find(recipient, subject, wait:)
16
+ message = {}
17
+ retryable(find_retry_params(wait)) { message = get_message(recipient, subject) }
18
+ return new(message) if message.present?
19
+ raise Howitzer::EmailNotFoundError,
20
+ "Message with subject '#{subject}' for recipient '#{recipient}' was not found."
21
+ end
22
+
23
+ # @return [String] plain text body of the email message
24
+
25
+ def plain_text_body
26
+ message.body.to_s
27
+ end
28
+
29
+ # @return [String] html body of the email message
30
+
31
+ def html_body
32
+ message.html_part.to_s
33
+ end
34
+
35
+ # @return [String] stripped text
36
+
37
+ def text
38
+ message.text_part.to_s
39
+ end
40
+
41
+ # @return [String] an email address specified in `From` field
42
+
43
+ def mail_from
44
+ "#{message.from[0]['mailbox']}@#{message.from[0]['host']}"
45
+ end
46
+
47
+ # @return [Array] recipient emails
48
+
49
+ def recipients
50
+ message.to.inject([]) { |ar, to| ar << "#{to['mailbox']}@#{to['host']}" }
51
+ end
52
+
53
+ # @return [String] when email was received
54
+
55
+ def received_time
56
+ Time.parse(message.date).strftime('%F %T')
57
+ end
58
+
59
+ # @return [Array] attachments
60
+
61
+ def mime_part
62
+ message.attachments
63
+ end
64
+
65
+ # @raise [NoAttachmentsError] if no attachments present
66
+ # @return [Array] attachments
67
+
68
+ def mime_part!
69
+ files = mime_part
70
+ return files if files.present?
71
+ raise Howitzer::NoAttachmentsError, 'No attachments were found.'
72
+ end
73
+
74
+ def self.get_message(recipient, subject)
75
+ message = Howitzer::GmailApi::Client.new.find_message(recipient, subject)
76
+ raise Howitzer::EmailNotFoundError if message.blank?
77
+ message
78
+ end
79
+ private_class_method :get_message
80
+
81
+ def self.find_retry_params(wait)
82
+ {
83
+ timeout: wait,
84
+ sleep: Howitzer.mail_sleep_time,
85
+ silent: true,
86
+ logger: Howitzer::Log,
87
+ on: Howitzer::EmailNotFoundError
88
+ }
89
+ end
90
+ private_class_method :find_retry_params
91
+ end
92
+ end
93
+ end
@@ -75,7 +75,7 @@ module Howitzer
75
75
  def mime_part!
76
76
  files = mime_part
77
77
  return files if files.present?
78
- raise Howitzer::NoAttachmentsError, 'No attachments where found.'
78
+ raise Howitzer::NoAttachmentsError, 'No attachments were found.'
79
79
  end
80
80
 
81
81
  def self.events
@@ -95,7 +95,7 @@ module Howitzer
95
95
  def self.find_retry_params(wait)
96
96
  {
97
97
  timeout: wait || Howitzer.try(:mailgun_idle_timeout),
98
- sleep: Howitzer.mailgun_sleep_time,
98
+ sleep: Howitzer.mail_sleep_time || Howitzer.mailgun_sleep_time,
99
99
  silent: true,
100
100
  logger: Howitzer::Log,
101
101
  on: Howitzer::EmailNotFoundError
@@ -0,0 +1,105 @@
1
+ require 'howitzer/exceptions'
2
+ require 'howitzer/mail_adapters/abstract'
3
+ require 'howitzer/mailtrap_api/client'
4
+
5
+ module Howitzer
6
+ module MailAdapters
7
+ # This class represents mailtrap mail adapter
8
+ class Mailtrap < Abstract
9
+ # Finds an email in storage
10
+ # @param recipient [String] an email
11
+ # @param subject [String]
12
+ # @param wait [Integer] how much time is required to wait an email
13
+ # @raise [EmailNotFoundError] if blank message
14
+
15
+ def self.find(recipient, subject, wait:)
16
+ message = {}
17
+ retryable(find_retry_params(wait)) { message = retrieve_message(recipient, subject) }
18
+ return new(message) if message.present?
19
+ raise Howitzer::EmailNotFoundError,
20
+ "Message with subject '#{subject}' for recipient '#{recipient}' was not found."
21
+ end
22
+
23
+ # @return [String] plain text body of the email message
24
+
25
+ def plain_text_body
26
+ message['text_body']
27
+ end
28
+
29
+ # @return [String] html body of the email message
30
+
31
+ def html_body
32
+ message['html_body']
33
+ end
34
+
35
+ # @return [String] stripped text
36
+
37
+ def text
38
+ message['text_body']
39
+ end
40
+
41
+ # @return [String] an email address specified in `From` field
42
+
43
+ def mail_from
44
+ message['from_email']
45
+ end
46
+
47
+ # @return [String] recipient emails separated with `, `
48
+
49
+ def recipients
50
+ message['to_email'].split ', '
51
+ end
52
+
53
+ # @return [String] when email was received
54
+
55
+ def received_time
56
+ Time.parse(message['created_at']).to_s
57
+ end
58
+
59
+ # @return [String] a real sender email address
60
+
61
+ def sender_email
62
+ message['from_email']
63
+ end
64
+
65
+ # @return [Array] attachments
66
+
67
+ def mime_part
68
+ retrieve_attachments(message)
69
+ end
70
+
71
+ # @raise [NoAttachmentsError] if no attachments present
72
+ # @return [Array] attachments
73
+
74
+ def mime_part!
75
+ files = mime_part
76
+ return files if files.present?
77
+ raise Howitzer::NoAttachmentsError, 'No attachments were found.'
78
+ end
79
+
80
+ def self.find_retry_params(wait)
81
+ {
82
+ timeout: wait,
83
+ sleep: Howitzer.mail_sleep_time,
84
+ silent: true,
85
+ logger: Howitzer::Log,
86
+ on: Howitzer::EmailNotFoundError
87
+ }
88
+ end
89
+ private_class_method :find_retry_params
90
+
91
+ def self.retrieve_message(recipient, subject)
92
+ message = Howitzer::MailtrapApi::Client.new.find_message(recipient, subject)
93
+ raise Howitzer::EmailNotFoundError, 'Message not received yet, retry...' unless message
94
+ message
95
+ end
96
+ private_class_method :retrieve_message
97
+
98
+ private
99
+
100
+ def retrieve_attachments(message)
101
+ Howitzer::MailtrapApi::Client.new.find_attachments(message)
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,7 @@
1
+ module Howitzer
2
+ # This module holds simple implementation of mailtrap api client
3
+ module MailtrapApi
4
+ end
5
+ end
6
+
7
+ require 'howitzer/mailtrap_api/client'
@@ -0,0 +1,52 @@
1
+ require 'json'
2
+ require 'rest-client'
3
+
4
+ module Howitzer
5
+ module MailtrapApi
6
+ # A Mailtrap::Client object is used to communicate with the Mailtrap API.
7
+ class Client
8
+ BASE_URL = "https://mailtrap.io/api/v1/inboxes/#{Howitzer.mailtrap_inbox_id}".freeze #:nodoc:
9
+
10
+ def initialize
11
+ @api_token = Howitzer.mailtrap_api_token
12
+ end
13
+
14
+ # Finds message according to given parameters
15
+ #
16
+ # @param recipient [String] this is recipient mail address for message filtering
17
+ # @param subject [String] this is subject of the message to filter particular message
18
+ # @return [Hash] json message parsed to ruby hash
19
+
20
+ def find_message(recipient, subject)
21
+ recipient = recipient.gsub('+', '%2B')
22
+ messages = filter_by_subject(messages(recipient), subject)
23
+ latest_message(messages)
24
+ end
25
+
26
+ # Finds attachments for message
27
+ #
28
+ # @param message [Hash] which attachments should be extracted
29
+ # @return [Array] returns array of attachments
30
+
31
+ def find_attachments(message)
32
+ JSON.parse(RestClient.get("#{BASE_URL}/messages/#{message['id']}/attachments", 'Api-Token' => @api_token))
33
+ end
34
+
35
+ private
36
+
37
+ def messages(recipient)
38
+ JSON.parse(RestClient.get("#{BASE_URL}/messages?search=#{recipient}", 'Api-Token' => @api_token))
39
+ end
40
+
41
+ def latest_message(messages)
42
+ messages[0]
43
+ end
44
+
45
+ def filter_by_subject(messages, subject)
46
+ result_messages = []
47
+ messages.each { |msg| result_messages << msg if msg['subject'] == subject }
48
+ result_messages
49
+ end
50
+ end
51
+ end
52
+ end
@@ -1,4 +1,4 @@
1
1
  # This module holds howitzer version
2
2
  module Howitzer
3
- VERSION = '2.0.3'.freeze #:nodoc:
3
+ VERSION = '2.1.0'.freeze #:nodoc:
4
4
  end
@@ -39,6 +39,7 @@ module Howitzer
39
39
 
40
40
  def self.open(validate: true, url_processor: nil, **params)
41
41
  url = expanded_url(params, url_processor)
42
+ modify_user_agent if Howitzer.user_agent.present?
42
43
  Howitzer::Log.info "Open #{name} page by '#{url}' url"
43
44
  retryable(tries: 2, logger: Howitzer::Log, trace: true, on: Exception) do |retries|
44
45
  Howitzer::Log.info 'Retry...' unless retries.zero?
@@ -147,13 +148,24 @@ module Howitzer
147
148
  "Current page matches more that one page class (#{page_list.join(', ')}).\n" \
148
149
  "\tCurrent url: #{current_url}\n\tCurrent title: #{instance.title}"
149
150
  end
151
+
152
+ def modify_user_agent
153
+ driver = Capybara.current_session.driver
154
+ case Howitzer.driver.to_sym
155
+ when CapybaraHelpers::POLTERGEIST
156
+ driver.add_headers('User-Agent' => Howitzer.user_agent)
157
+ when CapybaraHelpers::WEBKIT
158
+ driver.header('User-Agent', Howitzer.user_agent)
159
+ end
160
+ end
150
161
  end
151
162
 
152
163
  site Howitzer.app_uri.site
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