howitzer 2.1.1 → 2.2.0

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