bugsnag-maze-runner 6.27.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.
- checksums.yaml +7 -0
- data/bin/bugsnag-print-load-paths +6 -0
- data/bin/download-logs +76 -0
- data/bin/maze-runner +136 -0
- data/bin/upload-app +56 -0
- data/lib/features/scripts/await-android-emulator.sh +11 -0
- data/lib/features/scripts/clear-android-app-data.sh +8 -0
- data/lib/features/scripts/force-stop-android-app.sh +8 -0
- data/lib/features/scripts/install-android-app.sh +15 -0
- data/lib/features/scripts/launch-android-app.sh +38 -0
- data/lib/features/scripts/launch-android-emulator.sh +15 -0
- data/lib/features/steps/android_steps.rb +51 -0
- data/lib/features/steps/app_automator_steps.rb +228 -0
- data/lib/features/steps/aws_sam_steps.rb +212 -0
- data/lib/features/steps/breadcrumb_steps.rb +50 -0
- data/lib/features/steps/browser_steps.rb +93 -0
- data/lib/features/steps/build_api_steps.rb +25 -0
- data/lib/features/steps/document_server_steps.rb +7 -0
- data/lib/features/steps/error_reporting_steps.rb +342 -0
- data/lib/features/steps/feature_flag_steps.rb +190 -0
- data/lib/features/steps/header_steps.rb +72 -0
- data/lib/features/steps/log_steps.rb +29 -0
- data/lib/features/steps/multipart_request_steps.rb +142 -0
- data/lib/features/steps/network_steps.rb +75 -0
- data/lib/features/steps/payload_steps.rb +234 -0
- data/lib/features/steps/proxy_steps.rb +34 -0
- data/lib/features/steps/query_parameter_steps.rb +31 -0
- data/lib/features/steps/request_assertion_steps.rb +107 -0
- data/lib/features/steps/runner_steps.rb +406 -0
- data/lib/features/steps/session_tracking_steps.rb +116 -0
- data/lib/features/steps/value_steps.rb +119 -0
- data/lib/features/support/env.rb +7 -0
- data/lib/features/support/internal_hooks.rb +260 -0
- data/lib/maze/appium_server.rb +112 -0
- data/lib/maze/assertions/request_set_assertions.rb +97 -0
- data/lib/maze/aws/sam.rb +112 -0
- data/lib/maze/bitbar_devices.rb +84 -0
- data/lib/maze/bitbar_utils.rb +112 -0
- data/lib/maze/browser_stack_devices.rb +160 -0
- data/lib/maze/browser_stack_utils.rb +164 -0
- data/lib/maze/browsers_bs.yml +220 -0
- data/lib/maze/browsers_cbt.yml +100 -0
- data/lib/maze/bugsnag_config.rb +42 -0
- data/lib/maze/capabilities.rb +126 -0
- data/lib/maze/checks/assert_check.rb +91 -0
- data/lib/maze/checks/noop_check.rb +34 -0
- data/lib/maze/compare.rb +161 -0
- data/lib/maze/configuration.rb +174 -0
- data/lib/maze/docker.rb +108 -0
- data/lib/maze/document_server.rb +46 -0
- data/lib/maze/driver/appium.rb +217 -0
- data/lib/maze/driver/browser.rb +138 -0
- data/lib/maze/driver/resilient_appium.rb +51 -0
- data/lib/maze/errors.rb +20 -0
- data/lib/maze/helper.rb +118 -0
- data/lib/maze/hooks/appium_hooks.rb +216 -0
- data/lib/maze/hooks/browser_hooks.rb +68 -0
- data/lib/maze/hooks/command_hooks.rb +9 -0
- data/lib/maze/hooks/hooks.rb +61 -0
- data/lib/maze/interactive_cli.rb +173 -0
- data/lib/maze/logger.rb +73 -0
- data/lib/maze/macos_utils.rb +14 -0
- data/lib/maze/network.rb +49 -0
- data/lib/maze/option/parser.rb +245 -0
- data/lib/maze/option/processor.rb +143 -0
- data/lib/maze/option/validator.rb +184 -0
- data/lib/maze/option.rb +64 -0
- data/lib/maze/plugins/bugsnag_reporting_plugin.rb +49 -0
- data/lib/maze/plugins/cucumber_report_plugin.rb +101 -0
- data/lib/maze/plugins/global_retry_plugin.rb +38 -0
- data/lib/maze/proxy.rb +114 -0
- data/lib/maze/request_list.rb +82 -0
- data/lib/maze/retry_handler.rb +76 -0
- data/lib/maze/runner.rb +149 -0
- data/lib/maze/sauce_labs_utils.rb +96 -0
- data/lib/maze/server.rb +207 -0
- data/lib/maze/servlets/base_servlet.rb +22 -0
- data/lib/maze/servlets/command_servlet.rb +44 -0
- data/lib/maze/servlets/log_servlet.rb +64 -0
- data/lib/maze/servlets/reflective_servlet.rb +69 -0
- data/lib/maze/servlets/servlet.rb +160 -0
- data/lib/maze/smart_bear_utils.rb +71 -0
- data/lib/maze/store.rb +15 -0
- data/lib/maze/terminating_server.rb +129 -0
- data/lib/maze/timers.rb +51 -0
- data/lib/maze/wait.rb +35 -0
- data/lib/maze.rb +27 -0
- metadata +371 -0
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'pty'
|
2
|
+
# TODO: Removed pending PLAT-6322
|
3
|
+
# require 'boring'
|
4
|
+
|
5
|
+
module Maze
|
6
|
+
# Encapsulates a shell session, retaining state and input streams for interactive tests
|
7
|
+
class InteractiveCLI
|
8
|
+
# @!attribute [r] stdout_lines
|
9
|
+
# @return [Array] An array of output strings received from the terminals STDOUT pipe
|
10
|
+
attr_reader :stdout_lines
|
11
|
+
|
12
|
+
# @!attribute [r] stderr_lines
|
13
|
+
# @return [Array] An array of error strings received from the terminals STDERR pipe
|
14
|
+
attr_reader :stderr_lines
|
15
|
+
|
16
|
+
# @!attribute [r] pid
|
17
|
+
# @return [Number, nil] The PID of the running terminal
|
18
|
+
attr_reader :pid
|
19
|
+
|
20
|
+
# @!attribute [r] current_buffer
|
21
|
+
# @return [String] A string representation of the current output present in the terminal
|
22
|
+
attr_reader :current_buffer
|
23
|
+
|
24
|
+
# Creates an InteractiveCLI instance
|
25
|
+
#
|
26
|
+
# @param shell [String] A path to the shell to run, defaults to `/bin/sh`
|
27
|
+
# @param stop_command [String] The stop command, defaults to `exit`
|
28
|
+
def initialize(shell = '/bin/sh', stop_command = 'exit')
|
29
|
+
@shell = shell
|
30
|
+
@stop_command = stop_command
|
31
|
+
@stdout_lines = []
|
32
|
+
@stderr_lines = []
|
33
|
+
@on_exit_blocks = []
|
34
|
+
@current_buffer = ''
|
35
|
+
# TODO: Removed pending PLAT-6322
|
36
|
+
# @boring = Boring.new
|
37
|
+
|
38
|
+
start_threaded_shell(shell)
|
39
|
+
end
|
40
|
+
|
41
|
+
def start(threaded: true)
|
42
|
+
threaded ? start_threaded_shell(@shell) : start_shell(@shell)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Attempts to stop the shell using the preset command and wait for it to exit
|
46
|
+
#
|
47
|
+
# @return [Boolean] If the shell stopped successfully
|
48
|
+
def stop
|
49
|
+
run_command(@stop_command)
|
50
|
+
|
51
|
+
@in_stream.close
|
52
|
+
|
53
|
+
maybe_thread = @thread.join(15)
|
54
|
+
|
55
|
+
# The thread did not exit!
|
56
|
+
return false if maybe_thread.nil?
|
57
|
+
|
58
|
+
@pid = nil
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [Boolean] Whether the shell is currently running
|
63
|
+
def running?
|
64
|
+
!@pid.nil?
|
65
|
+
end
|
66
|
+
|
67
|
+
# Runs the given command if the shell is running
|
68
|
+
#
|
69
|
+
# @param command [String] The command to run
|
70
|
+
#
|
71
|
+
# @return [Boolean] true if the command is executed, false otherwise
|
72
|
+
def run_command(command)
|
73
|
+
return false unless running?
|
74
|
+
|
75
|
+
@in_stream.puts(command)
|
76
|
+
|
77
|
+
true
|
78
|
+
rescue ::Errno::EIO => err
|
79
|
+
$logger.debug(pid) { "EIO error: #{err}" }
|
80
|
+
false
|
81
|
+
end
|
82
|
+
|
83
|
+
def on_exit(&block)
|
84
|
+
@on_exit_blocks << block
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
# Starts a shell on another thread
|
90
|
+
#
|
91
|
+
# @param shell [String] A path to the shell to run
|
92
|
+
def start_threaded_shell(shell)
|
93
|
+
@thread = Thread.new do
|
94
|
+
start_shell(shell)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Starts a shell
|
99
|
+
#
|
100
|
+
# @param shell [String] A path to the shell to run
|
101
|
+
def start_shell(shell)
|
102
|
+
stderr_reader, stderr_writer = IO.pipe
|
103
|
+
|
104
|
+
PTY.spawn(shell, err: stderr_writer.fileno) do |stdout, stdin, pid|
|
105
|
+
# We don't need to write to stderr so close it ASAP
|
106
|
+
stderr_writer.close
|
107
|
+
|
108
|
+
$logger.debug(pid) { 'PTY spawned!' }
|
109
|
+
@pid = pid
|
110
|
+
@in_stream = stdin
|
111
|
+
|
112
|
+
stdout_thread = Thread.new do
|
113
|
+
stdout.each_char do |char|
|
114
|
+
if char == "\n"
|
115
|
+
line = format_line(@current_buffer)
|
116
|
+
|
117
|
+
$logger.debug("#{pid} STDOUT") { line.dump }
|
118
|
+
@stdout_lines << line
|
119
|
+
@current_buffer.clear
|
120
|
+
else
|
121
|
+
@current_buffer << char
|
122
|
+
end
|
123
|
+
end
|
124
|
+
rescue ::Errno::EIO => err
|
125
|
+
$logger.debug(pid) { "EIO error: #{err}" }
|
126
|
+
end
|
127
|
+
|
128
|
+
stderr_thread = Thread.new do
|
129
|
+
buffer = ''
|
130
|
+
|
131
|
+
stderr_reader.each_char do |char|
|
132
|
+
if char == "\n"
|
133
|
+
line = format_line(buffer)
|
134
|
+
|
135
|
+
$logger.debug("#{pid} STDERR") { line.dump }
|
136
|
+
@stderr_lines << line
|
137
|
+
buffer.clear
|
138
|
+
else
|
139
|
+
buffer << char
|
140
|
+
end
|
141
|
+
end
|
142
|
+
rescue ::Errno::EIO => err
|
143
|
+
$logger.debug(pid) { "EIO error: #{err}" }
|
144
|
+
end
|
145
|
+
|
146
|
+
_, status = Process.wait2(@pid)
|
147
|
+
@pid = nil
|
148
|
+
|
149
|
+
# Stop the thread that's reading from stdout
|
150
|
+
failed = stdout_thread.join(5).nil?
|
151
|
+
raise 'stdout is blocked!' if failed
|
152
|
+
|
153
|
+
# Stop the thread that's reading from stderr
|
154
|
+
failed = stderr_thread.join(5).nil?
|
155
|
+
raise 'stderr is blocked!' if failed
|
156
|
+
|
157
|
+
$logger.debug(pid) { "PTY exit status: #{status.exitstatus}" }
|
158
|
+
@on_exit_blocks.each do |block|
|
159
|
+
block.call(status.exitstatus)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
ensure
|
163
|
+
stderr_reader.close unless stderr_reader.closed?
|
164
|
+
stderr_writer.close unless stderr_writer.closed?
|
165
|
+
end
|
166
|
+
|
167
|
+
def format_line(line)
|
168
|
+
# TODO: Removed pending PLAT-6322
|
169
|
+
# @boring.scrub(line.strip)
|
170
|
+
line.strip
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
data/lib/maze/logger.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'singleton'
|
5
|
+
|
6
|
+
# Logger classes
|
7
|
+
module Maze
|
8
|
+
# A logger, with level configured according to the environment
|
9
|
+
class Logger < Logger
|
10
|
+
|
11
|
+
include Singleton
|
12
|
+
def initialize
|
13
|
+
if ENV['VERBOSE'] || ENV['DEBUG']
|
14
|
+
super(STDOUT, level: Logger::DEBUG)
|
15
|
+
elsif ENV['QUIET']
|
16
|
+
super(STDOUT, level: Logger::ERROR)
|
17
|
+
else
|
18
|
+
super(STDOUT, level: Logger::INFO)
|
19
|
+
end
|
20
|
+
self.datetime_format = '%Y-%m-%d %H:%M:%S'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
$logger = Maze::Logger.instance
|
25
|
+
|
26
|
+
# A collection of logging utilities
|
27
|
+
class LogUtil
|
28
|
+
class << self
|
29
|
+
# Logs Hash-based data, accounting for things like file upload requests that are too big to log meaningfully.
|
30
|
+
#
|
31
|
+
# @param severity [Integer] A constant from Logger::Severity
|
32
|
+
# @param data [Hash] The data to log (currently needs to be a Hash)
|
33
|
+
def log_hash(severity, data)
|
34
|
+
return unless data.is_a? Hash
|
35
|
+
|
36
|
+
# Try to pretty print as JSON, if not too big
|
37
|
+
begin
|
38
|
+
json = JSON.pretty_generate data
|
39
|
+
if json.length < 128 * 1024
|
40
|
+
$logger.add severity, json
|
41
|
+
else
|
42
|
+
log_hash_by_field severity, data
|
43
|
+
end
|
44
|
+
rescue Encoding::UndefinedConversionError
|
45
|
+
log_hash_by_field severity, data
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Logs a hash field by field,
|
50
|
+
#
|
51
|
+
# @param severity [Integer] A Logger::Severity
|
52
|
+
# @param hash [Hash] The Hash
|
53
|
+
def log_hash_by_field(severity, hash)
|
54
|
+
hash.keys.each do |key|
|
55
|
+
value = hash[key].to_s
|
56
|
+
if value.length < 1024
|
57
|
+
$logger.add severity, " #{key}: #{value}"
|
58
|
+
else
|
59
|
+
$logger.add severity, " #{key} (length): #{value.length}"
|
60
|
+
$logger.add severity, " #{key} (start): #{value[0, 1024]}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Produces a clickable link when logged in Buildkite
|
66
|
+
# @param url [String] Link URL
|
67
|
+
# @param text [String] Link text
|
68
|
+
def linkify(url, text)
|
69
|
+
"\033]1339;url='#{url}';content='#{text}'\a"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Maze
|
4
|
+
class MacosUtils
|
5
|
+
class << self
|
6
|
+
def capture_screen(scenario)
|
7
|
+
path = File.join(File.join(Dir.pwd, 'maze_output'), 'failed', Maze::Helper.to_friendly_filename(scenario.name))
|
8
|
+
FileUtils.makedirs(path)
|
9
|
+
|
10
|
+
system("/usr/sbin/screencapture #{path}/#{Maze::Helper.to_friendly_filename(scenario.name)}-screenshot.jpg")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/maze/network.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
module Maze
|
6
|
+
# Sets the maximum number of times Maze runner will ping to see if a port is open, defaulting to 100
|
7
|
+
MAX_MAZE_CONNECT_ATTEMPTS = ENV.fetch('MAX_MAZE_CONNECT_ATTEMPTS', 100).to_i
|
8
|
+
|
9
|
+
# Provides network utility functionality
|
10
|
+
class Network
|
11
|
+
class << self
|
12
|
+
# Repeatedly pings a port to see if the host is ready for a connection.
|
13
|
+
# The maximum amount of attempts is determined by the MAX_MAZE_CONNECT_ATTEMPTS variable.
|
14
|
+
#
|
15
|
+
# @param host [String] The name of the host to connect to
|
16
|
+
# @param port [String] The port to attempt to connect to
|
17
|
+
#
|
18
|
+
# @raise [StandardError] When the port is not available for a connection
|
19
|
+
def wait_for_port(host, port)
|
20
|
+
attempts = 0
|
21
|
+
up = false
|
22
|
+
until (attempts >= MAX_MAZE_CONNECT_ATTEMPTS) || up
|
23
|
+
attempts += 1
|
24
|
+
up = port_open?(host, port)
|
25
|
+
sleep 0.1 unless up
|
26
|
+
end
|
27
|
+
raise "Port not ready in time!" unless up
|
28
|
+
end
|
29
|
+
|
30
|
+
# Attempts to connect to a port, timing out after a time.
|
31
|
+
#
|
32
|
+
# @param host [String] The name of the host to connect to
|
33
|
+
# @param port [String] The port to attempt to connect to
|
34
|
+
# @param seconds [Float] Optional. The length of time to wait before timing out.
|
35
|
+
def port_open?(host, port, seconds=0.1)
|
36
|
+
Timeout::timeout(seconds) do
|
37
|
+
begin
|
38
|
+
TCPSocket.new(host, port).close
|
39
|
+
true
|
40
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError
|
41
|
+
false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
rescue Timeout::Error
|
45
|
+
false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,245 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cucumber/cli/main'
|
4
|
+
require 'optimist'
|
5
|
+
require_relative '../option'
|
6
|
+
require_relative '../../maze'
|
7
|
+
|
8
|
+
module Maze
|
9
|
+
module Option
|
10
|
+
# Parses the command line options
|
11
|
+
class Parser
|
12
|
+
class << self
|
13
|
+
def parse(args)
|
14
|
+
parser = Optimist::Parser.new do
|
15
|
+
text 'Maze Runner extends the functionality of Cucumber, ' \
|
16
|
+
'providing all of the command line arguments that it provides.'
|
17
|
+
text ''
|
18
|
+
text 'Usage [OPTIONS] <filenames>'
|
19
|
+
text ''
|
20
|
+
text 'Overridden Cucumber options:'
|
21
|
+
opt :help,
|
22
|
+
'Print this help.'
|
23
|
+
opt :version,
|
24
|
+
'Display Maze Runner and Cucumber versions'
|
25
|
+
|
26
|
+
text ''
|
27
|
+
text 'General options:'
|
28
|
+
|
29
|
+
opt Option::ENABLE_RETRIES,
|
30
|
+
'Enables retrying failed scenarios when tagged',
|
31
|
+
type: :boolean,
|
32
|
+
default: true
|
33
|
+
|
34
|
+
opt Option::ENABLE_BUGSNAG,
|
35
|
+
'Enables reporting to Bugsnag on scenario failure (Require MAZE_BUGSNAG_API_KEY)',
|
36
|
+
type: :boolean,
|
37
|
+
default: true
|
38
|
+
|
39
|
+
text ''
|
40
|
+
text 'Server options:'
|
41
|
+
|
42
|
+
opt Option::BIND_ADDRESS,
|
43
|
+
'Mock server bind address',
|
44
|
+
type: :string
|
45
|
+
opt Option::PORT,
|
46
|
+
'Mock server port',
|
47
|
+
default: 9339
|
48
|
+
opt Option::NULL_PORT,
|
49
|
+
'Terminating connection port',
|
50
|
+
default: 9341
|
51
|
+
|
52
|
+
text ''
|
53
|
+
text 'Document server options:'
|
54
|
+
|
55
|
+
opt Option::DS_ROOT,
|
56
|
+
'Document server root',
|
57
|
+
type: :string
|
58
|
+
opt Option::DS_BIND_ADDRESS,
|
59
|
+
'Document server bind address',
|
60
|
+
type: :string
|
61
|
+
opt Option::DS_PORT,
|
62
|
+
'Document server port',
|
63
|
+
default: 9340
|
64
|
+
|
65
|
+
text ''
|
66
|
+
text 'Appium options:'
|
67
|
+
|
68
|
+
opt Option::SEPARATE_SESSIONS,
|
69
|
+
'Start a new Appium session for each scenario',
|
70
|
+
type: :boolean,
|
71
|
+
default: false
|
72
|
+
opt Option::FARM,
|
73
|
+
'Device farm to use: "bs" (BrowserStack) or "local"',
|
74
|
+
type: :string
|
75
|
+
opt Option::APP,
|
76
|
+
'The app to be installed and run against. Assumed to be contained in a file if prefixed with @.',
|
77
|
+
type: :string
|
78
|
+
opt Option::A11Y_LOCATOR,
|
79
|
+
'Locate elements by accessibility id rather than id',
|
80
|
+
type: :boolean,
|
81
|
+
default: false
|
82
|
+
opt Option::RESILIENT,
|
83
|
+
'Use the resilient Appium driver',
|
84
|
+
default: false
|
85
|
+
opt Option::CAPABILITIES,
|
86
|
+
'Additional desired Appium capabilities as a JSON string',
|
87
|
+
default: '{}'
|
88
|
+
|
89
|
+
text ''
|
90
|
+
text 'Device farm options:'
|
91
|
+
|
92
|
+
opt Option::DEVICE,
|
93
|
+
'Device to use. Can be listed multiple times to have a prioritised list of devices',
|
94
|
+
short: :none,
|
95
|
+
type: :string,
|
96
|
+
multi: true
|
97
|
+
opt Option::BROWSER,
|
98
|
+
'Browser to use (an entry in browsers_<farm>.yml)',
|
99
|
+
short: :none,
|
100
|
+
type: :string
|
101
|
+
opt Option::USERNAME,
|
102
|
+
'Device farm username. Consumes env var from environment based on farm set',
|
103
|
+
type: :string
|
104
|
+
opt Option::ACCESS_KEY,
|
105
|
+
'Device farm access key. Consumes env var from environment based on farm set',
|
106
|
+
type: :string
|
107
|
+
opt Option::APPIUM_VERSION,
|
108
|
+
'The Appium version to use',
|
109
|
+
type: :string
|
110
|
+
opt Option::LIST_DEVICES,
|
111
|
+
'Lists the devices available for the configured device-farm, or all devices if none are specified',
|
112
|
+
default: false
|
113
|
+
opt Option::APP_BUNDLE_ID,
|
114
|
+
'The bundle identifier of the test application',
|
115
|
+
type: :string
|
116
|
+
|
117
|
+
# SmartBear-only options
|
118
|
+
opt Option::SB_LOCAL,
|
119
|
+
'(SB only) Path to the SBSecureTunnel binary. MAZE_SB_LOCAL env var or "/SBSecureTunnel" by default',
|
120
|
+
type: :string
|
121
|
+
|
122
|
+
# Sauce Labs-only options
|
123
|
+
opt Option::SL_LOCAL,
|
124
|
+
'(SL only) Path to the Sauce Connect binary. MAZE_SL_LOCAL env var or "/sauce-connect/bin/sc" by default',
|
125
|
+
type: :string
|
126
|
+
|
127
|
+
# BrowserStack-only options
|
128
|
+
opt Option::BS_LOCAL,
|
129
|
+
'(BS only) Path to the BrowserStackLocal binary. MAZE_BS_LOCAL env var or "/BrowserStackLocal" by default',
|
130
|
+
type: :string
|
131
|
+
|
132
|
+
# TMS options
|
133
|
+
opt Option::TMS_URI,
|
134
|
+
'URI of the test management server root. MAZE_TMS_URI env var',
|
135
|
+
type: :string
|
136
|
+
|
137
|
+
opt Option::TMS_TOKEN,
|
138
|
+
'Token used to access the test management server. MAZE_TMS_TOKEN env var',
|
139
|
+
type: :string
|
140
|
+
|
141
|
+
text ''
|
142
|
+
text 'Local device options:'
|
143
|
+
|
144
|
+
opt Option::OS,
|
145
|
+
'OS type to use ("ios", "android")',
|
146
|
+
type: :string
|
147
|
+
opt Option::OS_VERSION,
|
148
|
+
'The intended OS version when running on a local device',
|
149
|
+
type: :string
|
150
|
+
opt Option::APPIUM_SERVER,
|
151
|
+
'Appium server URL, only used for --farm=local. MAZE_APPIUM_SERVER env var or "http://localhost:4723/wd/hub" by default',
|
152
|
+
type: :string
|
153
|
+
opt Option::START_APPIUM,
|
154
|
+
'Whether a local Appium server should be start. Only used for --farm=local.',
|
155
|
+
default: true
|
156
|
+
opt Option::APPIUM_LOGFILE,
|
157
|
+
'The file local appium server output is logged to, defaulting to "appium_server.log"',
|
158
|
+
default: 'appium_server.log'
|
159
|
+
opt Option::APPLE_TEAM_ID,
|
160
|
+
'Apple Team Id, required for local iOS testing. MAZE_APPLE_TEAM_ID env var by default',
|
161
|
+
type: :string
|
162
|
+
opt Option::UDID,
|
163
|
+
'Apple UDID, required for local iOS testing. MAZE_UDID env var by default',
|
164
|
+
type: :string
|
165
|
+
|
166
|
+
text ''
|
167
|
+
text 'Logging options:'
|
168
|
+
|
169
|
+
opt Option::FILE_LOG,
|
170
|
+
"Writes lists of received requests to the maze_output folder for all scenarios",
|
171
|
+
type: :boolean,
|
172
|
+
default: true
|
173
|
+
|
174
|
+
opt Option::LOG_REQUESTS,
|
175
|
+
"Log lists of received requests to the console in the event of scenario failure. Defaults to true if the BUILDKITE environment variable is set",
|
176
|
+
type: :boolean,
|
177
|
+
default: false
|
178
|
+
|
179
|
+
opt Option::ALWAYS_LOG,
|
180
|
+
"Always log all received requests at the end of a scenario, whether is passes or fails",
|
181
|
+
type: :boolean,
|
182
|
+
default: false
|
183
|
+
|
184
|
+
version "Maze Runner v#{Maze::VERSION} " \
|
185
|
+
"(Cucumber v#{Cucumber::VERSION.strip})"
|
186
|
+
text ''
|
187
|
+
text 'The Cucumber help follows:'
|
188
|
+
text ''
|
189
|
+
end
|
190
|
+
|
191
|
+
# Allow for options destined for Cucumber
|
192
|
+
parser.ignore_invalid_options = true
|
193
|
+
options = parser.parse args
|
194
|
+
populate_environmental_defaults(options)
|
195
|
+
|
196
|
+
rescue Optimist::HelpNeeded
|
197
|
+
parser.educate
|
198
|
+
Cucumber::Cli::Main.new(['--help']).execute!
|
199
|
+
exit
|
200
|
+
rescue Optimist::VersionNeeded
|
201
|
+
puts parser.version
|
202
|
+
exit
|
203
|
+
end
|
204
|
+
|
205
|
+
# Populates unset options with appropriate environment variables or default values if necessary
|
206
|
+
#
|
207
|
+
# @param options [Hash] The hash of already-parsed options
|
208
|
+
#
|
209
|
+
# @returns [Hash] The options hash with environment vars added
|
210
|
+
def populate_environmental_defaults(options)
|
211
|
+
case options.farm
|
212
|
+
when 'cbt'
|
213
|
+
options[Option::USERNAME] ||= ENV['CBT_USERNAME']
|
214
|
+
options[Option::ACCESS_KEY] ||= ENV['CBT_ACCESS_KEY']
|
215
|
+
when 'bs'
|
216
|
+
# Allow browser/device credentials to exist in separate accounts
|
217
|
+
if options[Option::BROWSER]
|
218
|
+
options[Option::USERNAME] ||= ENV['BROWSER_STACK_BROWSERS_USERNAME'] || ENV['BROWSER_STACK_USERNAME']
|
219
|
+
options[Option::ACCESS_KEY] ||= ENV['BROWSER_STACK_BROWSERS_ACCESS_KEY'] ||ENV['BROWSER_STACK_ACCESS_KEY']
|
220
|
+
else
|
221
|
+
options[Option::USERNAME] ||= ENV['BROWSER_STACK_DEVICES_USERNAME'] || ENV['BROWSER_STACK_USERNAME']
|
222
|
+
options[Option::ACCESS_KEY] ||= ENV['BROWSER_STACK_DEVICES_ACCESS_KEY'] ||ENV['BROWSER_STACK_ACCESS_KEY']
|
223
|
+
end
|
224
|
+
when 'sl'
|
225
|
+
options[Option::USERNAME] ||= ENV['SAUCE_LABS_USERNAME']
|
226
|
+
options[Option::ACCESS_KEY] ||= ENV['SAUCE_LABS_ACCESS_KEY']
|
227
|
+
when 'bb'
|
228
|
+
options[Option::USERNAME] ||= ENV['BITBAR_USERNAME']
|
229
|
+
options[Option::ACCESS_KEY] ||= ENV['BITBAR_ACCESS_KEY']
|
230
|
+
options[Option::TMS_URI] ||= ENV['MAZE_TMS_URI']
|
231
|
+
end
|
232
|
+
options[Option::SB_LOCAL] ||= ENV['MAZE_SB_LOCAL'] || '/SBSecureTunnel'
|
233
|
+
options[Option::TMS_URI] ||= ENV['MAZE_TMS_URI']
|
234
|
+
options[Option::TMS_TOKEN] ||= ENV['MAZE_TMS_TOKEN']
|
235
|
+
options[Option::BS_LOCAL] ||= ENV['MAZE_BS_LOCAL'] || '/BrowserStackLocal'
|
236
|
+
options[Option::SL_LOCAL] ||= ENV['MAZE_SL_LOCAL'] || '/sauce-connect/bin/sc'
|
237
|
+
options[Option::APPIUM_SERVER] ||= ENV['MAZE_APPIUM_SERVER'] || 'http://localhost:4723/wd/hub'
|
238
|
+
options[Option::APPLE_TEAM_ID] ||= ENV['MAZE_APPLE_TEAM_ID']
|
239
|
+
options[Option::UDID] ||= ENV['MAZE_UDID']
|
240
|
+
options
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../option'
|
4
|
+
require_relative '../browser_stack_devices'
|
5
|
+
|
6
|
+
module Maze
|
7
|
+
module Option
|
8
|
+
# Processes the parsed command line options
|
9
|
+
class Processor
|
10
|
+
class << self
|
11
|
+
# Populates config from the parsed options given
|
12
|
+
# @param config [Configuration] MazeRunner configuration to populate
|
13
|
+
# @param options [Hash] Parsed command line options
|
14
|
+
def populate(config, options)
|
15
|
+
|
16
|
+
# Server options
|
17
|
+
config.bind_address = options[Maze::Option::BIND_ADDRESS]
|
18
|
+
config.port = options[Maze::Option::PORT]
|
19
|
+
config.null_port = options[Maze::Option::NULL_PORT]
|
20
|
+
|
21
|
+
# General options
|
22
|
+
config.enable_retries = options[Maze::Option::ENABLE_RETRIES]
|
23
|
+
config.enable_bugsnag = options[Maze::Option::ENABLE_BUGSNAG]
|
24
|
+
config.tms_uri = options[Maze::Option::TMS_URI]
|
25
|
+
config.tms_token = options[Maze::Option::TMS_TOKEN]
|
26
|
+
|
27
|
+
# Document server options
|
28
|
+
config.document_server_root = options[Maze::Option::DS_ROOT]
|
29
|
+
config.document_server_bind_address = options[Maze::Option::DS_BIND_ADDRESS]
|
30
|
+
config.document_server_port = options[Maze::Option::DS_PORT]
|
31
|
+
|
32
|
+
# Logger options
|
33
|
+
config.file_log = options[Maze::Option::FILE_LOG]
|
34
|
+
config.log_requests = options[Maze::Option::LOG_REQUESTS] || !ENV['BUILDKITE'].nil?
|
35
|
+
config.always_log = options[Maze::Option::ALWAYS_LOG]
|
36
|
+
|
37
|
+
# General appium options
|
38
|
+
config.appium_session_isolation = options[Maze::Option::SEPARATE_SESSIONS]
|
39
|
+
config.app = Maze::Helper.read_at_arg_file options[Maze::Option::APP]
|
40
|
+
config.resilient = options[Maze::Option::RESILIENT]
|
41
|
+
farm = options[Maze::Option::FARM]
|
42
|
+
config.farm = case farm
|
43
|
+
when nil then :none
|
44
|
+
when 'cbt' then :cbt
|
45
|
+
when 'bs' then :bs
|
46
|
+
when 'sl' then :sl
|
47
|
+
when 'bb' then :bb
|
48
|
+
when 'local' then :local
|
49
|
+
else
|
50
|
+
raise "Unknown farm '#{farm}'"
|
51
|
+
end
|
52
|
+
config.locator = options[Maze::Option::A11Y_LOCATOR] ? :accessibility_id : :id
|
53
|
+
config.capabilities_option = options[Maze::Option::CAPABILITIES]
|
54
|
+
|
55
|
+
# Farm specific options
|
56
|
+
case config.farm
|
57
|
+
when :cbt
|
58
|
+
config.browser = options[Maze::Option::BROWSER]
|
59
|
+
config.sb_local = Maze::Helper.expand_path(options[Maze::Option::SB_LOCAL])
|
60
|
+
username = config.username = options[Maze::Option::USERNAME]
|
61
|
+
access_key = config.access_key = options[Maze::Option::ACCESS_KEY]
|
62
|
+
when :bs
|
63
|
+
device_option = options[Maze::Option::DEVICE]
|
64
|
+
if device_option.nil? || device_option.empty?
|
65
|
+
config.browser = options[Maze::Option::BROWSER]
|
66
|
+
else
|
67
|
+
if device_option.is_a?(Array)
|
68
|
+
config.device = device_option.first
|
69
|
+
config.device_list = device_option.drop(1)
|
70
|
+
else
|
71
|
+
config.device = device_option
|
72
|
+
config.device_list = []
|
73
|
+
end
|
74
|
+
config.os_version = Maze::BrowserStackDevices::DEVICE_HASH[config.device]['os_version'].to_f
|
75
|
+
end
|
76
|
+
config.bs_local = Maze::Helper.expand_path(options[Maze::Option::BS_LOCAL])
|
77
|
+
config.appium_version = options[Maze::Option::APPIUM_VERSION]
|
78
|
+
username = config.username = options[Maze::Option::USERNAME]
|
79
|
+
access_key = config.access_key = options[Maze::Option::ACCESS_KEY]
|
80
|
+
config.appium_server_url = "http://#{username}:#{access_key}@hub-cloud.browserstack.com/wd/hub"
|
81
|
+
when :sl
|
82
|
+
device_option = options[Maze::Option::DEVICE]
|
83
|
+
if device_option.is_a?(Array)
|
84
|
+
config.device = device_option.first
|
85
|
+
config.device_list = device_option.drop(1)
|
86
|
+
else
|
87
|
+
config.device = device_option
|
88
|
+
config.device_list = []
|
89
|
+
end
|
90
|
+
config.browser = options[Maze::Option::BROWSER]
|
91
|
+
config.os = options[Maze::Option::OS]
|
92
|
+
config.os_version = options[Maze::Option::OS_VERSION].to_f
|
93
|
+
config.sl_local = Maze::Helper.expand_path(options[Maze::Option::SL_LOCAL])
|
94
|
+
config.appium_version = options[Maze::Option::APPIUM_VERSION]
|
95
|
+
username = config.username = options[Maze::Option::USERNAME]
|
96
|
+
access_key = config.access_key = options[Maze::Option::ACCESS_KEY]
|
97
|
+
config.appium_server_url = "https://#{username}:#{access_key}@ondemand.us-west-1.saucelabs.com/wd/hub"
|
98
|
+
when :bb then
|
99
|
+
config.username = options[Maze::Option::USERNAME]
|
100
|
+
config.access_key = options[Maze::Option::ACCESS_KEY]
|
101
|
+
config.tms_uri = options[Maze::Option::TMS_URI]
|
102
|
+
device_option = options[Maze::Option::DEVICE]
|
103
|
+
if device_option.is_a?(Array)
|
104
|
+
config.device = device_option.first
|
105
|
+
config.device_list = device_option.drop(1)
|
106
|
+
else
|
107
|
+
config.device = device_option
|
108
|
+
config.device_list = []
|
109
|
+
end
|
110
|
+
config.os = options[Maze::Option::OS]
|
111
|
+
config.os_version = options[Maze::Option::OS_VERSION]
|
112
|
+
config.sb_local = Maze::Helper.expand_path(options[Maze::Option::SB_LOCAL])
|
113
|
+
config.appium_server_url = 'https://appium.bitbar.com/wd/hub'
|
114
|
+
config.app_bundle_id = options[Maze::Option::APP_BUNDLE_ID]
|
115
|
+
when :local then
|
116
|
+
if options[Maze::Option::BROWSER]
|
117
|
+
config.browser = options[Maze::Option::BROWSER]
|
118
|
+
else
|
119
|
+
os = config.os = options[Maze::Option::OS].downcase
|
120
|
+
config.os_version = options[Maze::Option::OS_VERSION].to_f unless options[Maze::Option::OS_VERSION].nil?
|
121
|
+
config.appium_server_url = options[Maze::Option::APPIUM_SERVER]
|
122
|
+
config.start_appium = options[Maze::Option::START_APPIUM]
|
123
|
+
config.appium_logfile = options[Maze::Option::APPIUM_LOGFILE]
|
124
|
+
if os == 'ios'
|
125
|
+
config.apple_team_id = options[Maze::Option::APPLE_TEAM_ID]
|
126
|
+
config.device_id = options[Maze::Option::UDID]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
when :none
|
130
|
+
if options[Maze::Option::OS]
|
131
|
+
config.os = options[Maze::Option::OS].downcase
|
132
|
+
end
|
133
|
+
if options[Maze::Option::OS_VERSION]
|
134
|
+
config.os_version = options[Maze::Option::OS_VERSION].to_f
|
135
|
+
end
|
136
|
+
else
|
137
|
+
raise "Unexpected farm option #{config.farm}"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|