bugsnag-maze-runner 6.27.0 → 7.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/bin/download-logs +14 -16
  3. data/bin/maze-runner +53 -15
  4. data/bin/upload-app +6 -6
  5. data/lib/features/steps/breadcrumb_steps.rb +44 -14
  6. data/lib/features/steps/error_reporting_steps.rb +16 -0
  7. data/lib/features/steps/network_steps.rb +66 -6
  8. data/lib/features/steps/payload_steps.rb +23 -0
  9. data/lib/features/steps/request_assertion_steps.rb +87 -8
  10. data/lib/features/steps/runner_steps.rb +22 -0
  11. data/lib/features/steps/session_tracking_steps.rb +1 -1
  12. data/lib/features/steps/trace_steps.rb +254 -0
  13. data/lib/features/support/internal_hooks.rb +31 -84
  14. data/lib/maze/api/appium/file_manager.rb +29 -0
  15. data/lib/maze/aws_public_ip.rb +53 -0
  16. data/lib/maze/checks/assert_check.rb +9 -31
  17. data/lib/maze/client/appium/base_client.rb +131 -0
  18. data/lib/maze/client/appium/bb_client.rb +102 -0
  19. data/lib/maze/client/appium/bb_devices.rb +127 -0
  20. data/lib/maze/client/appium/bs_client.rb +91 -0
  21. data/lib/maze/client/appium/bs_devices.rb +141 -0
  22. data/lib/maze/client/appium/bs_legacy_client.rb +31 -0
  23. data/lib/maze/client/appium/local_client.rb +67 -0
  24. data/lib/maze/client/appium.rb +23 -0
  25. data/lib/maze/client/bb_api_client.rb +102 -0
  26. data/lib/maze/client/bb_client_utils.rb +181 -0
  27. data/lib/maze/client/bs_client_utils.rb +168 -0
  28. data/lib/maze/client/selenium/base_client.rb +15 -0
  29. data/lib/maze/client/selenium/bb_browsers.yml +188 -0
  30. data/lib/maze/client/selenium/bb_client.rb +38 -0
  31. data/lib/maze/client/selenium/bs_browsers.yml +257 -0
  32. data/lib/maze/client/selenium/bs_client.rb +89 -0
  33. data/lib/maze/client/selenium/local_client.rb +16 -0
  34. data/lib/maze/client/selenium.rb +16 -0
  35. data/lib/maze/configuration.rb +18 -10
  36. data/lib/maze/docker.rb +40 -1
  37. data/lib/maze/driver/appium.rb +5 -24
  38. data/lib/maze/driver/browser.rb +12 -26
  39. data/lib/maze/errors.rb +32 -0
  40. data/lib/maze/generator.rb +55 -0
  41. data/lib/maze/helper.rb +7 -3
  42. data/lib/maze/hooks/appium_hooks.rb +29 -190
  43. data/lib/maze/hooks/browser_hooks.rb +2 -55
  44. data/lib/maze/hooks/error_code_hook.rb +49 -0
  45. data/lib/maze/hooks/hooks.rb +2 -2
  46. data/lib/maze/http_request.rb +21 -0
  47. data/lib/maze/logger.rb +16 -3
  48. data/lib/maze/maze_output.rb +88 -0
  49. data/lib/maze/option/parser.rb +17 -22
  50. data/lib/maze/option/processor.rb +21 -34
  51. data/lib/maze/option/validator.rb +38 -67
  52. data/lib/maze/option.rb +16 -18
  53. data/lib/maze/plugins/cucumber_report_plugin.rb +1 -1
  54. data/lib/maze/plugins/error_code_plugin.rb +21 -0
  55. data/lib/maze/request_list.rb +10 -5
  56. data/lib/maze/request_repeater.rb +49 -0
  57. data/lib/maze/retry_handler.rb +4 -13
  58. data/lib/maze/schemas/OtelTraceSchema.json +390 -0
  59. data/lib/maze/schemas/trace_schema.rb +7 -0
  60. data/lib/maze/schemas/trace_validator.rb +98 -0
  61. data/lib/maze/server.rb +74 -30
  62. data/lib/maze/servlets/base_servlet.rb +10 -5
  63. data/lib/maze/servlets/command_servlet.rb +10 -7
  64. data/lib/maze/servlets/log_servlet.rb +2 -2
  65. data/lib/maze/servlets/reflective_servlet.rb +12 -11
  66. data/lib/maze/servlets/servlet.rb +47 -8
  67. data/lib/maze/servlets/temp.rb +0 -0
  68. data/lib/maze/servlets/trace_servlet.rb +13 -0
  69. data/lib/maze.rb +2 -2
  70. data/lib/utils/deep_merge.rb +17 -0
  71. data/lib/utils/selenium_money_patch.rb +17 -0
  72. metadata +97 -17
  73. data/lib/maze/bitbar_devices.rb +0 -84
  74. data/lib/maze/bitbar_utils.rb +0 -112
  75. data/lib/maze/browser_stack_devices.rb +0 -160
  76. data/lib/maze/browser_stack_utils.rb +0 -164
  77. data/lib/maze/browsers_bs.yml +0 -220
  78. data/lib/maze/browsers_cbt.yml +0 -100
  79. data/lib/maze/capabilities.rb +0 -126
  80. data/lib/maze/driver/resilient_appium.rb +0 -51
  81. data/lib/maze/sauce_labs_utils.rb +0 -96
  82. data/lib/maze/smart_bear_utils.rb +0 -71
