howitzer 2.0.3 → 2.1.0

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