bugsnag-maze-runner 6.27.0 → 7.22.1

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 +206 -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 +101 -21
  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
@@ -0,0 +1,55 @@
1
+ # A thread-safe generator of values, backed by an Enumerator.
2
+ module Maze
3
+ class Generator
4
+ def initialize(enumerator)
5
+ # A SizedQueue allows a set number of values to always be ready for clients
6
+ # and will be automatically topped up from the enumerator.
7
+ @queue = SizedQueue.new(10)
8
+
9
+ # The queue filler continually adds to the queue (when there is room), taking
10
+ # values from the Enumerator. This ensure the enumerator is always run inside
11
+ # the same thread
12
+ @queue_filler = create_queue_filler(enumerator)
13
+
14
+ while @queue.empty? && enumerator.size != 0
15
+ # Wait for the queue to start filling
16
+ end
17
+ end
18
+
19
+ # @return The next value
20
+ def next
21
+ @queue.pop
22
+ end
23
+
24
+ # @return Whether the generator has been closed
25
+ def closed?
26
+ @queue.closed?
27
+ end
28
+
29
+ # Cleans up resources used by the generator
30
+ def close
31
+ @queue_filler.exit
32
+ @queue.close
33
+ end
34
+
35
+ private
36
+
37
+ # Create a thread that will constantly append to @queue with values from the
38
+ # given enumerator
39
+ #
40
+ # @param enumerator [Enumerator]
41
+ # @return [Thread]
42
+ def create_queue_filler(enumerator)
43
+ # By passing the enumerator as an argument to Thread.new, it creates a copy
44
+ # local to that thread and therefore we're not sharing an enumerator across
45
+ # threads
46
+ Thread.new(enumerator) do |inner|
47
+ loop do
48
+ # Add to the queue until it fills up, this will then block until there's
49
+ # room in the queue again
50
+ @queue << inner.next
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
data/lib/maze/helper.rb CHANGED
@@ -94,9 +94,7 @@ module Maze
94
94
  else
95
95
  os = case Maze.config.farm
96
96
  when :bs
97
- Maze.config.capabilities['os']
98
- when :sl
99
- Maze.driver.capabilities['platformName']
97
+ Maze.config.capabilities['platformName']
100
98
  else
101
99
  Maze.config.os
102
100
  end
@@ -108,6 +106,12 @@ module Maze
108
106
  os
109
107
  end
110
108
 
109
+ # Logs the given message and exits the program with a failure status
110
+ def error_exit(message)
111
+ $logger.error message
112
+ exit false
113
+ end
114
+
111
115
  # Returns the name of the scenario to
112
116
  # @param string [String] a string to convert to a file name
113
117
  def to_friendly_filename(string)
@@ -3,213 +3,52 @@ module Maze
3
3
  module Hooks
4
4
  # Hooks for Appium mode use
5
5
  class AppiumHooks < InternalHooks
6
- def before_all
7
- # Setup Appium capabilities. Note that the 'app' capability is
8
- # set in a hook as it will change if uploaded to BrowserStack.
9
-
10
- config = Maze.config
11
- case config.farm
12
- when :bs
13
- tunnel_id = SecureRandom.uuid
14
- config.app = Maze::BrowserStackUtils.upload_app config.username,
15
- config.access_key,
16
- config.app
17
- Maze::BrowserStackUtils.start_local_tunnel config.bs_local,
18
- tunnel_id,
19
- config.access_key
20
- when :sl
21
- config.app = Maze::SauceLabsUtils.upload_app config.username,
22
- config.access_key,
23
- config.app
24
- tunnel_id = SecureRandom.uuid
25
- Maze::SauceLabsUtils.start_sauce_connect config.sl_local,
26
- tunnel_id,
27
- config.username,
28
- config.access_key
29
- when :bb
30
- if ENV['BUILDKITE']
31
- credentials = Maze::BitBarUtils.account_credentials config.tms_uri
32
- config.username = credentials[:username]
33
- config.access_key = credentials[:access_key]
34
- end
35
- config.app = Maze::BitBarUtils.upload_app config.access_key,
36
- config.app
37
- Maze::SmartBearUtils.start_local_tunnel config.sb_local,
38
- config.username,
39
- config.access_key
40
- when :local
41
- # Attempt to start the local appium server
42
- appium_uri = URI(config.appium_server_url)
43
- Maze::AppiumServer.start(address: appium_uri.host, port: appium_uri.port) if config.start_appium
44
- end
6
+ @client
45
7
 
