bugsnag-maze-runner 6.27.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|