bugsnag-maze-runner 7.22.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. checksums.yaml +7 -0
  2. data/bin/bugsnag-print-load-paths +6 -0
  3. data/bin/download-logs +74 -0
  4. data/bin/maze-runner +174 -0
  5. data/bin/upload-app +56 -0
  6. data/lib/features/scripts/await-android-emulator.sh +11 -0
  7. data/lib/features/scripts/clear-android-app-data.sh +8 -0
  8. data/lib/features/scripts/force-stop-android-app.sh +8 -0
  9. data/lib/features/scripts/install-android-app.sh +15 -0
  10. data/lib/features/scripts/launch-android-app.sh +38 -0
  11. data/lib/features/scripts/launch-android-emulator.sh +15 -0
  12. data/lib/features/steps/android_steps.rb +51 -0
  13. data/lib/features/steps/app_automator_steps.rb +228 -0
  14. data/lib/features/steps/aws_sam_steps.rb +212 -0
  15. data/lib/features/steps/breadcrumb_steps.rb +80 -0
  16. data/lib/features/steps/browser_steps.rb +93 -0
  17. data/lib/features/steps/build_api_steps.rb +25 -0
  18. data/lib/features/steps/document_server_steps.rb +7 -0
  19. data/lib/features/steps/error_reporting_steps.rb +358 -0
  20. data/lib/features/steps/feature_flag_steps.rb +190 -0
  21. data/lib/features/steps/header_steps.rb +72 -0
  22. data/lib/features/steps/log_steps.rb +29 -0
  23. data/lib/features/steps/multipart_request_steps.rb +142 -0
  24. data/lib/features/steps/network_steps.rb +135 -0
  25. data/lib/features/steps/payload_steps.rb +257 -0
  26. data/lib/features/steps/proxy_steps.rb +34 -0
  27. data/lib/features/steps/query_parameter_steps.rb +31 -0
  28. data/lib/features/steps/request_assertion_steps.rb +186 -0
  29. data/lib/features/steps/runner_steps.rb +428 -0
  30. data/lib/features/steps/session_tracking_steps.rb +116 -0
  31. data/lib/features/steps/trace_steps.rb +206 -0
  32. data/lib/features/steps/value_steps.rb +119 -0
  33. data/lib/features/support/env.rb +7 -0
  34. data/lib/features/support/internal_hooks.rb +207 -0
  35. data/lib/maze/api/appium/file_manager.rb +29 -0
  36. data/lib/maze/appium_server.rb +112 -0
  37. data/lib/maze/assertions/request_set_assertions.rb +97 -0
  38. data/lib/maze/aws/sam.rb +112 -0
  39. data/lib/maze/aws_public_ip.rb +53 -0
  40. data/lib/maze/bugsnag_config.rb +42 -0
  41. data/lib/maze/checks/assert_check.rb +69 -0
  42. data/lib/maze/checks/noop_check.rb +34 -0
  43. data/lib/maze/client/appium/base_client.rb +131 -0
  44. data/lib/maze/client/appium/bb_client.rb +102 -0
  45. data/lib/maze/client/appium/bb_devices.rb +127 -0
  46. data/lib/maze/client/appium/bs_client.rb +91 -0
  47. data/lib/maze/client/appium/bs_devices.rb +141 -0
  48. data/lib/maze/client/appium/bs_legacy_client.rb +31 -0
  49. data/lib/maze/client/appium/local_client.rb +67 -0
  50. data/lib/maze/client/appium.rb +23 -0
  51. data/lib/maze/client/bb_api_client.rb +102 -0
  52. data/lib/maze/client/bb_client_utils.rb +181 -0
  53. data/lib/maze/client/bs_client_utils.rb +168 -0
  54. data/lib/maze/client/selenium/base_client.rb +15 -0
  55. data/lib/maze/client/selenium/bb_browsers.yml +188 -0
  56. data/lib/maze/client/selenium/bb_client.rb +38 -0
  57. data/lib/maze/client/selenium/bs_browsers.yml +257 -0
  58. data/lib/maze/client/selenium/bs_client.rb +89 -0
  59. data/lib/maze/client/selenium/local_client.rb +16 -0
  60. data/lib/maze/client/selenium.rb +16 -0
  61. data/lib/maze/compare.rb +161 -0
  62. data/lib/maze/configuration.rb +182 -0
  63. data/lib/maze/docker.rb +147 -0
  64. data/lib/maze/document_server.rb +46 -0
  65. data/lib/maze/driver/appium.rb +198 -0
  66. data/lib/maze/driver/browser.rb +124 -0
  67. data/lib/maze/errors.rb +52 -0
  68. data/lib/maze/generator.rb +55 -0
  69. data/lib/maze/helper.rb +122 -0
  70. data/lib/maze/hooks/appium_hooks.rb +55 -0
  71. data/lib/maze/hooks/browser_hooks.rb +15 -0
  72. data/lib/maze/hooks/command_hooks.rb +9 -0
  73. data/lib/maze/hooks/error_code_hook.rb +49 -0
  74. data/lib/maze/hooks/hooks.rb +61 -0
  75. data/lib/maze/http_request.rb +21 -0
  76. data/lib/maze/interactive_cli.rb +173 -0
  77. data/lib/maze/logger.rb +86 -0
  78. data/lib/maze/macos_utils.rb +14 -0
  79. data/lib/maze/maze_output.rb +88 -0
  80. data/lib/maze/network.rb +49 -0
  81. data/lib/maze/option/parser.rb +240 -0
  82. data/lib/maze/option/processor.rb +130 -0
  83. data/lib/maze/option/validator.rb +155 -0
  84. data/lib/maze/option.rb +62 -0
  85. data/lib/maze/plugins/bugsnag_reporting_plugin.rb +49 -0
  86. data/lib/maze/plugins/cucumber_report_plugin.rb +101 -0
  87. data/lib/maze/plugins/error_code_plugin.rb +21 -0
  88. data/lib/maze/plugins/global_retry_plugin.rb +38 -0
  89. data/lib/maze/proxy.rb +114 -0
  90. data/lib/maze/request_list.rb +87 -0
  91. data/lib/maze/request_repeater.rb +49 -0
  92. data/lib/maze/retry_handler.rb +67 -0
  93. data/lib/maze/runner.rb +149 -0
  94. data/lib/maze/schemas/OtelTraceSchema.json +390 -0
  95. data/lib/maze/schemas/trace_schema.rb +7 -0
  96. data/lib/maze/schemas/trace_validator.rb +98 -0
  97. data/lib/maze/server.rb +251 -0
  98. data/lib/maze/servlets/base_servlet.rb +27 -0
  99. data/lib/maze/servlets/command_servlet.rb +47 -0
  100. data/lib/maze/servlets/log_servlet.rb +64 -0
  101. data/lib/maze/servlets/reflective_servlet.rb +70 -0
  102. data/lib/maze/servlets/servlet.rb +199 -0
  103. data/lib/maze/servlets/temp.rb +0 -0
  104. data/lib/maze/servlets/trace_servlet.rb +13 -0
  105. data/lib/maze/store.rb +15 -0
  106. data/lib/maze/terminating_server.rb +129 -0
  107. data/lib/maze/timers.rb +51 -0
  108. data/lib/maze/wait.rb +35 -0
  109. data/lib/maze.rb +27 -0
  110. data/lib/utils/deep_merge.rb +17 -0
  111. data/lib/utils/selenium_money_patch.rb +17 -0
  112. metadata +451 -0
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Provides the set of Maze Runner command line options
4
+ module Maze
5
+ module Option
6
+ # Document server options
7
+ DS_BIND_ADDRESS = 'document-server-bind-address'
8
+ DS_PORT = 'document-server-port'
9
+ DS_ROOT = 'document-server-root'
10
+
11
+ # Server options
12
+ BIND_ADDRESS = 'bind-address'
13
+ NULL_PORT = 'null-port'
14
+ PORT = 'port'
15
+
16
+ # Appium options
17
+ A11Y_LOCATOR = 'a11y-locator'
18
+ APP = 'app'
19
+ CAPABILITIES = 'capabilities'
20
+ FARM = 'farm'
21
+
22
+ # Generic device farm options
23
+ ACCESS_KEY = 'access-key'
24
+ APP_BUNDLE_ID = 'app-bundle-id'
25
+ APPIUM_VERSION = 'appium-version'
26
+ BROWSER = 'browser'
27
+ DEVICE = 'device'
28
+ LIST_DEVICES = 'list-devices'
29
+ OS = 'os'
30
+ OS_VERSION = 'os-version'
31
+ TUNNEL = 'tunnel'
32
+ USERNAME = 'username'
33
+
34
+ # BitBar options
35
+ SB_LOCAL = 'sb-local'
36
+
37
+ # BrowserStack-only options
38
+ BS_LOCAL = 'bs-local'
39
+
40
+ # BitBar-only options
41
+ TMS_URI = 'tms-uri'
42
+ TMS_TOKEN = 'tms-token'
43
+
44
+ # Local-only options
45
+ APPIUM_LOGFILE = 'appium-logfile'
46
+ APPIUM_SERVER = 'appium-server'
47
+ APPLE_TEAM_ID = 'apple-team-id'
48
+ START_APPIUM = 'start-appium'
49
+ UDID = 'udid'
50
+
51
+ # Logging options
52
+ ALWAYS_LOG = 'always-log'
53
+ FILE_LOG = 'file-log'
54
+ LOG_REQUESTS = 'log-requests'
55
+
56
+ # General options
57
+ AWS_PUBLIC_IP = 'aws-public-ip'
58
+ REPEATER_API_KEY = 'repeater-api-key'
59
+ ENABLE_BUGSNAG = 'enable-bugsnag'
60
+ ENABLE_RETRIES = 'enable-retries'
61
+ end
62
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bugsnag'
4
+ require 'cucumber/core/filter'
5
+
6
+ # Required to access the options
7
+ module Cucumber
8
+ class Configuration
9
+ attr_accessor :options
10
+ end
11
+ end
12
+
13
+ module Maze
14
+ module Plugins
15
+ class BugsnagReportingPlugin < Cucumber::Core::Filter.new(:configuration)
16
+
17
+ def test_case(test_case)
18
+ configuration.on_event(:test_step_finished) do |event|
19
+ @last_test_step = event.test_step if event.result.failed?
20
+ end
21
+
22
+ configuration.on_event(:test_case_finished) do |event|
23
+
24
+ # Ensure we're in the correct test case and that it's failed
25
+ next unless event.test_case.eql?(test_case) && event.result.failed?
26
+
27
+ Bugsnag.notify(event.result.exception) do |bsg_event|
28
+ unless @last_test_step.nil?
29
+ bsg_event.context = @last_test_step.location
30
+ bsg_event.grouping_hash = test_case.name + @last_test_step.location
31
+ bsg_event.add_metadata(:'scenario', {
32
+ 'failing step': @last_test_step.to_s,
33
+ 'failing step location': @last_test_step.location
34
+ })
35
+ end
36
+ bsg_event.add_metadata(:'scenario', {
37
+ 'scenario name': test_case.name,
38
+ 'scenario location': test_case.location,
39
+ 'scenario tags': test_case.tags,
40
+ 'scenario duration (mS)': event.result.duration.nanoseconds/1000000
41
+ })
42
+ end
43
+ end
44
+
45
+ super
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bugsnag'
4
+ require 'cucumber/core/filter'
5
+ require 'json'
6
+
7
+ # Required to access the options
8
+ module Cucumber
9
+ class Configuration
10
+ attr_accessor :options
11
+ end
12
+ end
13
+
14
+ module Maze
15
+ module Plugins
16
+ class CucumberReportPlugin
17
+
18
+ def initialize
19
+ configured_data = {
20
+ driver_class: Maze.driver.class,
21
+ device_farm: Maze.config.farm,
22
+ device: Maze.config.device,
23
+ os: Maze.config.os,
24
+ os_version: Maze.config.os_version
25
+ }
26
+ buildkite_data = {
27
+ pipeline: ENV['BUILDKITE_PIPELINE_NAME'],
28
+ repo: ENV['BUILDKITE_REPO'],
29
+ build_url: ENV['BUILDKITE_BUILD_URL'],
30
+ branch: ENV['BUILDKITE_BRANCH'],
31
+ message: ENV['BUILDKITE_MESSAGE'],
32
+ step: ENV['BUILDKITE_LABEL'],
33
+ commit: ENV['BUILDKITE_COMMIT']
34
+ }
35
+ report['configuration'] = configured_data
36
+ report['build'] = buildkite_data
37
+ end
38
+
39
+ def install_plugin(cuc_config)
40
+ unless Maze.config.tms_uri && Maze.config.tms_token && ENV['BUILDKITE']
41
+ $logger.info 'No test report will be delivered for this run'
42
+ return
43
+ end
44
+ # Add installation hook
45
+ cuc_config.formats << ['json', {}, json_report_stream]
46
+
47
+ # Add exit hook
48
+ at_exit do
49
+ finish_report
50
+ end
51
+ end
52
+
53
+ def json_report_stream
54
+ @json_report_stream ||= StringIO.new
55
+ end
56
+
57
+ def report
58
+ @report ||= {}
59
+ end
60
+
61
+ private
62
+
63
+ def finish_report
64
+ session_hash = JSON.parse(json_report_stream.string)
65
+ report[:session] = session_hash
66
+ output_folder = File.join(Dir.pwd, 'maze_output')
67
+ filename = 'maze_report.json'
68
+ filepath = File.join(output_folder, filename)
69
+
70
+ begin
71
+ File.open(filepath, 'w') do |file|
72
+ file.puts JSON.pretty_generate(report)
73
+ end
74
+ rescue => e
75
+ $logger.warn 'Report could not be saved locally'
76
+ $logger.warn e.message
77
+ end
78
+
79
+ send_report
80
+ end
81
+
82
+ def send_report
83
+ uri = URI("#{Maze.config.tms_uri}/report")
84
+ request = Net::HTTP::Post.new(uri)
85
+ request['Content-Type'] = 'application/json'
86
+ request['Authorization'] = Maze.config.tms_token
87
+ request.body = JSON.generate(report)
88
+
89
+ begin
90
+ http = Net::HTTP.new(uri.hostname, uri.port)
91
+ http.request(request)
92
+ rescue => e
93
+ $logger.warn 'Report delivery attempt failed'
94
+ $logger.warn e.message
95
+ else
96
+ $logger.info 'Cucumber report delivered to test report server'
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maze
4
+ module Plugins
5
+ class ErrorCodePlugin < Cucumber::Core::Filter.new(:configuration)
6
+
7
+ def test_case(test_case)
8
+ configuration.on_event(:test_case_finished) do |event|
9
+
10
+ # Ensure we're in the correct test case, and the test failed
11
+ next unless event.test_case == test_case
12
+
13
+ error = event.result.failed? ? event.result.exception.class : nil
14
+ Maze::Hooks::ErrorCodeHook.last_test_error_class = error
15
+ end
16
+
17
+ super
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cucumber/core/filter'
4
+
5
+ # Required to access the options
6
+ module Cucumber
7
+ class Configuration
8
+ attr_accessor :options
9
+ end
10
+ end
11
+
12
+ module Maze
13
+ module Plugins
14
+ class GlobalRetryPlugin < Cucumber::Core::Filter.new(:configuration)
15
+
16
+ def test_case(test_case)
17
+ configuration.on_event(:test_case_finished) do |event|
18
+
19
+ # Ensure we're in the correct test case
20
+ next unless event.test_case == test_case
21
+
22
+ # Set retry to 0
23
+ configuration.options[:retry] = 0
24
+
25
+ # Guard to check if the case should be retried
26
+ should_retry = event.result.failed? && Maze::RetryHandler.should_retry?(test_case, event)
27
+
28
+ next unless should_retry
29
+
30
+ # Set retry to 1
31
+ configuration.options[:retry] = 1
32
+ end
33
+
34
+ super
35
+ end
36
+ end
37
+ end
38
+ end
data/lib/maze/proxy.rb ADDED
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'singleton'
5
+ require 'webrick'
6
+ require 'webrick/https'
7
+ require 'webrick/httpproxy'
8
+
9
+ module Maze
10
+ # Provides the ability to run a proxy server, using the WEBrick proxy server.
11
+ # Note that for an HTTPS proxy a self-signed certificate will be used. If using curl, for example, this
12
+ # means having to employ the --proxy-insecure option.
13
+ class Proxy
14
+ include Singleton
15
+
16
+ # There are some constraints on the port from driving remote browsers on BrowserStack.
17
+ # E.g. the ports/ranges that Safari will access on "localhost" urls are restricted to the following:
18
+ # 80, 3000, 4000, 5000, 8000, 8080 or 9000-9999 [ from https://stackoverflow.com/a/28678652 ]
19
+ PORT = 9000
20
+
21
+ def initialize
22
+ @hosts = []
23
+ end
24
+
25
+ # Whether the proxy handled a request for the given host
26
+ #
27
+ # @param host [String] The destination host to test for
28
+ def handled_host?(host)
29
+ @hosts.include? host
30
+ end
31
+
32
+ # Whether the proxy server thread is running
33
+ #
34
+ def running?
35
+ @thread&.alive?
36
+ end
37
+
38
+ # Starts the WEBrick proxy in a separate thread
39
+ # If authentication if requested, then the credentials used are simply 'user' with 'password'.
40
+ #
41
+ # @param protocol [Symbol] :Http or Https
42
+ # @param authenticated [Boolean] Whether basic authentication should be applied.
43
+ def start(protocol, authenticated = false)
44
+ @hosts.clear
45
+
46
+ attempts = 0
47
+ $logger.info 'Starting proxy server'
48
+ loop do
49
+ @thread = Thread.new do
50
+
51
+ handler = proc do |req, res|
52
+ req.header['host'].each { |host| @hosts.append(host) }
53
+ end
54
+ config = {
55
+ Logger: $logger,
56
+ Port: PORT,
57
+ ProxyContentHandler: handler
58
+ }
59
+
60
+ # Setup protocol
61
+ if protocol == :Http
62
+ $logger.info 'Starting HTTP proxy'
63
+ elsif protocol == :Https
64
+ $logger.info 'Starting HTTPS proxy'
65
+ cert_name = [
66
+ %w[CN localhost]
67
+ ]
68
+ config[:SSLCertName] = cert_name
69
+ config[:SSLEnable] = true
70
+ else
71
+ raise "Unsupported protocol #{protocol}: :Http and :Https are supported"
72
+ end
73
+
74
+ # Authentication required?
75
+ if authenticated
76
+ # Apache compatible Password manager
77
+ htpasswd = WEBrick::HTTPAuth::Htpasswd.new File.expand_path('htpasswd', __dir__)
78
+ htpasswd.set_passwd 'Proxy Realm', 'user', 'password'
79
+ htpasswd.flush
80
+ authenticator = WEBrick::HTTPAuth::ProxyBasicAuth.new Realm: 'Proxy Realm',
81
+ UserDB: htpasswd
82
+ config[:ProxyAuthProc] = authenticator.method(:authenticate).to_proc
83
+ end
84
+
85
+ # Crwate and start the proxy
86
+ proxy = WEBrick::HTTPProxyServer.new config
87
+ proxy.start
88
+ rescue StandardError => e
89
+ $logger.warn "Failed to start proxy server: #{e.message}"
90
+ ensure
91
+ proxy&.shutdown
92
+ end
93
+
94
+ # Need a short sleep here as a dying thread is still alive momentarily
95
+ sleep 1
96
+ break if running?
97
+
98
+ # Bail out after 3 attempts
99
+ attempts += 1
100
+ raise 'Too many failed attempts to start proxy server' if attempts == 3
101
+
102
+ # Failed to start - sleep before retrying
103
+ $logger.info 'Retrying in 5 seconds'
104
+ sleep 5
105
+ end
106
+ end
107
+
108
+ # Stops the WEBrick proxy thread if it's running
109
+ def stop
110
+ @thread&.kill if @thread&.alive?
111
+ @thread = nil
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maze
4
+ # An abstraction for storing a list of requests (e.g. Errors, Sessions),
5
+ # keeping track of the "current" request (i.e. the one being inspected).
6
+ class RequestList
7
+ def initialize
8
+ @requests = []
9
+ @current = 0
10
+ @count = 0
11
+ end
12
+
13
+ # The number of unprocessed/remaining requests in the list (not the total number actually held)
14
+ def size_remaining
15
+ @count
16
+ end
17
+
18
+ # The total number of requests received, including those already processed
19
+ def size_all
20
+ @requests.size
21
+ end
22
+
23
+ # Add a request to the list
24
+ #
25
+ # @param request The new request, from which a clone is made
26
+ def add(request)
27
+ @requests.append request.clone
28
+ @count += 1
29
+ end
30
+
31
+ # The current request
32
+ def current
33
+ @requests[@current] if @requests.size > @current
34
+ end
35
+
36
+ # Peek at requests yet to be processed - i.e. from current onwards. All requests are left visible in the list.
37
+ # Returns an empty array if there are no requests outstanding.
38
+ def remaining
39
+ return [] if current.nil?
40
+
41
+ @requests[@current..@requests.size]
42
+ end
43
+
44
+ # Moves to the next request, if there is one
45
+ def next
46
+ return if @current >= @requests.size
47
+
48
+ @current += 1
49
+ @count -= 1
50
+ end
51
+
52
+ # A frozen clone of all requests held, including those already processed
53
+ def all
54
+ @requests.clone.freeze
55
+ end
56
+
57
+ # Clears the list
58
+ def clear
59
+ @requests.clear
60
+ @current = 0
61
+ @count = 0
62
+ end
63
+
64
+ # Sorts the first `count` elements of the list by the Bugsnag-Sent-At header, if present in all of those elements
65
+ def sort_by_sent_at!(count)
66
+ return unless count > 1
67
+
68
+ header = 'Bugsnag-Sent-At'
69
+ sub_list = @requests[@current...@current + count]
70
+
71
+ return if sub_list.any? { |r| r[:request][header].nil? }
72
+
73
+ # Sort sublist by Bugsnag-Sent-At and overwrite in the main list
74
+ sub_list.sort_by! { |r| DateTime.parse(r[:request][header]) }
75
+ sub_list.each_with_index { |r, i| @requests[@current + i] = r }
76
+ end
77
+
78
+ # Sorts the remaining elements of the list by the field given, if present in all of those elements
79
+ def sort_by!(key_path)
80
+ list = remaining
81
+
82
+ # Sort the list and overwrite in the main list
83
+ list.sort_by! { |r| Maze::Helper.read_key_path(r[:body], key_path) }
84
+ list.each_with_index { |r, i| @requests[@current + i] = r }
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,49 @@
1
+ module Maze
2
+ # Repeats POST requests
3
+ module RequestRepeater
4
+
5
+ def do_POST(request, response)
6
+ repeat(request) if enabled?
7
+
8
+ super(request, response)
9
+ end
10
+
11
+ private
12
+
13
+ def enabled?
14
+ # enabled if the config option is on and this request type should be repeated
15
+ Maze.config.repeater_api_key && url_for_request_type
16
+ end
17
+
18
+ # @param request [HTTPRequest] The request to be repeated
19
+ def repeat(request)
20
+
21
+ # TODO Forwarding of internal errors to be considered later
22
+ return if request.header.keys.any? { |key| key.downcase == 'bugsnag-internal-error' }
23
+
24
+ url = url_for_request_type
25
+ http = Net::HTTP.new(url.host)
26
+ bugsnag_request = Net::HTTP::Post.new(url.path)
27
+
28
+ # Set all headers that are present
29
+ bugsnag_request.body = request.body
30
+ request.header.each {|key,value| bugsnag_request[key] = value }
31
+ bugsnag_request['bugsnag-api-key'] = Maze.config.repeater_api_key
32
+
33
+ # TODO Also overwrite apiKey in the payload, if present, recalculate the integrity header (handling
34
+ # compressed payloads if the content-encoding header is set accordingly)
35
+
36
+ http.request(bugsnag_request)
37
+ end
38
+
39
+ def url_for_request_type
40
+ url = case @request_type
41
+ when :errors then 'https://notify.bugsnag.com/'
42
+ when :sessions then 'https://sessions.bugsnag.com/'
43
+ when :traces then 'https://otlp.bugsnag.com/v1/traces'
44
+ else return nil
45
+ end
46
+ URI.parse(url)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'appium_lib'
4
+
5
+ module Maze
6
+ # Handles the logic of when a test should be retried after a failure.
7
+ # Note: This class expects a failed test. For repeating a single test see RepeatHandler
8
+ class RetryHandler
9
+ class << self
10
+
11
+ # Acceptable tags to indicate a test should be restarted
12
+ RETRY_TAGS = %w[@retry @retryable @retriable].freeze
13
+
14
+ # Determines whether a failed test_case should be restarted
15
+ #
16
+ # @param test_case [Cucumber::RunningTestCase] The current test_case or scenario
17
+ # @param event [Cucumber::Core::Event] The triggering event
18
+ def should_retry?(test_case, event)
19
+ # Only retry if the option is set and we haven't already retried
20
+ return false if !Maze.config.enable_retries || retried_previously?(test_case)
21
+
22
+ if retry_on_driver_error?(event)
23
+ $logger.warn "Retrying #{test_case.name} due to driver error: #{event.result.exception}"
24
+ if Maze.driver.is_a?(Maze::Driver::Appium)
25
+ Maze.driver.restart
26
+ elsif Maze.driver.is_a?(Maze::Driver::Browser)
27
+ Maze.driver.refresh
28
+ end
29
+ elsif retry_on_tag?(test_case)
30
+ $logger.warn "Retrying #{test_case.name} due to retry tag"
31
+ elsif Maze.dynamic_retry
32
+ $logger.warn "Retrying #{test_case.name} due to dynamic retry set"
33
+ else
34
+ return false
35
+ end
36
+ increment_retry_count(test_case)
37
+ true
38
+ end
39
+
40
+ def retried_previously?(test_case)
41
+ global_retried[test_case] > 0
42
+ end
43
+
44
+ private
45
+
46
+ def increment_retry_count(test_case)
47
+ global_retried[test_case] += 1
48
+ end
49
+
50
+ def retry_on_driver_error?(event)
51
+ error_class = event.result.exception.class
52
+ maze_errors = Maze::Error::ERROR_CODES
53
+ Maze.driver && maze_errors.include?(error_class) && maze_errors[error_class][:retry]
54
+ end
55
+
56
+ def retry_on_tag?(test_case)
57
+ test_case.tags.any? do |tag|
58
+ RETRY_TAGS.include?(tag.name)
59
+ end
60
+ end
61
+
62
+ def global_retried
63
+ @global_retried ||= Hash.new { |h, k| h[k] = 0 }
64
+ end
65
+ end
66
+ end
67
+ end