data/lib/maze/server.rb CHANGED
@@ -9,44 +9,63 @@ require_relative './request_list'
9
9
  module Maze
10
10
  # Receives and stores requests through a WEBrick HTTPServer
11
11
  class Server
12
+ ALLOWED_HTTP_VERBS = %w[OPTIONS GET POST PUT DELETE HEAD TRACE PATCH CONNECT]
13
+ DEFAULT_RESPONSE_DELAY = 0
14
+ DEFAULT_SAMPLING_PROBABILITY = 1
15
+ DEFAULT_STATUS_CODE = 200
12
16
 
13
17
  class << self
14
- # Allows overwriting of the server status code
15
- attr_writer :status_code
16
-
17
- # Dictates if the status code should be reset after use
18
- attr_writer :reset_status_code
19
-
20
- # Allows a delay in milliseconds before responding to HTTP requests to be set
21
- attr_writer :response_delay_ms
18
+ # Sets the response delay generator.
19
+ #
20
+ # @param generator [Maze::Generator] The new generator
21
+ def set_response_delay_generator(generator)
22
+ @response_delay_generator&.close
23
+ @response_delay_generator = generator
24
+ end
22
25
 
23
- # Dictates if the response delay should be reset after use
24
- attr_writer :reset_response_delay
26
+ # Sets the sampling probability generator.
27
+ #
28
+ # @param generator [Maze::Generator] The new generator
29
+ def set_sampling_probability_generator(generator)
30
+ @sampling_probability_generator&.close
31
+ @sampling_probability_generator = generator
32
+ end
25
33
 
26
- # @return [String] The UUID attached to all command requests for this session
27
- attr_reader :command_uuid
34
+ # Sets the status code generator for the HTTP verb given. If no verb is given then the
35
+ # generator will be shared across all allowable HTTP verbs.
36
+ #
37
+ # @param generator [Maze::Generator] The new generator
38
+ # @param verb [String] HTTP verb
39
+ def set_status_code_generator(generator, verb = nil)
40
+ @status_code_generators ||= {}
41
+ Array(verb || ALLOWED_HTTP_VERBS).each do |verb|
42
+ old = @status_code_generators[verb]
43
+ @status_code_generators[verb] = generator
44
+
45
+ # Close the old generator unless it's still being used by another verb
46
+ old&.close unless @status_code_generators.value?(old)
47
+ end
48
+ end
28
49
 
29
50
  # The intended HTTP status code on a successful request
