howitzer 2.1.1 → 2.2.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 (68) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +27 -18
  3. data/.travis.yml +4 -3
  4. data/CHANGELOG.md +15 -1
  5. data/README.md +6 -3
  6. data/Rakefile +1 -1
  7. data/features/cli_new.feature +12 -8
  8. data/features/cli_update.feature +14 -9
  9. data/features/step_definitions/common_steps.rb +3 -3
  10. data/generators/base_generator.rb +1 -1
  11. data/generators/config/config_generator.rb +2 -1
  12. data/generators/config/templates/boot.rb +1 -1
  13. data/generators/config/templates/capybara.rb +2 -1
  14. data/generators/config/templates/default.yml +22 -4
  15. data/generators/config/templates/drivers/appium.rb +25 -0
  16. data/generators/config/templates/drivers/headless_firefox.rb +23 -0
  17. data/generators/cucumber/templates/cuke_sniffer.rake +2 -2
  18. data/generators/cucumber/templates/env.rb +8 -0
  19. data/generators/prerequisites/templates/factory_bot.rb +1 -0
  20. data/generators/root/root_generator.rb +1 -1
  21. data/generators/root/templates/{.rubocop.yml → .rubocop.yml.erb} +34 -13
  22. data/generators/root/templates/Gemfile.erb +4 -0
  23. data/generators/rspec/templates/spec_helper.rb +1 -0
  24. data/generators/turnip/templates/spec_helper.rb +1 -0
  25. data/howitzer.gemspec +3 -3
  26. data/lib/howitzer.rb +40 -0
  27. data/lib/howitzer/cache.rb +19 -18
  28. data/lib/howitzer/capybara_helpers.rb +13 -5
  29. data/lib/howitzer/email.rb +1 -0
  30. data/lib/howitzer/mail_adapters/gmail.rb +3 -0
  31. data/lib/howitzer/mail_adapters/mailgun.rb +2 -0
  32. data/lib/howitzer/mail_adapters/mailtrap.rb +3 -0
  33. data/lib/howitzer/mailgun_api/connector.rb +1 -0
  34. data/lib/howitzer/meta.rb +11 -0
  35. data/lib/howitzer/meta/actions.rb +38 -0
  36. data/lib/howitzer/meta/element.rb +38 -0
  37. data/lib/howitzer/meta/entry.rb +62 -0
  38. data/lib/howitzer/meta/iframe.rb +41 -0
  39. data/lib/howitzer/meta/section.rb +30 -0
  40. data/lib/howitzer/version.rb +1 -1
  41. data/lib/howitzer/web/capybara_context_holder.rb +1 -0
  42. data/lib/howitzer/web/capybara_methods_proxy.rb +4 -1
  43. data/lib/howitzer/web/element_dsl.rb +1 -0
  44. data/lib/howitzer/web/iframe_dsl.rb +2 -0
  45. data/lib/howitzer/web/page.rb +10 -0
  46. data/lib/howitzer/web/page_dsl.rb +3 -0
  47. data/lib/howitzer/web/page_validator.rb +2 -0
  48. data/lib/howitzer/web/section.rb +8 -0
  49. data/lib/howitzer/web/section_dsl.rb +1 -0
  50. data/spec/support/shared_examples/capybara_context_holder.rb +1 -1
  51. data/spec/support/shared_examples/meta_highlight_xpath.rb +41 -0
  52. data/spec/unit/generators/config_generator_spec.rb +4 -2
  53. data/spec/unit/generators/root_generator_spec.rb +32 -21
  54. data/spec/unit/generators/templates/cucumber_spec.rb +97 -0
  55. data/spec/unit/generators/templates/rspec_spec.rb +88 -0
  56. data/spec/unit/generators/templates/turnip_spec.rb +98 -0
  57. data/spec/unit/lib/capybara_helpers_spec.rb +37 -4
  58. data/spec/unit/lib/howitzer_spec.rb +23 -0
  59. data/spec/unit/lib/meta/element_spec.rb +59 -0
  60. data/spec/unit/lib/meta/entry_spec.rb +77 -0
  61. data/spec/unit/lib/meta/iframe_spec.rb +66 -0
  62. data/spec/unit/lib/meta/section_spec.rb +43 -0
  63. data/spec/unit/lib/utils/string_extensions_spec.rb +1 -1
  64. data/spec/unit/lib/web/element_dsl_spec.rb +10 -1
  65. data/spec/unit/lib/web/page_spec.rb +7 -0
  66. data/spec/unit/lib/web/section_spec.rb +7 -0
  67. metadata +31 -15
  68. data/generators/config/templates/drivers/phantomjs.rb +0 -19
