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/docker.rb CHANGED
@@ -54,6 +54,45 @@ module Maze
54
54
  @services_started = true
55
55
  end
56
56
 
57
+ # Execute a command in an already running docker container. Use {start_service}
58
+ # to build and run the service before calling this method
59
+ #
60
+ # This is equivalent to the Docker Compose 'exec' command:
61
+ # https://docs.docker.com/engine/reference/commandline/compose_exec/
62
+ #
63
+ # @param service [String] The name of the service. This must already be running
64
+ # @param command [String] The command to run
65
+ # @param detach [Boolean] Optional. Whether to run detached
66
+ def exec(service, command, detach: false)
67
+ flags = detach ? " --detach" : ""
68
+
69
+ run_docker_compose_command("exec #{flags} #{service} /bin/sh -c '#{command}'")
70
+ end
71
+
72
+ # Copy a file or directory from a container to the local filesystem
73
+ #
74
+ # This is equivalent to the Docker Compose 'cp' command:
75
+ # https://docs.docker.com/engine/reference/commandline/compose_cp/
76
+ #
77
+ # @param service [String] The name of the service to copy from. This must already be running
78
+ # @param from [String] The path to the file/directory that should be copied
79
+ # @param to [String] The path to copy the file/directory to
80
+ def copy_from_container(service, from:, to:)
81
+ run_docker_compose_command("cp #{service}:#{from} #{to}", success_codes: [0])
82
+ end
83
+
84
+ # Copy a file or directory from the local filesystem to a container
85
+ #
86
+ # This is equivalent to the Docker Compose 'cp' command:
87
+ # https://docs.docker.com/engine/reference/commandline/compose_cp/
88
+ #
89
+ # @param service [String] The name of the service to copy to. This must already be running
90
+ # @param from [String] The path to the file/directory that should be copied
91
+ # @param to [String] The path to copy the file/directory to
92
+ def copy_to_container(service, from:, to:)
93
+ run_docker_compose_command("cp #{from} #{service}:#{to}", success_codes: [0])
94
+ end
95
+
57
96
  # Kills a running service
58
97
  #
59
98
  # @param service [String] The name of the service to kill
@@ -91,7 +130,7 @@ module Maze
91
130
  def get_docker_compose_command(command)
92
131
  project_name = compose_project_name.nil? ? '' : "-p #{compose_project_name}"
93
132
 
94
- "docker-compose #{project_name} -f #{COMPOSE_FILENAME} #{command}"
133
+ "docker compose #{project_name} -f #{COMPOSE_FILENAME} #{command}"
95
134
  end
96
135
 
97
136
  def run_docker_compose_command(command, success_codes: nil)
@@ -10,6 +10,10 @@ module Maze
10
10
  # Provide a thin layer of abstraction above @see Appium::Driver
11
11
  class Appium < Appium::Driver
12
12
 
13
+ # @!attribute [rw] app_id
14
+ # @return [String] The app_id derived from session_capabilities (appPackage on Android, bundleID on iOS)
15
+ attr_accessor :app_id
16
+
13
17
  # @!attribute [r] device_type
14
18
  # @return [String] The device, from the list of device capabilities, used for this test
15
19
  attr_reader :device_type
@@ -25,11 +29,10 @@ module Maze
25
29
  # @param locator [Symbol] the primary locator strategy Appium should use to find elements
26
30
  def initialize(server_url, capabilities, locator = :id)
27
31
  # Sets up identifiers for ease of connecting jobs
28
- name_capabilities = project_name_capabilities
32
+ capabilities ||= {}
29
33
 
30
34
  @element_locator = locator
31
35
  @capabilities = capabilities
32
- @capabilities.merge! name_capabilities
33
36
 
34
37
  # Timers
35
38
  @find_element_timer = Maze.timers.add 'Appium - find element'
@@ -43,10 +46,6 @@ module Maze
43
46
  server_url: server_url
44
47
  }
45
48
  }, true)
46
-
47
- $logger.info 'Appium driver initialized for:'
48
- $logger.info " project : #{name_capabilities[:project]}"
49
- $logger.info " build : #{name_capabilities[:build]}"
50
49
  end
51
50
 
52
51
  # Starts the Appium driver
@@ -187,24 +186,6 @@ module Maze
187
186
  reset
188
187
  end
189
188
 