46
- start_driver(config, tunnel_id)
47
-
48
- # Ensure the device is unlocked
49
- Maze.driver.unlock
50
-
51
- # Write links to device farm sessions, where applicable
52
- write_session_link
8
+ def before_all
9
+ @client = Maze::Client::Appium.start
53
10
  end
54
11
 
55
- def before
56
- Maze.driver.start_driver if Maze.config.farm != :none && Maze.config.appium_session_isolation
57
-
58
- # Launch the app on macOS, if Appium is being used
59
- Maze.driver.get(Maze.config.app) if Maze.driver && Maze.config.os == 'macos'
12
+ def before(scenario)
13
+ @client.start_scenario
60
14
  end
61
15
 
62
- def after
63
- if Maze.config.appium_session_isolation
64
- Maze.driver.driver_quit
65
- elsif Maze.config.os == 'macos'
16
+ def after(scenario)
17
+
18
+ if Maze.config.os == 'macos'
66
19
  # Close the app - without the sleep, launching the app for the next scenario intermittently fails
67
20
  system("killall -KILL #{Maze.config.app} && sleep 1")
68
- elsif [:bb].include? Maze.config.farm
69
- Maze.driver.launch_app
70
- else
71
- Maze.driver.reset
72
- end
73
- end
74
-
75
- def at_exit
76
- # Stop the Appium session and server
77
- Maze.driver.driver_quit unless Maze.config.appium_session_isolation
78
- Maze::AppiumServer.stop if Maze::AppiumServer.running
79
-
80
- if Maze.config.farm == :local && Maze.config.os == 'macos'
81
- # Acquire and output the logs for the current session
82
- Maze::Runner.run_command("log show --predicate '(process == \"#{Maze.config.app}\")' --style syslog --start '#{Maze.start_time}' > #{Maze.config.app}.log")
83
- elsif Maze.config.farm == :bs
84
- Maze::BrowserStackUtils.stop_local_tunnel
85
- elsif Maze.config.farm == :sl
86
- $logger.info 'Stopping Sauce Connect'
87
- Maze::SauceLabsUtils.stop_sauce_connect
88
- elsif Maze.config.farm == :bb
89
- Maze::SmartBearUtils.stop_local_tunnel
90
- Maze::BitBarUtils.release_account(Maze.config.tms_uri) if ENV['BUILDKITE']
91
- end
92
- end
21
+ elsif [:bb, :bs, :local].include? Maze.config.farm
22
+ write_device_logs(scenario) if scenario.failed?
93
23
 
94
- def write_session_link
95
- config = Maze.config
96
- if config.farm == :bs && (config.device || config.browser)
97
- # Log a link to the BrowserStack session search dashboard
98
- build = Maze.driver.capabilities[:build]
99
- url = "https://app-automate.browserstack.com/dashboard/v2/search?query=#{build}&type=builds"
100
- if ENV['BUILDKITE']
101
- $logger.info Maze::LogUtil.linkify url, 'BrowserStack session(s)'
102
- else
103
- $logger.info "BrowserStack session(s): #{url}"
104
- end
24
+ # appium_lib 12 says that reset is deprecated and activate_app/terminate_app should be used
25
+ # instead. However, they do not clear out app data, which we need between scenarios.
26
+ # install_app/remove_app might also be an option to consider.
27
+ Maze.driver.reset
105
28
  end
106
29
  end
107
30
 
108
- def device_capabilities(config, tunnel_id = nil)
109
- case config.farm
110
- when :bs
111
- capabilities = Maze::Capabilities.for_browser_stack_device config.device,
112
- tunnel_id,
113
- config.appium_version,
114
- config.capabilities_option
115
- capabilities['app'] = config.app
116
- when :sl
117
- capabilities = Maze::Capabilities.for_sauce_labs_device config.device,
118
- config.os,
119
- config.os_version,
120
- tunnel_id,
121
- config.appium_version,
122
- config.capabilities_option
123
- capabilities['app'] = "storage:#{config.app}"
124
- when :local
125
- capabilities = Maze::Capabilities.for_local config.os,
126
- config.capabilities_option,
127
- config.apple_team_id,
128
- config.device_id
129
- capabilities['app'] = config.app
130
- when :bb
131
- capabilities = Maze::Capabilities.for_bitbar_device config.access_key,
132
- config.device,
133
- config.os,
134
- config.os_version,
135
- config.capabilities_option
136
- capabilities['bitbar_app'] = config.app
137
- capabilities['bundleId'] = config.app_bundle_id
138
- end
139
- capabilities
31
+ def after_all
32
+ @client&.log_run_outro
140
33
  end