@@ -0,0 +1,25 @@
1
+ CapybaraHelpers.load_driver_gem!(:appium, 'appium_capybara', 'appium_capybara')
2
+
3
+ Capybara.register_driver(:appium) do |app|
4
+ caps = {}
5
+ caps['deviceName'] = Howitzer.appium_device_name
6
+ caps['deviceOrientation'] = Howitzer.appium_device_orientation
7
+ caps['platformVersion'] = Howitzer.appium_platform_version
8
+ caps['platformName'] = Howitzer.appium_platform_name
9
+ caps['browserName'] = Howitzer.appium_browser_name
10
+
11
+ url = Howitzer.appium_url
12
+
13
+ appium_lib_options = {
14
+ server_url: url
15
+ }
16
+ all_options = {
17
+ appium_lib: appium_lib_options,
18
+ caps: caps
19
+ }
20
+ Appium::Capybara::Driver.new app, all_options
21
+ end
22
+
23
+ Capybara::Screenshot.class_eval do
24
+ register_driver :appium, &registered_drivers[:selenium]
25
+ end
@@ -0,0 +1,23 @@
1
+ # :headless_firefox driver
2
+
3
+ Capybara.register_driver :headless_firefox do |app|
4
+ startup_flags = ['--headless']
5
+ startup_flags.concat(Howitzer.headless_firefox_flags.split(/\s*,\s*/)) if Howitzer.headless_firefox_flags
6
+ ff_profile = Selenium::WebDriver::Firefox::Profile.new.tap do |profile|
7
+ profile['network.http.phishy-userpass-length'] = 255
8
+ profile['browser.safebrowsing.malware.enabled'] = false
9
+ profile['network.automatic-ntlm-auth.allow-non-fqdn'] = true
10
+ profile['network.ntlm.send-lm-response'] = true
11
+ profile['network.automatic-ntlm-auth.trusted-uris'] = Howitzer.app_host
12
+ profile['general.useragent.override'] = Howitzer.user_agent if Howitzer.user_agent.present?
13
+ end
14
+ options = Selenium::WebDriver::Firefox::Options.new(args: startup_flags, profile: ff_profile)
15
+ params = { browser: :firefox, options: options }
16
+ Capybara::Selenium::Driver.new app, params
17
+ end
18
+
19
+ Capybara.javascript_driver = :headless_firefox
20
+
21
+ Capybara::Screenshot.class_eval do
22
+ register_driver :headless_firefox, &registered_drivers[:selenium]
23
+ end
@@ -1,11 +1,11 @@
1
1
  require 'cuke_sniffer'
2
2
 
3
3
  def path_to_features
4
- @_path_to_features ||= File.expand_path(File.join(__dir__, '..', 'features'))
4
+ @path_to_features ||= File.expand_path(File.join(__dir__, '..', 'features'))
5
5
  end
6
6
 
7
7
  def cuke_sniffer