30
51
  #
31
- # @return [Integer] The HTTP status code, defaults to 200
32
- def status_code
33
- code = @status_code ||= 200
34
- @status_code = 200 if reset_status_code
35
- code
52
+ # @param verb [String] HTTP verb for which the status code is wanted
53
+ #
54
+ # @return [Integer] The HTTP status code for the verb given
55
+ def status_code(verb)
56
+ if @status_code_generators[verb].nil? || @status_code_generators[verb].closed?
57
+ DEFAULT_STATUS_CODE
58
+ else
59
+ @status_code_generators[verb].next
60
+ end
36
61
  end
37
62
 
38
- def reset_status_code
39
- @reset_status_code ||= false
63
+ def sampling_probability
64
+ @sampling_probability_generator.next
40
65
  end
41
66
 
42
67
  def response_delay_ms
43
- delay = @response_delay_ms ||= 0
44
- @response_delay_ms = 0 if reset_response_delay
45
- delay
46
- end
47
-
48
- def reset_response_delay
49
- @reset_response_delay ||= false
68
+ @response_delay_generator.next
50
69
  end
51
70
 
52
71
  # Provides dynamic access to request lists by name
@@ -64,6 +83,8 @@ module Maze
64
83
  builds
65
84
  when 'log', 'logs'
66
85
  logs
86
+ when 'trace', 'traces'
87
+ traces
67
88
  when 'upload', 'uploads'
68
89
  uploads
69
90
  when 'sourcemap', 'sourcemaps'
@@ -89,6 +110,13 @@ module Maze
89
110
  @sessions ||= RequestList.new
90
111
  end
91
112
 
113
+ # A list of trace requests received
114
+ #
115
+ # @return [RequestList] Received error requests
116
+ def traces
117
+ @traces ||= RequestList.new
118
+ end
119
+
92
120
  # A list of build requests received
93
121
  #
94
122
  # @return [RequestList] Received build requests
@@ -145,9 +173,6 @@ module Maze
145
173
  # Starts the WEBrick server in a separate thread
146
174
  def start
147
175
  attempts = 0
148
- $logger.info 'Starting mock server'
149
- @command_uuid = SecureRandom.uuid
150
- $logger.info "Fixture commands UUID: #{@command_uuid}"
151
176
  loop do
152
177
 
153
178
  @thread = Thread.new do
@@ -161,7 +186,7 @@ module Maze
161
186
 
162
187
  # Mount a block to respond to all requests with status:200
163
188
  server.mount_proc '/' do |_request, response|
164
- $logger.info 'Received request on server root, responding with 200'
189
+ $logger.debug 'Received request on server root, responding with 200'
165
190
  response.header['Access-Control-Allow-Origin'] = '*'
166
191
  response.body = 'Maze runner received request'
167
192
  response.status = 200
@@ -173,9 +198,11 @@ module Maze
173
198
  server.mount '/builds', Servlets::Servlet, :builds
174
199
  server.mount '/uploads', Servlets::Servlet, :uploads
175
200
  server.mount '/sourcemap', Servlets::Servlet, :sourcemaps
201
+ server.mount '/traces', Servlets::TraceServlet, :traces, Maze::Schemas::TRACE_SCHEMA
176
202
  server.mount '/react-native-source-map', Servlets::Servlet, :sourcemaps
177
203
  server.mount '/command', Servlets::CommandServlet
178
204
  server.mount '/logs', Servlets::LogServlet
205
+ server.mount '/reflect', Servlets::ReflectiveServlet
179
206
  server.start
180
207
  rescue StandardError => e
181
208
  $logger.warn "Failed to start mock server: #{e.message}"
@@ -202,6 +229,23 @@ module Maze
202
229
  @thread&.kill if @thread&.alive?
203
230
  @thread = nil
204
231
  end