141
34
 
142
- def create_driver(config)
143
- if Maze.config.resilient
144
- $logger.info 'Creating ResilientAppium driver instance'
145
- Maze::Driver::ResilientAppium.new config.appium_server_url,
146
- config.capabilities,
147
- config.locator
148
- else
149
- $logger.info 'Creating Appium driver instance'
150
- Maze::Driver::Appium.new config.appium_server_url,
151
- config.capabilities,
152
- config.locator
153
- end
35
+ def at_exit
36
+ @client&.stop_session
154
37
  end
155
38
 
156
- def start_driver(config, tunnel_id = nil)
157
- retry_failure = config.device_list.nil? || config.device_list.empty?
158
- until Maze.driver
159
- begin
160
- config.capabilities = device_capabilities(config, tunnel_id)
161
- driver = create_driver(config)
162
-
163
- start_driver_closure = Proc.new do
164
- begin
165
- driver.start_driver
166
- true
167
- rescue => start_error
168
- raise start_error unless retry_failure
169
- false
170
- end
171
- end
39
+ private
172
40
 
173
- unless config.appium_session_isolation
174
- if retry_failure
175
- wait = Maze::Wait.new(interval: 10, timeout: 60)
176
- success = wait.until(&start_driver_closure)
41
+ # Pulls the device logs using Appium and writes them to file in the maze_output folder
42
+ def write_device_logs(scenario)
43
+ log_name = case Maze::Helper.get_current_platform
44
+ when 'android'
45
+ 'logcat'
46
+ when 'ios'
47
+ 'syslog'
48
+ end
49
+ logs = Maze.driver.get_log(log_name)
177
50
 
178
- unless success
179
- $logger.error 'Appium driver failed to start after 6 attempts in 60 seconds'
180
- raise RuntimeError.new('Appium driver failed to start in 60 seconds')
181
- end
182
- else
183
- start_driver_closure.call
184
- end
185
- end
186
-
187
- # Infer OS version if necessary when running locally
188
- if Maze.config.farm == :local && Maze.config.os_version.nil?
189
- version = case Maze.config.os
190
- when 'android'
191
- driver.session_capabilities['platformVersion'].to_f
192
- when 'ios'
193
- driver.session_capabilities['sdkVersion'].to_f
194
- end
195
- $logger.info "Inferred OS version to be #{version}"
196
- Maze.config.os_version = version
197
- end
198
-
199
- Maze.driver = driver
200
- rescue Selenium::WebDriver::Error::UnknownError => original_exception
201
- $logger.warn "Attempt to acquire #{config.device} device from farm #{config.farm} failed"
202
- $logger.warn "Exception: #{original_exception.message}"
203
- if config.device_list.empty?
204
- $logger.error 'No further devices to try - raising original exception'
205
- raise original_exception
206
- else
207
- config.device = config.device_list.first
208
- config.device_list = config.device_list.drop(1)
209
- $logger.warn "Retrying driver initialisation using next device: #{config.device}"
210
- end
211
- end
212
- end
51
+ Maze::MazeOutput.new(scenario).write_device_logs(logs)
213
52
  end
214
53
  end
215
54
  end
@@ -4,64 +4,11 @@ module Maze
4
4
  # Hooks for Browser mode use
5
5
  class BrowserHooks < InternalHooks
6
6
  def before_all