8
- @_cuke_sniffer ||= CukeSniffer::CLI.new(
8
+ @cuke_sniffer ||= CukeSniffer::CLI.new(
9
9
  features_location: path_to_features,
10
10
  step_definitions_location: File.join(path_to_features, 'step_definitions'),
11
11
  hooks_location: File.join(path_to_features, 'support'),
@@ -10,6 +10,14 @@ RSpec.configure do |config|
10
10
  config.disable_monkey_patching!
11
11
  end
12
12
 
13
+ AfterConfiguration do |config|
14
+ if Howitzer.test_order.present?
15
+ order, seed = Howitzer.test_order.split(':')
16
+ config.instance_variable_get(:@options)[:order] = order
17
+ config.instance_variable_get(:@options)[:seed] = seed
18
+ end
19
+ end
20
+
13
21
  FileUtils.mkdir_p(Howitzer.log_dir)
14
22
 
15
23
  Howitzer::Log.settings_as_formatted_text
@@ -15,6 +15,7 @@ module FactoryBot
15
15
  def self.given_by_number(factory, num)
16
16
  data = Howitzer::Cache.extract(factory, num.to_i)
17
17
  return data if data.present?
18
+
18
19
  Howitzer::Cache.store(factory, num.to_i, build(factory))
19
20
  end
20
21
  end
@@ -7,11 +7,11 @@ module Howitzer
7
7
  { files:
8
8
  [
9
9
  { source: '.gitignore', destination: '.gitignore' },
10
- { source: '.rubocop.yml', destination: '.rubocop.yml' },
11
10
  { source: 'Rakefile', destination: 'Rakefile' }
12
11
  ],
13
12
  templates:
14
13
  [
14
+ { source: '.rubocop.yml.erb', destination: '.rubocop.yml' },
15
15
  { source: 'Gemfile.erb', destination: 'Gemfile' }
16
16
  ] }
17
17
  end
@@ -2,34 +2,55 @@
2
2
  # To see all cops used see here: https://github.com/bbatsov/rubocop/blob/master/config/enabled.yml
3
3
 
4
4
  AllCops:
5
- TargetRubyVersion: 2.3
6
- Include:
7
- - Rakefile
8
- - Gemfile
9
-
10
- LineLength:
11
- Max: 120
5
+ DisplayCopNames: true
6
+ TargetRubyVersion: 2.4
12
7
 
13
8
  Layout/CaseIndentation:
14
9
  Enabled: false
10
+ <% if cucumber -%>
15
11
 
16
- Style/EmptyElse:
12
+ Lint/AmbiguousBlockAssociation:
17
13
  Enabled: false
14
+ <% end -%>
18
15
 
19
16
  Lint/AmbiguousRegexpLiteral:
20
17
  Enabled: false
21
18
 
22
- Lint/AmbiguousBlockAssociation:
19
+ Metrics/BlockLength:
23
20
  Enabled: false
24
21
 
22
+ Metrics/LineLength:
23
+ Max: 120
24
+
25
+ Metrics/ModuleLength:
26
+ Max: 150
27
+
25
28
  Style/CaseEquality:
26
29
  Enabled: false
27
30
 
28
- Documentation:
31
+ Style/Documentation:
29
32
  Enabled: false
30
33
 
31
- Style/FrozenStringLiteralComment:
34
+ Style/EmptyElse:
32
35
  Enabled: false
33
36
 
34
- Metrics/ModuleLength:
35
- Max: 150
37
+ Style/FrozenStringLiteralComment:
38
+ Enabled: false
39
+ <% if turnip -%>
40
+
41
+ Style/MixinGrouping:
42
+ EnforcedStyle: separated
43
+ Exclude:
44
+ - '**/*_steps.rb'
45
+ <% end -%>
46
+ <% if cucumber || turnip -%>
47
+
48
+ Style/SymbolProc:
49
+ Exclude:
50
+ <% if cucumber -%>
51
+ - 'features/step_definitions/**/*.rb'
52
+ <%- end -%>
53
+ <% if turnip -%>
54
+ - 'spec/steps/**/*.rb'
55
+ <%- end -%>
56
+ <% end -%>
@@ -14,6 +14,10 @@ gem 'rubocop'
14
14
  <%= "gem 'turnip'\n" if turnip -%>
15
15
  <%= "gem 'syntax'\n" if cucumber -%>
16
16
 
17
+ # Uncomment it if you are going to use 'appium' driver. Appium and Android SDK should be installed.
18
+ # See https://appium.io/docs/en/about-appium/getting-started/
19
+ # gem 'appium_capybara'
20
+
17
21
  # Uncomment it if you are going to use 'webkit' driver. QT library should be installed.
18
22
  # See https://github.com/thoughtbot/capybara-webkit/wiki/Installing-Qt-and-compiling-capybara-webkit
19
23
  #
@@ -15,6 +15,7 @@ RSpec.configure do |config|
15
15
  config.disable_monkey_patching!
16
16
  config.color = true
17
17
  config.wait_timeout = Howitzer.rspec_wait_timeout
18
+ config.order = Howitzer.test_order.presence || :defined
18
19
 
19
20
  config.before(:each) do
20
21
  scenario_name =
@@ -14,6 +14,7 @@ RSpec.configure do |config|
14
14
  config.disable_monkey_patching!
15
15
  config.color = true
16
16
  config.wait_timeout = Howitzer.rspec_wait_timeout
17
+ config.order = Howitzer.test_order.presence || :defined
17
18
 
18
19
  config.before(:each) do
19
20
  scenario_name =
@@ -1,4 +1,4 @@
1
- require File.expand_path('../lib/howitzer/version', __FILE__)
1
+ require File.expand_path('lib/howitzer/version', __dir__)
2
2
 
3
3
  Gem::Specification.new do |gem|
4
4
  gem.author = 'Roman Parashchenko'
@@ -16,10 +16,10 @@ Gem::Specification.new do |gem|
16
16
  gem.name = 'howitzer'
17
17
  gem.require_paths = ['lib']
18
18
  gem.version = Howitzer::VERSION
19
- gem.required_ruby_version = '>= 2.2.2'
19
+ gem.required_ruby_version = '>= 2.3'
20
20
 
21
21
  gem.add_runtime_dependency 'activesupport', '~>5.0'
22
- gem.add_runtime_dependency 'capybara', ['>= 2.1', '< 3.0']
22
+ gem.add_runtime_dependency 'capybara', '< 4.0'
23
23
  gem.add_runtime_dependency 'colorize'
24
24
  gem.add_runtime_dependency 'gli'
25
25
  gem.add_runtime_dependency 'gmail'
@@ -29,6 +29,45 @@ module Howitzer
29
29
  ::SexySettings::Base.instance.all['mailgun_idle_timeout']
30
30
  end
31
31
 
32
+ # @return active session name
33
+
34
+ def session_name
35
+ @session_name ||= 'default'
36
+ end
37
+
38
+ # Sets new session name
39
+ #
40
+ # @param name [String] string identifier for the session
41
+ #
42
+ # @example Executing code in another browser
43
+ # Howitzer.session_name = 'browser2'
44
+ # LoginPage.on do
45
+ # expect(title).to eq('Login Page')
46
+ # end
47
+ #
48
+ # # Switching back to main browser
49
+ # Howitzer.session_name = 'default'
50
+
51
+ def session_name=(name)
52
+ @session_name = name
53
+ Capybara.session_name = @session_name
54
+ end
55
+
56
+ # Yield a block using a specific session name
57
+ #
58
+ # @param name [String] string identifier for the session
59
+ #
60
+ # @example Opening page in another browser
61
+ # Howitzer.using_session('browser2') do
62
+ # LoginPage.on do
63
+ # expect(title).to eq('Login Page')
64
+ # end
65
+ # end
66
+
67
+ def using_session(name)
68
+ Capybara.using_session(name) { yield }
69
+ end
70
+
32
71
  attr_accessor :current_rake_task
33
72
  end
34
73
 
@@ -60,6 +99,7 @@ module Howitzer
60
99
 
61
100
  def self.sexy_setting!(name)
62
101
  return Howitzer.public_send(name) if Howitzer.respond_to?(name)
102
+
63
103
  raise UndefinedSexySettingError,
64
104
  "Undefined '#{name}' setting. Please add the setting to config/default.yml:\n #{name}: some_value\n"
65
105
  end
@@ -12,35 +12,35 @@ module Howitzer
12
12
 
13
13
  # Saves data into memory. Marking by a namespace and a key
14
14
  #
15
- # @param ns [String] a namespace
15
+ # @param namespace [String] a namespace
16
16
  # @param key [String] a key that should be uniq within the namespace
17
17
  # @param value [Object] everything you want to store in Memory
18
18
  # @raise [NoDataError] if the namespace missing
19
19
 
20
- def store(ns, key, value)
21
- check_ns(ns)
22
- @data[ns][key] = value
20
+ def store(namespace, key, value)
21
+ check_ns(namespace)
22
+ @data[namespace][key] = value
23
23
  end
24
24
 
25
25
  # Gets data from memory. Can get all namespace or single data value in namespace using key
26
26
  #
27
- # @param ns [String] a namespace
27
+ # @param namespace [String] a namespace
28
28
  # @param key [String] key that isn't necessary required
29
29
  # @return [Object, Hash] all data from the namespace if the key is ommited, otherwise returs
30
30
  # all data for the namespace
31
31
  # @raise [NoDataError] if the namespace missing
32
32
 
33
- def extract(ns, key = nil)
34
- check_ns(ns)
35
- key ? @data[ns][key] : @data[ns]
33
+ def extract(namespace, key = nil)
34
+ check_ns(namespace)
35
+ key ? @data[namespace][key] : @data[namespace]
36
36
  end
37
37
 
38
38
  # Deletes all data from a namespace
39
39
  #
40
- # @param ns [String] a namespace
40
+ # @param namespace [String] a namespace
41
41
 
42
- def clear_ns(ns)
43
- init_ns(ns)
42
+ def clear_ns(namespace)
43
+ init_ns(namespace)
44
44
  end
45
45
 
46
46
  # Deletes all namespaces with data
@@ -53,17 +53,18 @@ module Howitzer
53
53
 
54
54
  private
55
55
 
56
- def check_ns(ns)
57
- raise Howitzer::NoDataError, 'Data storage namespace can not be empty' unless ns
58
- init_ns(ns) if ns_absent?(ns)
56
+ def check_ns(namespace)
57
+ raise Howitzer::NoDataError, 'Data storage namespace can not be empty' unless namespace
58
+
59
+ init_ns(namespace) if ns_absent?(namespace)
59
60
  end
60
61
 
61
- def ns_absent?(ns)
62
- !@data.key?(ns)
62
+ def ns_absent?(namespace)
63
+ !@data.key?(namespace)
63
64
  end
64
65
 
65
- def init_ns(ns)
66
- @data[ns] = {}
66
+ def init_ns(namespace)
67
+ @data[namespace] = {}
67
68
  end
68
69
  end
69
70
  end
@@ -14,7 +14,7 @@ module Howitzer
14
14
  ].freeze,
15
15
  LOCAL_BROWSERS = [
16
16
  HEADLESS_CHROME = :headless_chrome,
17
- PHANTOMJS = :phantomjs,
17
+ HEADLESS_FIREFOX = :headless_firefox,
18
18
  POLTERGEIST = :poltergeist,
19
19
  SELENIUM = :selenium,
20
20
  SELENIUM_GRID = :selenium_grid,
@@ -51,7 +51,7 @@ module Howitzer
51
51
  # @raise [SelBrowserNotSpecifiedError] if selenium driver and missing browser name
52
52
 
53
53
  def chrome_browser?
54
- browser?(:chrome) || Howitzer.driver == HEADLESS_CHROME.to_s
54
+ browser? :chrome
55
55
  end
56
56
 
57
57
  # @return [Boolean] whether or not current browser is Safari.
@@ -155,13 +155,13 @@ module Howitzer
155
155
  unless Howitzer.cloud_browser_name.nil?
156
156
  return browser_aliases.include?(Howitzer.cloud_browser_name.to_s.downcase.to_sym)
157
157
  end
158
+
158
159
  raise Howitzer::CloudBrowserNotSpecifiedError, CHECK_YOUR_SETTINGS_MSG
159
160
  end
160
161
 
161
162
  def selenium_browser?(*browser_aliases)
162
- unless Howitzer.selenium_browser.nil?
163
- return browser_aliases.include?(Howitzer.selenium_browser.to_s.to_sym)
164
- end
163
+ return browser_aliases.include?(Howitzer.selenium_browser.to_s.to_sym) unless Howitzer.selenium_browser.nil?
164
+
165
165
  raise Howitzer::SelBrowserNotSpecifiedError, CHECK_YOUR_SETTINGS_MSG
166
166
  end
167
167
 
@@ -169,6 +169,14 @@ module Howitzer
169
169
  Howitzer.driver.to_sym == SELENIUM
170
170
  end
171
171
 
172
+ def headless_chrome_driver?
173
+ Howitzer.driver.to_sym == HEADLESS_CHROME
174
+ end
175
+
176
+ def headless_firefox_driver?
177
+ Howitzer.driver.to_sym == HEADLESS_FIREFOX
178
+ end
179
+
172
180
  def selenium_grid_driver?
173
181
  Howitzer.driver.to_sym == SELENIUM_GRID
174
182
  end
@@ -12,6 +12,7 @@ module Howitzer
12
12
 
13
13
  def self.adapter
14
14
  return @adapter if @adapter
15
+
15
16
  self.adapter = Howitzer.mail_adapter.to_sym
16
17
  @adapter
17
18
  end
@@ -16,6 +16,7 @@ module Howitzer
16
16
  message = {}
17
17
  retryable(find_retry_params(wait)) { message = get_message(recipient, subject) }
18
18
  return new(message) if message.present?
19
+
19
20
  raise Howitzer::EmailNotFoundError,
20
21
  "Message with subject '#{subject}' for recipient '#{recipient}' was not found."
21
22
  end
@@ -68,12 +69,14 @@ module Howitzer
68
69
  def mime_part!
69
70
  files = mime_part
70
71
  return files if files.present?
72
+
71
73
  raise Howitzer::NoAttachmentsError, 'No attachments were found.'
72
74
  end
73
75
 
74
76
  def self.get_message(recipient, subject)
75
77
  message = Howitzer::GmailApi::Client.new.find_message(recipient, subject)
76
78
  raise Howitzer::EmailNotFoundError if message.blank?
79
+
77
80
  message
78
81
  end
79
82
  private_class_method :get_message
@@ -17,6 +17,7 @@ module Howitzer
17
17
  message = {}
18
18
  retryable(find_retry_params(wait)) { message = retrieve_message(recipient, subject) }
19
19
  return new(message) if message.present?
20
+
20
21
  raise Howitzer::EmailNotFoundError,
21
22
  "Message with subject '#{subject}' for recipient '#{recipient}' was not found."
22
23
  end
@@ -75,6 +76,7 @@ module Howitzer
75
76
  def mime_part!
76
77
  files = mime_part
77
78
  return files if files.present?
79
+
78
80
  raise Howitzer::NoAttachmentsError, 'No attachments were found.'
79
81
  end
80
82
 
@@ -16,6 +16,7 @@ module Howitzer
16
16
  message = {}
17
17
  retryable(find_retry_params(wait)) { message = retrieve_message(recipient, subject) }
18
18
  return new(message) if message.present?
19
+
19
20
  raise Howitzer::EmailNotFoundError,
20
21
  "Message with subject '#{subject}' for recipient '#{recipient}' was not found."
21
22
  end
@@ -74,6 +75,7 @@ module Howitzer
74
75
  def mime_part!
75
76
  files = mime_part
76
77
  return files if files.present?
78
+
77
79
  raise Howitzer::NoAttachmentsError, 'No attachments were found.'
78
80
  end
79
81
 
@@ -91,6 +93,7 @@ module Howitzer
91
93
  def self.retrieve_message(recipient, subject)
92
94
  message = Howitzer::MailtrapApi::Client.new.find_message(recipient, subject)
93
95
  raise Howitzer::EmailNotFoundError, 'Message not received yet, retry...' unless message
96
+
94
97
  message
95
98
  end
96
99
  private_class_method :retrieve_message