232
+
233
+ def reset!
234
+ # Reset generators
235
+ set_response_delay_generator(Maze::Generator.new [DEFAULT_RESPONSE_DELAY].cycle)
236
+ set_status_code_generator(Maze::Generator.new [DEFAULT_STATUS_CODE].cycle)
237
+ set_sampling_probability_generator(Maze::Generator.new [DEFAULT_SAMPLING_PROBABILITY].cycle)
238
+
239
+ # Clear request lists
240
+ errors.clear
241
+ sessions.clear
242
+ builds.clear
243
+ uploads.clear
244
+ sourcemaps.clear
245
+ traces.clear
246
+ logs.clear
247
+ invalid_requests.clear
248
+ end
205
249
  end
206
250
  end
207
251
  end
@@ -11,11 +11,16 @@ module Maze
11
11
  # @param response [HTTPResponse] The response to return
12
12
  def do_OPTIONS(request, response)
13
13
  response.header['Access-Control-Allow-Origin'] = '*'
14
- response.header['Access-Control-Allow-Headers'] = %w[Accept
15
- Bugsnag-Api-Key Bugsnag-Integrity
16
- Bugsnag-Payload-Version
17
- Bugsnag-Sent-At Content-Type
18
- Origin].join(',')
14
+ response.header['Access-Control-Allow-Headers'] = %w[
15
+ Accept
16
+ Bugsnag-Api-Key
17
+ Bugsnag-Integrity
18
+ Bugsnag-Payload-Version
19
+ Bugsnag-Sent-At
20
+ Bugsnag-Span-Sampling
21
+ Content-Type
22
+ Origin
23
+ ].join(',')
19
24
  end
20
25
  end
21
26
  end
@@ -16,14 +16,15 @@ module Maze
16
16
  response.header['Access-Control-Allow-Origin'] = '*'
17
17
 
18
18
  commands = Maze::Server.commands
19
- # Note that empty? is not the same as size == 0 (design bug to be corrected in v7)
20
- if commands.size == 0
21
- response.body = 'No commands to provide'
22
- response.status = 400
19
+
20
+ if commands.size_remaining == 0
21
+ response.body = '{"action": "noop", "message": "No commands queued"}'
22
+ response.status = 200
23
23
  else
24
24
  command = commands.current
25
- command[:uuid] = Maze::Server.command_uuid
26
- response.body = JSON.pretty_generate(command)
25
+ command_json = JSON.pretty_generate(command)
26
+ command[:uuid] = Maze.run_uuid
27
+ response.body = command_json
27
28
  response.status = 200
28
29
  commands.next
29
30
  end
@@ -37,8 +38,10 @@ module Maze
37
38
  super
38
39
 
39
40
  response.header['Access-Control-Allow-Methods'] = 'POST, OPTIONS'
40
- response.status = Server.status_code
41
+ response.status = Server.status_code('OPTIONS')
41
42
  end
42
43
  end
43
44
  end
44
45
  end
46
+
47
+
@@ -27,7 +27,7 @@ module Maze
27
27
  @requests.add(hash)
28
28
 
29
29
  response.header['Access-Control-Allow-Origin'] = '*'
30
- response.status = Server.status_code
30
+ response.status = Server.status_code('POST')
31
31
  rescue JSON::ParserError => e
32
32
  msg = "Unable to parse request as JSON: #{e.message}"
33
33
  $logger.error msg
@@ -57,7 +57,7 @@ module Maze
57
57
  super
58
58
 
59
59
  response.header['Access-Control-Allow-Methods'] = 'POST, OPTIONS'
60
- response.status = Server.status_code
60
+ response.status = Server.status_code('OPTIONS')
61
61
  end
62
62
  end
63
63
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require 'rack'
2
3
 
3
4
  module Maze
4
5
  module Servlets
@@ -7,7 +8,7 @@ module Maze
7
8
  # - status - HTTP response code
8
9
  # For GET requests these are expected to passed as GET parameters,
9
10
  # for POST requests they are expected to be given as JSON fields.
10
- class ReflectiveServlet < WEBrick::HTTPServlet::AbstractServlet
11
+ class ReflectiveServlet < BaseServlet
11
12
 