7
- config = Maze.config
8
- case config.farm
9
- when :bs
10
- # BrowserStack browser
11
- tunnel_id = SecureRandom.uuid
12
- config.capabilities = Maze::Capabilities.for_browser_stack_browser config.browser,
13
- tunnel_id,
14
- config.capabilities_option
15
- Maze::BrowserStackUtils.start_local_tunnel config.bs_local,
16
- tunnel_id,
17
- config.access_key
18
- when :cbt
19
- # CrossBrowserTesting browser
20
- tunnel_id = SecureRandom.uuid
21
- config.capabilities = Maze::Capabilities.for_cbt_browser config.browser,
22
- tunnel_id,
23
- config.capabilities_option
24
- Maze::SmartBearUtils.start_local_tunnel config.sb_local,
25
- config.username,
26
- config.access_key,
27
- tunnel_id
28
- end
29
-
30
- # Create and start the relevant driver
31
- case config.farm
32
- when :cbt
33
- selenium_url = "http://#{config.username}:#{config.access_key}@hub.crossbrowsertesting.com:80/wd/hub"
34
- Maze.driver = Maze::Driver::Browser.new :remote, selenium_url, config.capabilities
35
- when :bs
36
- selenium_url = "http://#{config.username}:#{config.access_key}@hub.browserstack.com/wd/hub"
37
- Maze.driver = Maze::Driver::Browser.new :remote, selenium_url, config.capabilities
38
- when :local
39
- Maze.driver = Maze::Driver::Browser.new Maze.config.browser.to_sym
40
- end
41
-
42
- # Write links to device farm sessions, where applicable
43
- write_session_link
7
+ @client = Maze::Client::Selenium.start
44
8
  end
45
9
 
46
10
  def at_exit
47
- if Maze.config.farm == :bs
48
- Maze::BrowserStackUtils.stop_local_tunnel
49
- Maze.driver.driver_quit
50
- end
51
- end
52
-
53
- def write_session_link
54
- config = Maze.config
55
- if config.farm == :bs
56
- # Log a link to the BrowserStack session search dashboard
57
- build = Maze.driver.capabilities[:build]
58
- url = "https://automate.browserstack.com/dashboard/v2/search?query=#{build}&type=builds"
59
- if ENV['BUILDKITE']
60
- $logger.info Maze::LogUtil.linkify url, 'BrowserStack session(s)'
61
- else
62
- $logger.info "BrowserStack session(s): #{url}"
63
- end
64
- end
11
+ @client.stop_session
65
12
  end
66
13
  end
67
14
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maze
4
+ module Hooks
5
+ # Registers an exit hook that will process the reason for an early exit and provide a suitable error code.
6
+ # Error code sets and specific code meanings are as follows:
7
+ # 1*: An error has occurred within browser or device drivers:
8
+ # 10: An unknown error has occurred
9
+ # 11: An expected UI element was missing
10
+ # 12: A UI element was missing at time of interaction
11
+ # 13: A command sent to the remote server timed out
12
+ # 14: An element was present but did not accept interaction
13
+ # 2*: Errors relating to potential network, test server, or payload issues
14
+ # 21: Expected payload(s) was not received
15
+ # 22: A command was not read by the test fixture
16
+ class ErrorCodeHook
17
+ class << self
18
+
19
+ attr_accessor :exit_code
20
+ attr_accessor :last_test_error_class
21
+
22
+ def register_exit_code_hook
23
+ return if @registered
24
+ at_exit do
25
+ exit_hook
26
+ end
27
+ @registered = true
28
+ end
29
+
30
+ private
31
+
32
+ def exit_hook
33
+ override_exit_code = nil
34
+
35
+ maze_errors = Maze::Error::ERROR_CODES
36
+ if maze_errors.include?(last_test_error_class)
37
+ override_exit_code = maze_errors[last_test_error_class][:error_code]
38
+ end
39
+
40
+ # Check if a specific error code has been registered elsewhere
41
+ override_exit_code = @exit_code if @exit_code
42
+
43
+ # If an override code is specified, use it, otherwise we'll use the native exit code
44
+ exit(override_exit_code) unless override_exit_code.nil?
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -49,9 +49,9 @@ module Maze
49
49
  class InternalHooks
50
50
  def before_all; end
51
51
 
52
- def before; end
52
+ def before(_scenario); end
53
53
 
54
- def after; end
54
+ def after(_scenario); end
55
55
 
56
56
  def after_all; end
57
57
 
@@ -0,0 +1,21 @@
1
+ require 'delegate'
2
+
3
+ module Maze
4
+ class HttpRequest < SimpleDelegator
5
+ def body
6
+ @body ||= decode_body
7
+ end
8
+
9
+ private
10
+
11
+ def decode_body
12
+ delegate = __getobj__
13
+ if %r{^gzip$}.match(delegate['Content-Encoding'])
14
+ gz_element = Zlib::GzipReader.new(StringIO.new(delegate.body))
15
+ gz_element.read
16
+ else
17
+ delegate.body
18
+ end
19
+ end
20
+ end
21
+ end
data/lib/maze/logger.rb CHANGED
@@ -7,8 +7,10 @@ require 'singleton'
7
7
  module Maze
