automation_helpers 4.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.
- checksums.yaml +7 -0
- data/LICENSE.txt +13 -0
- data/README.md +49 -0
- data/lib/automation_helpers/drivers/browserstack.rb +9 -0
- data/lib/automation_helpers/drivers/local.rb +9 -0
- data/lib/automation_helpers/drivers/remote.rb +9 -0
- data/lib/automation_helpers/drivers/v4/browserstack.rb +135 -0
- data/lib/automation_helpers/drivers/v4/capabilities.rb +106 -0
- data/lib/automation_helpers/drivers/v4/local.rb +92 -0
- data/lib/automation_helpers/drivers/v4/options.rb +52 -0
- data/lib/automation_helpers/drivers/v4/remote.rb +64 -0
- data/lib/automation_helpers/drivers/v4.rb +7 -0
- data/lib/automation_helpers/drivers.rb +6 -0
- data/lib/automation_helpers/extensions/array.rb +18 -0
- data/lib/automation_helpers/extensions/capybara/node/element.rb +31 -0
- data/lib/automation_helpers/extensions/cucumber/core/test/case.rb +26 -0
- data/lib/automation_helpers/extensions/selenium/webdriver/logs.rb +47 -0
- data/lib/automation_helpers/extensions/string.rb +34 -0
- data/lib/automation_helpers/extensions.rb +7 -0
- data/lib/automation_helpers/logger.rb +21 -0
- data/lib/automation_helpers/patches/base.rb +52 -0
- data/lib/automation_helpers/patches/capybara.rb +27 -0
- data/lib/automation_helpers/patches/parallel_cucumber.rb +86 -0
- data/lib/automation_helpers/patches/selenium_logger.rb +24 -0
- data/lib/automation_helpers/patches/selenium_manager.rb +43 -0
- data/lib/automation_helpers/patches/selenium_options.rb +72 -0
- data/lib/automation_helpers/patches.rb +8 -0
- data/lib/automation_helpers/version.rb +5 -0
- data/lib/automation_helpers.rb +68 -0
- metadata +201 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Selenium
|
4
|
+
module WebDriver
|
5
|
+
#
|
6
|
+
# Additional useful methods to extend the Selenium::WebDriver::Logs class with
|
7
|
+
#
|
8
|
+
class Logs
|
9
|
+
# @return [Hash]
|
10
|
+
#
|
11
|
+
# Identical to the regular log fetch from Selenium, but stores the logs inside a Hash to be writable later
|
12
|
+
def get(type)
|
13
|
+
cached_logs[type] = @bridge.log(type)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [Integer]
|
17
|
+
#
|
18
|
+
# Write the logs to a filepath (If provided), or default file path (Configured). Returns the filesize
|
19
|
+
# as per the regular Ruby Filesystem method
|
20
|
+
def write_log_to_file(type, file = default_file_path)
|
21
|
+
get(type) unless cached_logs.key?(type)
|
22
|
+
|
23
|
+
cached_logs[type].each do |log_entry|
|
24
|
+
if file == $stdout
|
25
|
+
file << log_entry
|
26
|
+
else
|
27
|
+
File.write(file, log_entry)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def default_file_path
|
35
|
+
AutomationHelpers.chrome_log_path || raise(missing_file_path_error)
|
36
|
+
end
|
37
|
+
|
38
|
+
def cached_logs
|
39
|
+
@cached_logs ||= {}
|
40
|
+
end
|
41
|
+
|
42
|
+
def missing_file_path_error
|
43
|
+
'Set the path to store logs using AutomationHelpers.chrome_log_path= or pass in a filepath directly to #write_log_to_file'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Additional useful methods to extend the String class with
|
4
|
+
class String
|
5
|
+
# @return [String]
|
6
|
+
#
|
7
|
+
# Convert's a regular string name into it's pascalized format
|
8
|
+
# This can then be used to generate a ClassName
|
9
|
+
def pascalize
|
10
|
+
split('_').map(&:capitalize).join
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [String]
|
14
|
+
#
|
15
|
+
# Convert's a regular string into a snake cased format
|
16
|
+
# Will sanitize out some characters (Designed for titles)
|
17
|
+
def snake_case
|
18
|
+
gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
19
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
20
|
+
.squeeze(' ')
|
21
|
+
.tr('-', '_')
|
22
|
+
.tr(' ', '_')
|
23
|
+
.tr("'", '')
|
24
|
+
.downcase
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String]
|
28
|
+
#
|
29
|
+
# Sanitize and convert every individual whitespace character to
|
30
|
+
# a regular space character (Does not sanitize newlines)
|
31
|
+
def sanitize_whitespace
|
32
|
+
gsub(/[ \t\r\f\u00A0]/, ' ')
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'automation_helpers/extensions/capybara/node/element'
|
4
|
+
require 'automation_helpers/extensions/cucumber/core/test/case'
|
5
|
+
require 'automation_helpers/extensions/selenium/webdriver/logs'
|
6
|
+
require 'automation_helpers/extensions/array'
|
7
|
+
require 'automation_helpers/extensions/string'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module AutomationHelpers
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
#
|
9
|
+
class Logger
|
10
|
+
def self.create(output = $stdout)
|
11
|
+
logger = ::Logger.new(output)
|
12
|
+
logger.progname = 'Automation Helpers'
|
13
|
+
logger.level = :INFO
|
14
|
+
logger.formatter = proc do |severity, time, progname, msg|
|
15
|
+
"#{time.strftime('%F %T')} - #{severity} - #{progname} - #{msg}\n"
|
16
|
+
end
|
17
|
+
|
18
|
+
logger
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AutomationHelpers
|
4
|
+
module Patches
|
5
|
+
class Base
|
6
|
+
#
|
7
|
+
# api private (Not intended to be instantiated directly!)
|
8
|
+
#
|
9
|
+
def patch!
|
10
|
+
raise 'This is no longer supported' if prevent_usage?
|
11
|
+
|
12
|
+
Kernel.warn('This is now deprecated and should not be used') if deprecate?
|
13
|
+
AutomationHelpers.logger.info("Adding patch: #{self.class}")
|
14
|
+
AutomationHelpers.logger.debug(description)
|
15
|
+
perform
|
16
|
+
AutomationHelpers.logger.info('Patch successfully added.')
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
#
|
22
|
+
# api private (Not intended to be instantiated directly!)
|
23
|
+
#
|
24
|
+
# From what point should this patch start throwing deprecation notices
|
25
|
+
# If a date is provided, then after that date
|
26
|
+
# If a version from is provided, then all releases after that one (NB: You must provide a link to the gem version)
|
27
|
+
#
|
28
|
+
def deprecate?
|
29
|
+
if defined?(deprecation_notice_date)
|
30
|
+
Time.new >= deprecation_notice_date
|
31
|
+
elsif defined?(deprecate_from)
|
32
|
+
Gem::Version.new(gem_version) >= Gem::Version.new(deprecate_from)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# api private (Not intended to be instantiated directly!)
|
38
|
+
#
|
39
|
+
# From what point should this patch start preventing usage deprecation notices
|
40
|
+
# If a date is provided, then after that date
|
41
|
+
# If a version from is provided, then all releases after that one (NB: You must provide a link to the gem version)
|
42
|
+
#
|
43
|
+
def prevent_usage?
|
44
|
+
if defined?(prevent_usage_date)
|
45
|
+
Time.new >= prevent_usage_date
|
46
|
+
elsif defined?(prevent_usage_from)
|
47
|
+
Gem::Version.new(gem_version) >= Gem::Version.new(prevent_usage_from)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AutomationHelpers
|
4
|
+
module Patches
|
5
|
+
class Capybara < Base
|
6
|
+
private
|
7
|
+
|
8
|
+
def description
|
9
|
+
<<~DESCRIPTION
|
10
|
+
There is a bug in the #text method as Safari's w3c conformant endpoint isn't w3c conformant.
|
11
|
+
|
12
|
+
See https://github.com/teamcapybara/capybara/issues/2213 for more details.
|
13
|
+
DESCRIPTION
|
14
|
+
end
|
15
|
+
|
16
|
+
def perform
|
17
|
+
::Capybara::Node::Element.prepend SafariTextPatch
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module SafariTextPatch
|
22
|
+
def text(type = nil, normalize_ws: ::Capybara.default_normalize_ws)
|
23
|
+
super(type, normalize_ws: normalize_ws)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AutomationHelpers
|
4
|
+
module Patches
|
5
|
+
class ParallelCucumber < Base
|
6
|
+
private
|
7
|
+
|
8
|
+
def description
|
9
|
+
<<~DESCRIPTION
|
10
|
+
This patch fixes an issue where the parallel tests gem won't place the --retry flag in the correct
|
11
|
+
position for command invocation with cucumber
|
12
|
+
See: https://github.com/grosser/parallel_tests/pull/827 for more details/discussion including this fix
|
13
|
+
DESCRIPTION
|
14
|
+
end
|
15
|
+
|
16
|
+
def perform
|
17
|
+
::ParallelTests::Gherkin::Runner.extend RetryFlagFix
|
18
|
+
end
|
19
|
+
|
20
|
+
def deprecation_notice_date
|
21
|
+
Time.new(2022, 6, 30)
|
22
|
+
end
|
23
|
+
|
24
|
+
def prevent_usage_from
|
25
|
+
'4.0.0'
|
26
|
+
end
|
27
|
+
|
28
|
+
def gem_version
|
29
|
+
::ParallelTests::VERSION
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module RetryFlagFix
|
34
|
+
def run_tests(test_files, process_number, num_processes, options)
|
35
|
+
# Copied Code - https://github.com/grosser/parallel_tests/blob/master/lib/parallel_tests/gherkin/runner.rb#L9
|
36
|
+
combined_scenarios = test_files
|
37
|
+
|
38
|
+
if options[:group_by] == :scenarios
|
39
|
+
grouped = test_files.map { |t| t.split(':') }.group_by(&:first)
|
40
|
+
combined_scenarios = grouped.map do |file, files_and_lines|
|
41
|
+
"#{file}:#{files_and_lines.map(&:last).join(':')}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
sanitized_test_files = combined_scenarios.map { |val| WINDOWS ? "\"#{val}\"" : Shellwords.escape(val) }
|
46
|
+
|
47
|
+
options[:env] ||= {}
|
48
|
+
options[:env] = options[:env].merge({ 'AUTOTEST' => '1' }) if $stdout.tty?
|
49
|
+
|
50
|
+
# New code
|
51
|
+
opts = cucumber_opts(options[:test_options])
|
52
|
+
cmd = [
|
53
|
+
executable,
|
54
|
+
(runtime_logging if File.directory?(File.dirname(runtime_log))),
|
55
|
+
opts[0],
|
56
|
+
*sanitized_test_files,
|
57
|
+
opts[1]
|
58
|
+
].compact.reject(&:empty?).join(' ')
|
59
|
+
execute_command(cmd, process_number, num_processes, options)
|
60
|
+
end
|
61
|
+
|
62
|
+
def cucumber_opts(given)
|
63
|
+
# All new code
|
64
|
+
initial =
|
65
|
+
if given =~ (/--profile/) || given =~ (/(^|\s)-p /)
|
66
|
+
given
|
67
|
+
else
|
68
|
+
[given, profile_from_config].compact.join(" ")
|
69
|
+
end
|
70
|
+
|
71
|
+
opts_as_individuals = initial.scan(/\S+\s\S+/)
|
72
|
+
desired_output = ['', '']
|
73
|
+
|
74
|
+
opts_as_individuals.each do |opt|
|
75
|
+
if opt.match?(/--retry \d+/)
|
76
|
+
desired_output[1] = opt
|
77
|
+
else
|
78
|
+
desired_output[0] = "#{desired_output[0]} #{opt}".strip
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
desired_output
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AutomationHelpers
|
4
|
+
module Patches
|
5
|
+
class SeleniumLogger < Base
|
6
|
+
private
|
7
|
+
|
8
|
+
def description
|
9
|
+
<<~DESCRIPTION
|
10
|
+
When using the Selenium Logger that is set to pipe to a file, the Net::HTTP adapter (default),
|
11
|
+
can return unencoded binary (confusingly called ASCII-8BIT in Ruby).
|
12
|
+
Consequently we set the logger to binmode so it doesn't try to encode the data - this would always
|
13
|
+
cause errors for non-ASCII characters, whatever the parent encoding is. An example of this is ©.
|
14
|
+
|
15
|
+
See https://github.com/ruby/net-http/issues/14 for a root cause analysis of the Adapter
|
16
|
+
DESCRIPTION
|
17
|
+
end
|
18
|
+
|
19
|
+
def perform
|
20
|
+
::Selenium::WebDriver.logger.io.binmode
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AutomationHelpers
|
4
|
+
module Patches
|
5
|
+
class SeleniumManager < Base
|
6
|
+
private
|
7
|
+
|
8
|
+
def description
|
9
|
+
<<~DESCRIPTION
|
10
|
+
This patch fixes an issue with Selenium4 not bubbling up the httpOnly property of a cookie
|
11
|
+
See: https://github.com/SeleniumHQ/selenium/pull/8958 for more details/discussion including this fix
|
12
|
+
DESCRIPTION
|
13
|
+
end
|
14
|
+
|
15
|
+
def perform
|
16
|
+
::Selenium::WebDriver::Manager.prepend CookieConverter
|
17
|
+
end
|
18
|
+
|
19
|
+
def deprecate_from
|
20
|
+
'4.0.0.beta3'
|
21
|
+
end
|
22
|
+
|
23
|
+
def prevent_usage_from
|
24
|
+
'4.0.0.beta5'
|
25
|
+
end
|
26
|
+
|
27
|
+
def gem_version
|
28
|
+
::Selenium::WebDriver::VERSION
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module CookieConverter
|
33
|
+
def convert_cookie(cookie)
|
34
|
+
super(cookie)
|
35
|
+
.merge(
|
36
|
+
{
|
37
|
+
http_only: cookie['httpOnly']
|
38
|
+
}
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AutomationHelpers
|
4
|
+
module Patches
|
5
|
+
class SeleniumOptions < Base
|
6
|
+
def initialize(browser)
|
7
|
+
@browser = browser
|
8
|
+
super()
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return [Nil || AutomationHelpers.logger.info]
|
12
|
+
#
|
13
|
+
# For SeleniumOptions we only want to run the patch when
|
14
|
+
# we are on browsers without the relevant JSON fixes in upstream
|
15
|
+
def patch!
|
16
|
+
return unless valid?
|
17
|
+
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def valid?
|
24
|
+
%i[firefox safari].include?(@browser)
|
25
|
+
end
|
26
|
+
|
27
|
+
def description
|
28
|
+
<<~DESCRIPTION
|
29
|
+
This patch fixes an issue with Selenium4 not camelising the browser_name property
|
30
|
+
The issue is the driver, which is now fully W3C conformant expects `browserName`
|
31
|
+
|
32
|
+
See: https://github.com/SeleniumHQ/selenium/pull/8834 for more details/discussion including this fix
|
33
|
+
LH - Nov 2020
|
34
|
+
DESCRIPTION
|
35
|
+
end
|
36
|
+
|
37
|
+
def perform
|
38
|
+
case @browser
|
39
|
+
when :firefox then ::Selenium::WebDriver::Firefox::Options.include CapabilitiesAsJsonFix
|
40
|
+
when :safari then ::Selenium::WebDriver::Safari::Options.include CapabilitiesAsJsonFix
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def deprecate_from
|
45
|
+
'4.0.0.beta5'
|
46
|
+
end
|
47
|
+
|
48
|
+
def prevent_usage_from
|
49
|
+
'4.0.1'
|
50
|
+
end
|
51
|
+
|
52
|
+
def gem_version
|
53
|
+
::Selenium::WebDriver::VERSION
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
module CapabilitiesAsJsonFix
|
58
|
+
private
|
59
|
+
|
60
|
+
def generate_as_json(value, camelize_keys: true)
|
61
|
+
if value.is_a?(Hash)
|
62
|
+
value.each_with_object({}) do |(key, val), hash|
|
63
|
+
key = convert_json_key(key, camelize: camelize_keys)
|
64
|
+
hash[key] = generate_as_json(val, camelize_keys: key != 'prefs')
|
65
|
+
end
|
66
|
+
else
|
67
|
+
super
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'automation_helpers/patches/base'
|
4
|
+
require 'automation_helpers/patches/capybara'
|
5
|
+
require 'automation_helpers/patches/parallel_cucumber'
|
6
|
+
require 'automation_helpers/patches/selenium_logger'
|
7
|
+
require 'automation_helpers/patches/selenium_manager'
|
8
|
+
require 'automation_helpers/patches/selenium_options'
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'automation_helpers/drivers'
|
4
|
+
require 'automation_helpers/extensions'
|
5
|
+
require 'automation_helpers/logger'
|
6
|
+
require 'automation_helpers/patches'
|
7
|
+
require 'automation_helpers/version'
|
8
|
+
|
9
|
+
# {AutomationHelpers} namespace
|
10
|
+
module AutomationHelpers
|
11
|
+
class << self
|
12
|
+
attr_accessor :chrome_log_path
|
13
|
+
|
14
|
+
def configure
|
15
|
+
yield self
|
16
|
+
end
|
17
|
+
|
18
|
+
# The Automation Helpers logger object - This is called automatically in several
|
19
|
+
# locations and will log messages according to the normal Ruby protocol
|
20
|
+
# To alter (or check), the log level; call .log_level= or .log_level
|
21
|
+
#
|
22
|
+
# This logger object can also be used to manually log messages
|
23
|
+
#
|
24
|
+
# To Manually log a message
|
25
|
+
# AutomationHelpers.logger.info('Information')
|
26
|
+
# AutomationHelpers.logger.debug('Input debug message')
|
27
|
+
#
|
28
|
+
# By default the logger will output all messages to $stdout, but can be
|
29
|
+
# altered to log to a file or another IO location by calling `.log_path=`
|
30
|
+
def logger
|
31
|
+
@logger ||= Logger.create
|
32
|
+
end
|
33
|
+
|
34
|
+
def logger=(logger)
|
35
|
+
raise ArgumentError, 'You must supply an existing Logger' unless logger.is_a?(::Logger)
|
36
|
+
|
37
|
+
@logger = logger
|
38
|
+
end
|
39
|
+
|
40
|
+
# This writer method allows you to configure where you want the output of
|
41
|
+
# the automation_helpers logs to go (Default is $stdout)
|
42
|
+
#
|
43
|
+
# example: AutomationHelpers.log_path = 'automation_helpers.log' would save all
|
44
|
+
# log messages to `./automation_helpers.log`
|
45
|
+
def log_path=(logdev)
|
46
|
+
logger.reopen(logdev)
|
47
|
+
end
|
48
|
+
|
49
|
+
# To enable full logging (This uses the Ruby API, so can accept any of a
|
50
|
+
# Symbol / String / Integer as an input
|
51
|
+
# AutomationHelpers.log_level = :DEBUG
|
52
|
+
# AutomationHelpers.log_level = 'DEBUG'
|
53
|
+
# AutomationHelpers.log_level = 0
|
54
|
+
#
|
55
|
+
# To disable all logging
|
56
|
+
# AutomationHelpers.log_level = :UNKNOWN
|
57
|
+
def log_level=(value)
|
58
|
+
logger.level = value
|
59
|
+
end
|
60
|
+
|
61
|
+
# To query what level is being logged
|
62
|
+
# AutomationHelpers.log_level
|
63
|
+
# => :INFO # By default
|
64
|
+
def log_level
|
65
|
+
%i[DEBUG INFO WARN ERROR FATAL UNKNOWN][logger.level]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|