12
13
  # Accepts a GET request to provide a reflective response to.
13
14
  #
@@ -26,17 +27,17 @@ module Maze
26
27
  def do_POST(request, response)
27
28
 
28
29
  content_type = request['Content-Type']
29
- unless content_type == 'application/json'
30
- msg = "Content-Type '#{content_type}' not supported - only application/json is supported at present"
31
- $logger.error msg
32
- response.status = 415
33
- response.body = msg
34
- return
35
- end
36
30
 
37
- body = JSON.parse(request.body)
38
- delay_ms = body['delay_ms']
39
- status = body['status']
31
+ # For JSON, pull the instructions from the body. Otherwise, take them from the query string.
32
+ if content_type == 'application/json'
33
+ body = JSON.parse(request.body)
34
+ delay_ms = body['delay_ms']
35
+ status = body['status']
36
+ else
37
+ query = Rack::Utils.parse_nested_query(request.query_string)
38
+ delay_ms = query['delay_ms']
39
+ status = query['status']
40
+ end
40
41
 
41
42
  reflect response, delay_ms, status
42
43
  rescue JSON::ParserError => e
@@ -1,18 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'zlib'
4
+ require 'stringio'
5
+ require 'json_schemer'
6
+ require 'delegate'
7
+
3
8
  module Maze
9
+ class HttpRequest < SimpleDelegator
10
+ def body
11
+ @body ||= decode_body
12
+ end
13
+
14
+ private
15
+
16
+ def decode_body
17
+ delegate = __getobj__
18
+ if %r{^gzip$}.match(delegate['Content-Encoding'])
19
+ gz_element = Zlib::GzipReader.new(StringIO.new(delegate.body))
20
+ gz_element.read
21
+ else
22
+ delegate.body
23
+ end
24
+ end
25
+ end
26
+
4
27
  module Servlets
5
28
 
6
29
  # Receives and parses the requests and payloads sent from the test fixture
7
30
  class Servlet < BaseServlet
31
+ prepend RequestRepeater
32
+
8
33
  # Constructor
9
34
  #
10
35
  # @param server [HTTPServer] WEBrick HTTPServer
11
36
  # @param request_type [Symbol] Request type that the servlet will receive
12
- def initialize(server, request_type)
37
+ # @param schema [Dictionary] A `json-schema` describing the payload for POST requests
38
+ def initialize(server, request_type, schema=nil)
13
39
  super server
14
40
  @request_type = request_type
15
41
  @requests = Server.list_for request_type
42
+ @schema = JSONSchemer.schema(schema) unless schema.nil?
16
43
  end
17
44
 
18
45
  # Logs an incoming GET WEBrick request.
@@ -30,9 +57,11 @@ module Maze
30
57
  # @param request [HTTPRequest] The incoming GET request
31
58
  # @param response [HTTPResponse] The response to return
32
59
  def do_POST(request, response)
33
- log_request(request)
34
- case request['Content-Type']
35
- when %r{^multipart/form-data; boundary=([^;]+)}
60
+ # Turn the WEBrick HttpRequest into our internal HttpRequest delegate
61
+ request = HttpRequest.new(request)
62
+
63
+ content_type = request['Content-Type']
64
+ if %r{^multipart/form-data; boundary=([^;]+)}.match(content_type)
36
65
  boundary = WEBrick::HTTPUtils::dequote($1)
37
66
  body = WEBrick::HTTPUtils.parse_form_data(request.body, boundary)
38
67
  hash = {
@@ -50,6 +79,10 @@ module Maze
50
79
  digests: digests
51
80
  }
52
81
  end
82
+ if @schema
83
+ schema_errors = @schema.validate(hash[:body])
84
+ hash[:schema_errors] = schema_errors.to_a
85
+ end
53
86
  @requests.add(hash)
54
87
 
55
88
  # For the response, delaying if configured to do so
