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.
Files changed (30) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +13 -0
  3. data/README.md +49 -0
  4. data/lib/automation_helpers/drivers/browserstack.rb +9 -0
  5. data/lib/automation_helpers/drivers/local.rb +9 -0
  6. data/lib/automation_helpers/drivers/remote.rb +9 -0
  7. data/lib/automation_helpers/drivers/v4/browserstack.rb +135 -0
  8. data/lib/automation_helpers/drivers/v4/capabilities.rb +106 -0
  9. data/lib/automation_helpers/drivers/v4/local.rb +92 -0
  10. data/lib/automation_helpers/drivers/v4/options.rb +52 -0
  11. data/lib/automation_helpers/drivers/v4/remote.rb +64 -0
  12. data/lib/automation_helpers/drivers/v4.rb +7 -0
  13. data/lib/automation_helpers/drivers.rb +6 -0
  14. data/lib/automation_helpers/extensions/array.rb +18 -0
  15. data/lib/automation_helpers/extensions/capybara/node/element.rb +31 -0
  16. data/lib/automation_helpers/extensions/cucumber/core/test/case.rb +26 -0
  17. data/lib/automation_helpers/extensions/selenium/webdriver/logs.rb +47 -0
  18. data/lib/automation_helpers/extensions/string.rb +34 -0
  19. data/lib/automation_helpers/extensions.rb +7 -0
  20. data/lib/automation_helpers/logger.rb +21 -0
  21. data/lib/automation_helpers/patches/base.rb +52 -0
  22. data/lib/automation_helpers/patches/capybara.rb +27 -0
  23. data/lib/automation_helpers/patches/parallel_cucumber.rb +86 -0
  24. data/lib/automation_helpers/patches/selenium_logger.rb +24 -0
  25. data/lib/automation_helpers/patches/selenium_manager.rb +43 -0
  26. data/lib/automation_helpers/patches/selenium_options.rb +72 -0
  27. data/lib/automation_helpers/patches.rb +8 -0
  28. data/lib/automation_helpers/version.rb +5 -0
  29. data/lib/automation_helpers.rb +68 -0
  30. 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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AutomationHelpers
4
+ VERSION = '4.0'
5
+ end
@@ -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