8
8
  # A logger, with level configured according to the environment
9
9
  class Logger < Logger
10
-
11
10
  include Singleton
11
+
12
+ attr_accessor :datetime_format
13
+
12
14
  def initialize
13
15
  if ENV['VERBOSE'] || ENV['DEBUG']
14
16
  super(STDOUT, level: Logger::DEBUG)
@@ -17,7 +19,14 @@ module Maze
17
19
  else
18
20
  super(STDOUT, level: Logger::INFO)
19
21
  end
20
- self.datetime_format = '%Y-%m-%d %H:%M:%S'
22
+
23
+ @datetime_format = '%H:%M:%S'
24
+
25
+ @formatter = proc do |severity, time, _name, message|
26
+ formatted_time = time.strftime(@datetime_format)
27
+
28
+ "\e[2m[#{formatted_time}]\e[0m #{severity.rjust(5)}: #{message}\n"
29
+ end
21
30
  end
22
31
  end
23
32
 
@@ -66,7 +75,11 @@ module Maze
66
75
  # @param url [String] Link URL
67
76
  # @param text [String] Link text
68
77
  def linkify(url, text)
69
- "\033]1339;url='#{url}';content='#{text}'\a"
78
+ if ENV['BUILDKITE']
79
+ "\033]1339;url='#{url}';content='#{text}'\a"
80
+ else
81
+ "#{text}: #{url}"
82
+ end
70
83
  end
71
84
  end
72
85
  end
@@ -0,0 +1,88 @@
1
+ module Maze
2
+ # Responsible for writing files to the maze_output directory
3
+ class MazeOutput
4
+
5
+ # @param scenario The Cucumber scenario
6
+ def initialize(scenario)
7
+ @scenario = scenario
8
+ end
9
+
10
+ # Writes each list of requests to a separate file under, e.g:
11
+ # maze_output/failed/scenario_name/errors.log
12
+ def write_requests
13
+ path = output_folder
14
+ FileUtils.makedirs(path)
15
+
16
+ request_types = %w[errors sessions builds uploads logs sourcemaps traces invalid]
17
+
18
+ request_types.each do |request_type|
19
+ list = Maze::Server.list_for(request_type).all
20
+ next if list.empty?
21
+
22
+ filename = "#{request_type}.log"
23
+ filepath = File.join(path, filename)
24
+
25
+ counter = 1
26
+ File.open(filepath, 'w+') do |file|
27
+ list.each do |request|
28
+ file.puts "=== Request #{counter} of #{list.size} ==="
29
+ if request[:invalid]
30
+ invalid_request = true
31
+ uri = request[:request][:request_uri]
32
+ headers = request[:request][:header]
33
+ body = request[:request][:body]
34
+ else
35
+ invalid_request = false
36
+ uri = request[:request].request_uri
37
+ headers = request[:request].header
38
+ body = request[:body]
39
+ end
40
+ file.puts "URI: #{uri}"
41
+ file.puts "HEADERS:"
42
+ headers.each do |key, values|
43
+ file.puts " #{key}: #{values.map {|v| "'#{v}'"}.join(' ')}"
44
+ end
45
+ file.puts
46
+ file.puts "BODY:"
47
+ if !invalid_request && headers["content-type"].first == 'application/json'
48
+ file.puts JSON.pretty_generate(body)
49
+ else
50
+ file.puts body
51
+ end
52
+ file.puts
53
+ if request.include?(:reason)
54
+ file.puts "REASON:"
55
+ file.puts request[:reason]
56
+ file.puts
57
+ end
58
+ counter += 1
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ # Pulls the logs from the device if the scenario fails
65
+ # @param logs [Array<String>] The lines of log to be written
66
+ def write_device_logs(logs)
67
+
68
+ dir = output_folder
69
+ FileUtils.makedirs(dir)
70
+ filepath = File.join(dir, 'device.log')
71
+
72
+ File.open(filepath, 'w+') do |file|
73
+ logs.each { |line| file.puts line }
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ # Determines the output folder for the scenario
80
+ def output_folder
81
+ folder1 = File.join(Dir.pwd, 'maze_output')
82
+ folder2 = @scenario.failed? ? 'failed' : 'passed'
83
+ folder3 = Maze::Helper.to_friendly_filename(@scenario.name)
84
+
85
+ File.join(folder1, folder2, folder3)
86
+ end
87
+ end
88
+ end