@@ -58,8 +91,8 @@ module Maze
58
91
  $logger.info "Waiting #{response_delay_ms} milliseconds before responding"
59
92
  sleep response_delay_ms / 1000.0
60
93
  end
61
- response.header['Access-Control-Allow-Origin'] = '*'
62
- response.status = Server.status_code
94
+ set_response_header response.header
95
+ response.status = Server.status_code('POST')
63
96
  rescue JSON::ParserError => e
64
97
  msg = "Unable to parse request as JSON: #{e.message}"
65
98
  if Maze.config.captured_invalid_requests.include? @request_type
@@ -89,6 +122,10 @@ module Maze
89
122
  end
90
123
  end
91
124
 
125
+ def set_response_header(header)
126
+ header['Access-Control-Allow-Origin'] = '*'
127
+ end
128
+
92
129
  # Logs and returns a set of valid headers for this servlet.
93
130
  #
94
131
  # @param request [HTTPRequest] The incoming GET request
@@ -97,7 +134,7 @@ module Maze
97
134
  super
98
135
 
99
136
  response.header['Access-Control-Allow-Methods'] = 'POST, OPTIONS'
100
- response.status = Server.status_code
137
+ response.status = Server.status_code('OPTIONS')
101
138
  end
102
139
 
103
140
  private
@@ -116,8 +153,10 @@ module Maze
116
153
  body = WEBrick::HTTPUtils.parse_form_data(request.body, boundary)
117
154
  $logger.debug 'BODY:'
118
155
  LogUtil.log_hash(Logger::Severity::DEBUG, body)
119
- else
156
+ when %r{^application/json$}
120
157
  $logger.debug "BODY: #{JSON.pretty_generate(JSON.parse(request.body))}"
158
+ else
159
+ $logger.debug "BODY: #{request.body}"
121
160
  end
122
161
  end
123
162
 
File without changes
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maze
4
+ module Servlets
5
+ class TraceServlet < Servlet
6
+ def set_response_header(header)
7
+ super
8
+ value = Maze::Server.sampling_probability
9
+ header['Bugsnag-Sampling-Probability'] = value unless value == 'null'
10
+ end
11
+ end
12
+ end
13
+ end
data/lib/maze.rb CHANGED
@@ -7,10 +7,10 @@ require_relative 'maze/timers'
7
7
  # Glues the various parts of MazeRunner together that need to be accessed globally,
8
8
  # providing an alternative to the proliferation of global variables or singletons.
9
9
  module Maze
10
- VERSION = '6.27.0'
10
+ VERSION = '7.23.0'
11
11
 
12
12
  class << self
13
- attr_accessor :check, :driver, :internal_hooks, :mode, :start_time, :dynamic_retry
13
+ attr_accessor :check, :driver, :internal_hooks, :mode, :start_time, :dynamic_retry, :public_address, :run_uuid
14
14
 
15
15
  def config
16
16
  @config ||= Maze::Configuration.new
@@ -0,0 +1,17 @@
1
+ class Hash
2
+ def deep_merge!(other_hash, &block)
3
+ merge!(other_hash) do |key, this_val, other_val|
4
+ if this_val.is_a?(Hash) && other_val.is_a?(Hash)
5
+ this_val.deep_merge(other_val, &block)
6
+ elsif block_given?
7
+ block.call(key, this_val, other_val)
8
+ else
9
+ other_val
10
+ end
11
+ end
12
+ end
13
+
14
+ def deep_merge(other_hash, &block)
15
+ dup.deep_merge!(other_hash, &block)
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # Log the full response so we see more than just the error code
2
+ module Selenium
3
+ module WebDriver
4
+ module Error
5
+ class ServerError < StandardError
6
+ def initialize(response)
7
+ if response.is_a? String
8
+ super(response)
9
+ else
10
+ $logger.error "Server response: #{response.inspect}"
11
+ super("status code #{response.code}")
12
+ end
13
+ end
14
+ end # ServerError
15
+ end # Error
16
+ end # WebDriver
17
+ end # Selenium