190
- # Determines and returns sensible project, build, and name capabilities
191
- #
192
- # @return [Hash] A hash containing the 'project' and 'build' capabilities
193
- def project_name_capabilities
194
- # Default to values for running locally
195
- project = 'local'
196
- build = SecureRandom.uuid
197
-
198
- if ENV['BUILDKITE']
199
- # Project
200
- project = ENV['BUILDKITE_PIPELINE_NAME']
201
- end
202
- {
203
- project: project,
204
- build: build
205
- }
206
- end
207
-
208
189
  def device_info
209
190
  driver.execute_script('mobile:deviceInfo')
210
191
  end
@@ -11,12 +11,10 @@ module Maze
11
11
  attr_reader :capabilities
12
12
 
13
13
  def initialize(driver_for, selenium_url=nil, capabilities=nil)
14
- capabilities.merge! project_name_capabilities
14
+ capabilities ||= {}
15
15
  @capabilities = capabilities
16
16
  @driver_for = driver_for
17
17
  @selenium_url = selenium_url
18
-
19
- start_driver
20
18
  end
21
19
 
22
20
  def find_element(*args)
@@ -62,24 +60,6 @@ module Maze
62
60
  JAVASCRIPT
63
61
  end
64
62
 
65
- # Determines and returns sensible project and build capabilities
66
- #
67
- # @return [Hash] A hash containing the 'project' and 'build' capabilities
68
- def project_name_capabilities
69
- # Default to values for running locally
70
- project = 'local'
71
- build = SecureRandom.uuid
72
-
73
- if ENV['BUILDKITE']
74
- # Project
75
- project = ENV['BUILDKITE_PIPELINE_NAME']
76
- end
77
- {
78
- project: project,
79
- build: build
80
- }
81
- end
82
-
83
63
  # Restarts the underlying-driver in the case an unrecoverable error occurs
84
64
  #
85
65
  # @param attempts [Integer] The number of times we should retry a failed attempt (defaults to 6)
@@ -91,8 +71,6 @@ module Maze
91
71
  start_driver(attempts)
92
72
  end
93
73
 
94
- private
95
-
96
74
  # Attempts to create a new selenium driver a given number of times
97
75
  #
98
76
  # @param attempts [Integer] The number of times we should retry a failed attempt (defaults to 6)
@@ -114,15 +92,23 @@ module Maze
114
92
  end
115
93
  end
116
94
 
95
+ private
96
+
117
97
  # Creates and starts the selenium driver
118
98
  def create_driver(driver_for, selenium_url=nil)
119
99
  begin
120
100
  $logger.info "Starting Selenium driver"
121
101
  time = Time.now
122
102
  if driver_for == :remote
123
- driver = ::Selenium::WebDriver.for :remote,
124
- url: selenium_url,
125
- desired_capabilities: @capabilities
103
+ if Maze.config.legacy_driver?
104
+ driver = ::Selenium::WebDriver.for :remote,
105
+ url: selenium_url,
106
+ desired_capabilities: @capabilities
107
+ else
108
+ driver = ::Selenium::WebDriver.for :remote,
109
+ url: selenium_url,
110
+ capabilities: @capabilities
111
+ end
126
112
  else
127
113
  driver = ::Selenium::WebDriver.for driver_for
128
114
  end
data/lib/maze/errors.rb CHANGED
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'appium_lib'
4
+
1
5
  module Maze
2
6
  module Error
3
7
  # An error raised when an appium element cannot be found
@@ -16,5 +20,33 @@ module Maze
16
20
  super(message)
17
21
  end
18
22
  end
23
+
24
+ ERROR_CODES = {
25
+ ::Selenium::WebDriver::Error::UnknownError => {
26
+ retry: true,
27
+ error_code: 10
28
+ },
29
+ ::Selenium::WebDriver::Error::WebDriverError => {
30
+ retry: true,
31
+ error_code: 10
32
+ },
33
+ Maze::Error::AppiumElementNotFoundError => {
34
+ retry: true,
35
+ error_code: 11
36
+ },
37
+ ::Selenium::WebDriver::Error::NoSuchElementError => {
38
+ retry: true,
39
+ error_code: 12
40
+ },
41
+ ::Selenium::WebDriver::Error::TimeoutError => {
42
+ retry: true,
43
+ error_code: 13
44
+ },
45
+ ::Selenium::WebDriver::Error::StaleElementReferenceError => {
46
+ retry: true,
47
+ error_code: 14
48
+ },
49
+ }.freeze
50
+
19
51
  end
20
52
  end
@@ -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