howitzer 2.1.1 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.rubocop.yml +27 -18
- data/.travis.yml +4 -3
- data/CHANGELOG.md +15 -1
- data/README.md +6 -3
- data/Rakefile +1 -1
- data/features/cli_new.feature +12 -8
- data/features/cli_update.feature +14 -9
- data/features/step_definitions/common_steps.rb +3 -3
- data/generators/base_generator.rb +1 -1
- data/generators/config/config_generator.rb +2 -1
- data/generators/config/templates/boot.rb +1 -1
- data/generators/config/templates/capybara.rb +2 -1
- data/generators/config/templates/default.yml +22 -4
- data/generators/config/templates/drivers/appium.rb +25 -0
- data/generators/config/templates/drivers/headless_firefox.rb +23 -0
- data/generators/cucumber/templates/cuke_sniffer.rake +2 -2
- data/generators/cucumber/templates/env.rb +8 -0
- data/generators/prerequisites/templates/factory_bot.rb +1 -0
- data/generators/root/root_generator.rb +1 -1
- data/generators/root/templates/{.rubocop.yml → .rubocop.yml.erb} +34 -13
- data/generators/root/templates/Gemfile.erb +4 -0
- data/generators/rspec/templates/spec_helper.rb +1 -0
- data/generators/turnip/templates/spec_helper.rb +1 -0
- data/howitzer.gemspec +3 -3
- data/lib/howitzer.rb +40 -0
- data/lib/howitzer/cache.rb +19 -18
- data/lib/howitzer/capybara_helpers.rb +13 -5
- data/lib/howitzer/email.rb +1 -0
- data/lib/howitzer/mail_adapters/gmail.rb +3 -0
- data/lib/howitzer/mail_adapters/mailgun.rb +2 -0
- data/lib/howitzer/mail_adapters/mailtrap.rb +3 -0
- data/lib/howitzer/mailgun_api/connector.rb +1 -0
- data/lib/howitzer/meta.rb +11 -0
- data/lib/howitzer/meta/actions.rb +38 -0
- data/lib/howitzer/meta/element.rb +38 -0
- data/lib/howitzer/meta/entry.rb +62 -0
- data/lib/howitzer/meta/iframe.rb +41 -0
- data/lib/howitzer/meta/section.rb +30 -0
- data/lib/howitzer/version.rb +1 -1
- data/lib/howitzer/web/capybara_context_holder.rb +1 -0
- data/lib/howitzer/web/capybara_methods_proxy.rb +4 -1
- data/lib/howitzer/web/element_dsl.rb +1 -0
- data/lib/howitzer/web/iframe_dsl.rb +2 -0
- data/lib/howitzer/web/page.rb +10 -0
- data/lib/howitzer/web/page_dsl.rb +3 -0
- data/lib/howitzer/web/page_validator.rb +2 -0
- data/lib/howitzer/web/section.rb +8 -0
- data/lib/howitzer/web/section_dsl.rb +1 -0
- data/spec/support/shared_examples/capybara_context_holder.rb +1 -1
- data/spec/support/shared_examples/meta_highlight_xpath.rb +41 -0
- data/spec/unit/generators/config_generator_spec.rb +4 -2
- data/spec/unit/generators/root_generator_spec.rb +32 -21
- data/spec/unit/generators/templates/cucumber_spec.rb +97 -0
- data/spec/unit/generators/templates/rspec_spec.rb +88 -0
- data/spec/unit/generators/templates/turnip_spec.rb +98 -0
- data/spec/unit/lib/capybara_helpers_spec.rb +37 -4
- data/spec/unit/lib/howitzer_spec.rb +23 -0
- data/spec/unit/lib/meta/element_spec.rb +59 -0
- data/spec/unit/lib/meta/entry_spec.rb +77 -0
- data/spec/unit/lib/meta/iframe_spec.rb +66 -0
- data/spec/unit/lib/meta/section_spec.rb +43 -0
- data/spec/unit/lib/utils/string_extensions_spec.rb +1 -1
- data/spec/unit/lib/web/element_dsl_spec.rb +10 -1
- data/spec/unit/lib/web/page_spec.rb +7 -0
- data/spec/unit/lib/web/section_spec.rb +7 -0
- metadata +31 -15
- 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, ®istered_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, ®istered_drivers[:selenium]
|
23
|
+
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
require 'cuke_sniffer'
|
2
2
|
|
3
3
|
def path_to_features
|
4
|
-
@
|
4
|
+
@path_to_features ||= File.expand_path(File.join(__dir__, '..', 'features'))
|
5
5
|
end
|
6
6
|
|
7
7
|
def cuke_sniffer
|
8
|
-
@
|
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
|
@@ -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
|
-
|
6
|
-
|
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
|
-
|
12
|
+
Lint/AmbiguousBlockAssociation:
|
17
13
|
Enabled: false
|
14
|
+
<% end -%>
|
18
15
|
|
19
16
|
Lint/AmbiguousRegexpLiteral:
|
20
17
|
Enabled: false
|
21
18
|
|
22
|
-
|
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/
|
34
|
+
Style/EmptyElse:
|
32
35
|
Enabled: false
|
33
36
|
|
34
|
-
|
35
|
-
|
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
|
#
|
data/howitzer.gemspec
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.expand_path('
|
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.
|
19
|
+
gem.required_ruby_version = '>= 2.3'
|
20
20
|
|
21
21
|
gem.add_runtime_dependency 'activesupport', '~>5.0'
|
22
|
-
gem.add_runtime_dependency 'capybara',
|
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'
|
data/lib/howitzer.rb
CHANGED
@@ -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
|
data/lib/howitzer/cache.rb
CHANGED
@@ -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
|
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(
|
21
|
-
check_ns(
|
22
|
-
@data[
|
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
|
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(
|
34
|
-
check_ns(
|
35
|
-
key ? @data[
|
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
|
40
|
+
# @param namespace [String] a namespace
|
41
41
|
|
42
|
-
def clear_ns(
|
43
|
-
init_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(
|
57
|
-
raise Howitzer::NoDataError, 'Data storage namespace can not be empty' unless
|
58
|
-
|
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?(
|
62
|
-
!@data.key?(
|
62
|
+
def ns_absent?(namespace)
|
63
|
+
!@data.key?(namespace)
|
63
64
|
end
|
64
65
|
|
65
|
-
def init_ns(
|
66
|
-
@data[
|
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
|
-
|
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?
|
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
|
-
|
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
|
data/lib/howitzer/email.rb
CHANGED
@@ -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
|