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,100 @@
|
|
1
|
+
# Selenium capabilities for browserNames available on CrossbrowserNameTesting
|
2
|
+
---
|
3
|
+
ie_8:
|
4
|
+
browserName: "Internet Explorer"
|
5
|
+
version: "8"
|
6
|
+
platform: "Windows 7"
|
7
|
+
|
8
|
+
ie_9:
|
9
|
+
browserName: "Internet Explorer"
|
10
|
+
version: "9"
|
11
|
+
platform: "Windows 7"
|
12
|
+
|
13
|
+
ie_10:
|
14
|
+
browserName: "Internet Explorer"
|
15
|
+
version: "10"
|
16
|
+
os: "windows"
|
17
|
+
platform: "Windows 7 64-Bit"
|
18
|
+
|
19
|
+
ie_11:
|
20
|
+
browserName: "Internet Explorer"
|
21
|
+
version: "11"
|
22
|
+
platform: "Windows 7 64-Bit"
|
23
|
+
|
24
|
+
edge_14:
|
25
|
+
browserName: "MicrosoftEdge"
|
26
|
+
version: "14"
|
27
|
+
platform: "Windows 10"
|
28
|
+
|
29
|
+
edge_15:
|
30
|
+
browserName: "MicrosoftEdge"
|
31
|
+
version: "15"
|
32
|
+
platform: "Windows 10"
|
33
|
+
|
34
|
+
safari_8:
|
35
|
+
browserName: "Safari"
|
36
|
+
version: "8"
|
37
|
+
platform: "Mac OSX 10.10"
|
38
|
+
|
39
|
+
safari_10:
|
40
|
+
browserName: "Safari"
|
41
|
+
version: "10"
|
42
|
+
platform: "Mac OSX 10.12"
|
43
|
+
|
44
|
+
safari_13:
|
45
|
+
browserName: "Safari"
|
46
|
+
version: "13"
|
47
|
+
platform: "Mac OSX 10.15"
|
48
|
+
|
49
|
+
safari_14:
|
50
|
+
browserName: "Safari"
|
51
|
+
version: "14"
|
52
|
+
platform: "MacOS 11.0"
|
53
|
+
|
54
|
+
iphone_7_simulator:
|
55
|
+
browserName: "Safari"
|
56
|
+
deviceName: "iPhone 7 Simulator"
|
57
|
+
platformVersion': "10.0"
|
58
|
+
platformName': "iOS"
|
59
|
+
|
60
|
+
iphone_xr:
|
61
|
+
browserName: "Safari"
|
62
|
+
deviceName': "iPhone XR"
|
63
|
+
platformVersion': "12.1"
|
64
|
+
platformName': "iOS"
|
65
|
+
|
66
|
+
android_s7:
|
67
|
+
browserName: "Chrome"
|
68
|
+
deviceName: "Galaxy S7"
|
69
|
+
platformVersion: "7.0"
|
70
|
+
platformName: "Android"
|
71
|
+
|
72
|
+
firefox_30:
|
73
|
+
browserName: "Firefox"
|
74
|
+
version: "30"
|
75
|
+
platform: "Windows 7"
|
76
|
+
|
77
|
+
firefox_56:
|
78
|
+
browserName: "Firefox"
|
79
|
+
version: "56"
|
80
|
+
platform: "Mac OSX 10.13"
|
81
|
+
|
82
|
+
firefox_95:
|
83
|
+
browserName: "Firefox"
|
84
|
+
version: "95"
|
85
|
+
platform: "Windows 10"
|
86
|
+
|
87
|
+
chrome_43:
|
88
|
+
browserName: "Chrome"
|
89
|
+
version: "43"
|
90
|
+
platform: "Windows 7"
|
91
|
+
|
92
|
+
chrome_61:
|
93
|
+
browserName: "Chrome"
|
94
|
+
version: "61"
|
95
|
+
platform: "Windows 10"
|
96
|
+
|
97
|
+
chrome_96:
|
98
|
+
browserName: "Chrome"
|
99
|
+
version: "96"
|
100
|
+
platform: "Windows 10"
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'bugsnag'
|
2
|
+
|
3
|
+
# Contains logic for running Bugsnag
|
4
|
+
module Maze
|
5
|
+
class BugsnagConfig
|
6
|
+
class << self
|
7
|
+
def start_bugsnag(cucumber_config)
|
8
|
+
# Use MAZE_BUGSNAG_API_KEY explicitly to avoid collisions with test env
|
9
|
+
return unless Maze.config.enable_bugsnag && ENV['MAZE_BUGSNAG_API_KEY']
|
10
|
+
|
11
|
+
Bugsnag.configure do |config|
|
12
|
+
config.api_key = ENV['MAZE_BUGSNAG_API_KEY']
|
13
|
+
config.discard_classes << 'Test::Unit::AssertionFailedError'
|
14
|
+
config.add_metadata(:'test driver', {
|
15
|
+
'driver type': Maze.driver.class,
|
16
|
+
'device farm': Maze.config.farm,
|
17
|
+
'capabilities': Maze.config.capabilities
|
18
|
+
}) if Maze.driver
|
19
|
+
config.add_metadata(:'buildkite', {
|
20
|
+
'pipeline': ENV['BUILDKITE_PIPELINE_NAME'],
|
21
|
+
'repo': ENV['BUILDKITE_REPO'],
|
22
|
+
'build url': ENV['BUILDKITE_BUILD_URL'],
|
23
|
+
'branch': ENV['BUILDKITE_BRANCH'],
|
24
|
+
'builder': ENV['BUILDKITE_BUILD_CREATOR'],
|
25
|
+
'message': ENV['BUILDKITE_MESSAGE'],
|
26
|
+
'step': ENV['BUILDKITE_LABEL']
|
27
|
+
}) if ENV['BUILDKITE']
|
28
|
+
config.project_root = Dir.pwd
|
29
|
+
end
|
30
|
+
|
31
|
+
Bugsnag.start_session
|
32
|
+
|
33
|
+
at_exit do
|
34
|
+
if $!
|
35
|
+
Bugsnag.notify($!)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Maze
|
4
|
+
# Appium capabilities for each target farm
|
5
|
+
class Capabilities
|
6
|
+
class << self
|
7
|
+
# @param device_type [String] A key from @see BrowserStackDevices::DEVICE_HASH
|
8
|
+
# @param local_id [String] unique key for the tunnel instance
|
9
|
+
# @param capabilities_option [String] extra capabilities provided on the command line
|
10
|
+
def for_browser_stack_device(device_type, local_id, appium_version, capabilities_option)
|
11
|
+
capabilities = {
|
12
|
+
'browserstack.console' => 'errors',
|
13
|
+
'browserstack.localIdentifier' => local_id,
|
14
|
+
'browserstack.local' => 'true',
|
15
|
+
'noReset' => 'true'
|
16
|
+
}
|
17
|
+
capabilities.merge! BrowserStackDevices::DEVICE_HASH[device_type]
|
18
|
+
capabilities.merge! JSON.parse(capabilities_option)
|
19
|
+
capabilities['browserstack.appium_version'] = appium_version unless appium_version.nil?
|
20
|
+
capabilities
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param browser_type [String] A key from @see browsers_bs.yml
|
24
|
+
# @param local_id [String] unique key for the tunnel instance
|
25
|
+
# @param capabilities_option [String] extra capabilities provided on the command line
|
26
|
+
def for_browser_stack_browser(browser_type, local_id, capabilities_option)
|
27
|
+
capabilities = Selenium::WebDriver::Remote::Capabilities.new
|
28
|
+
capabilities['browserstack.local'] = 'true'
|
29
|
+
capabilities['browserstack.localIdentifier'] = local_id
|
30
|
+
capabilities['browserstack.console'] = 'errors'
|
31
|
+
browsers = YAML.safe_load(File.read("#{__dir__}/browsers_bs.yml"))
|
32
|
+
capabilities.merge! browsers[browser_type]
|
33
|
+
capabilities.merge! JSON.parse(capabilities_option)
|
34
|
+
capabilities
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param browser_type [String] A key from @see browsers_cbt.yml
|
38
|
+
# @param local_id [String] unique key for the SB tunnel instance
|
39
|
+
# @param capabilities_option [String] extra capabilities provided on the command line
|
40
|
+
def for_cbt_browser(browser_type, local_id, capabilities_option)
|
41
|
+
capabilities = Selenium::WebDriver::Remote::Capabilities.new
|
42
|
+
capabilities['tunnel_name'] = local_id
|
43
|
+
browsers = YAML.safe_load(File.read("#{__dir__}/browsers_cbt.yml"))
|
44
|
+
capabilities.merge! browsers[browser_type]
|
45
|
+
capabilities.merge! JSON.parse(capabilities_option)
|
46
|
+
capabilities
|
47
|
+
end
|
48
|
+
|
49
|
+
# @param device_type [String]
|
50
|
+
def for_bitbar_device(bitbar_api_key, device_type, platform, platform_version, capabilities_option)
|
51
|
+
capabilities = {
|
52
|
+
'bitbar_apiKey' => bitbar_api_key,
|
53
|
+
'bitbar_testrun' => "#{platform} #{platform_version}",
|
54
|
+
'bitbar_findDevice' => false,
|
55
|
+
'bitbar_testTimeout' => 7200,
|
56
|
+
'disabledAnimations' => 'true',
|
57
|
+
'noReset' => 'true'
|
58
|
+
}
|
59
|
+
capabilities.merge! BitBarDevices.get_device(device_type, platform, platform_version, bitbar_api_key)
|
60
|
+
capabilities.merge! JSON.parse(capabilities_option)
|
61
|
+
capabilities
|
62
|
+
end
|
63
|
+
|
64
|
+
# Constructs Appium capabilities for running on a local Android or iOS device.
|
65
|
+
# @param platform [String] 'ios' or 'android'
|
66
|
+
# @param capabilities_option [String] extra capabilities provided on the command line
|
67
|
+
# @param team_id [String] Apple Team Id, for iOS only
|
68
|
+
# @param udid [String] device UDID, for iOS only
|
69
|
+
# noinspection RubyStringKeysInHashInspection
|
70
|
+
def for_local(platform, capabilities_option, team_id = nil, udid = nil)
|
71
|
+
capabilities = case platform.downcase
|
72
|
+
when 'android'
|
73
|
+
{
|
74
|
+
'platformName' => 'Android',
|
75
|
+
'automationName' => 'UiAutomator2',
|
76
|
+
'autoGrantPermissions' => 'true',
|
77
|
+
'noReset' => 'true'
|
78
|
+
}
|
79
|
+
when 'ios'
|
80
|
+
{
|
81
|
+
'platformName' => 'iOS',
|
82
|
+
'automationName' => 'XCUITest',
|
83
|
+
'deviceName' => udid,
|
84
|
+
'xcodeOrgId' => team_id,
|
85
|
+
'xcodeSigningId' => 'iPhone Developer',
|
86
|
+
'udid' => udid,
|
87
|
+
'noReset' => 'true',
|
88
|
+
'waitForQuiescence' => false,
|
89
|
+
'newCommandTimeout' => 0
|
90
|
+
}
|
91
|
+
when 'macos'
|
92
|
+
{
|
93
|
+
'platformName' => 'Mac'
|
94
|
+
}
|
95
|
+
else
|
96
|
+
raise "Unsupported platform: #{platform}"
|
97
|
+
end
|
98
|
+
common = {
|
99
|
+
'os' => platform,
|
100
|
+
'autoAcceptAlerts': 'true'
|
101
|
+
}
|
102
|
+
capabilities.merge! common
|
103
|
+
capabilities.merge! JSON.parse(capabilities_option)
|
104
|
+
end
|
105
|
+
|
106
|
+
def for_sauce_labs_device(device_name, os, os_version, tunnel_id, appium_version, capabilities_option)
|
107
|
+
capabilities = {
|
108
|
+
'noReset' => true,
|
109
|
+
'deviceOrientation' => 'portrait',
|
110
|
+
'tunnelIdentifier' => tunnel_id,
|
111
|
+
'browserName' => '',
|
112
|
+
'autoAcceptAlerts' => true,
|
113
|
+
'sendKeyStrategy' => 'setValue',
|
114
|
+
'waitForQuiescence' => false,
|
115
|
+
'newCommandTimeout' => 0
|
116
|
+
}
|
117
|
+
capabilities['deviceName'] = device_name unless device_name.nil?
|
118
|
+
capabilities['platformName'] = os unless os.nil?
|
119
|
+
capabilities['platformVersion'] = os_version unless os_version.nil?
|
120
|
+
capabilities.merge! JSON.parse(capabilities_option)
|
121
|
+
capabilities['appiumVersion'] = appium_version unless appium_version.nil?
|
122
|
+
capabilities
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
module Maze
|
5
|
+
module Checks
|
6
|
+
# Assertion-backed data verification checks
|
7
|
+
class AssertCheck
|
8
|
+
include Test::Unit::Assertions
|
9
|
+
|
10
|
+
def true(test, message = nil)
|
11
|
+
assert_true(test, message)
|
12
|
+
end
|
13
|
+
|
14
|
+
def false(test, message = nil)
|
15
|
+
assert_false(test, message)
|
16
|
+
end
|
17
|
+
|
18
|
+
def nil(test, message = nil)
|
19
|
+
assert_nil(test, message)
|
20
|
+
end
|
21
|
+
|
22
|
+
def not_nil(test, message = nil)
|
23
|
+
assert_not_nil(test, message)
|
24
|
+
end
|
25
|
+
|
26
|
+
def match(pattern, string, message = nil)
|
27
|
+
assert_match(pattern, string, message)
|
28
|
+
end
|
29
|
+
|
30
|
+
def equal(expected, act, message = nil)
|
31
|
+
assert_equal(expected, act, message)
|
32
|
+
end
|
33
|
+
|
34
|
+
def not_equal(expected, act, message = nil)
|
35
|
+
assert_not_equal(expected, act, message)
|
36
|
+
end
|
37
|
+
|
38
|
+
def operator(operand1, operator, operand2, message = nil)
|
39
|
+
assert_operator(operand1, operator, operand2, message)
|
40
|
+
end
|
41
|
+
|
42
|
+
def kind_of(klass, object, message = nil)
|
43
|
+
assert_kind_of(klass, object, message)
|
44
|
+
end
|
45
|
+
|
46
|
+
def block(message = 'block failed', &block)
|
47
|
+
assert_block(message, &block)
|
48
|
+
end
|
49
|
+
|
50
|
+
def include(collection, object, message = nil)
|
51
|
+
assert_include(collection, object, message)
|
52
|
+
end
|
53
|
+
alias includes include
|
54
|
+
|
55
|
+
def not_include(collection, object, message = nil)
|
56
|
+
assert_not_include(collection, object, message)
|
57
|
+
end
|
58
|
+
alias not_includes not_include
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Wrapper for Maze.check.true to avoid making a breaking change.
|
64
|
+
# @deprecated TODO Remove in v7
|
65
|
+
def assert_true(value, message = nil)
|
66
|
+
Maze.check.true value, message
|
67
|
+
end
|
68
|
+
|
69
|
+
# Wrapper for Maze.check.false to avoid making a breaking change.
|
70
|
+
# @deprecated TODO Remove in v7
|
71
|
+
def assert_false(value, message = nil)
|
72
|
+
Maze.check.false value, message
|
73
|
+
end
|
74
|
+
|
75
|
+
# Wrapper for Maze.check.not_nil to avoid making a breaking change.
|
76
|
+
# @deprecated TODO Remove in v7
|
77
|
+
def assert_not_nil(value, message = nil)
|
78
|
+
Maze.check.not_nil value, message
|
79
|
+
end
|
80
|
+
|
81
|
+
# Wrapper for Maze.check.not_nil to avoid making a breaking change.
|
82
|
+
# @deprecated TODO Remove in v7
|
83
|
+
def assert_block(message = 'block failed', &block)
|
84
|
+
Maze.check.block(message, &block)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Wrapper for Maze.check.true to avoid making a breaking change.
|
88
|
+
# @deprecated TODO Remove in v7
|
89
|
+
def assert_equal(value, message = nil)
|
90
|
+
Maze.check.equal value, message
|
91
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Maze
|
4
|
+
module Checks
|
5
|
+
# Assertion-backed data verification checks
|
6
|
+
class NoopCheck
|
7
|
+
def true(_test, _message = nil) end
|
8
|
+
|
9
|
+
def false(_test, _message = nil) end
|
10
|
+
|
11
|
+
def nil(_test, _message = nil) end
|
12
|
+
|
13
|
+
def not_nil(_test, _message = nil) end
|
14
|
+
|
15
|
+
def match(_pattern, _string, _message = nil) end
|
16
|
+
|
17
|
+
def equal(_expected, _actual, _message = nil) end
|
18
|
+
|
19
|
+
def not_equal(_expected, _actual, _message = nil) end
|
20
|
+
|
21
|
+
def operator(_operand1, _operator, _operand2, _message = nil) end
|
22
|
+
|
23
|
+
def kind_of(_klass, _object, _message = nil) end
|
24
|
+
|
25
|
+
def block(_message = 'block failed', &_block) end
|
26
|
+
|
27
|
+
def include(_collection, _object, _message = nil) end
|
28
|
+
alias includes include
|
29
|
+
|
30
|
+
def not_include(_collection, _object, _message = nil) end
|
31
|
+
alias not_includes not_include
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/maze/compare.rb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Maze
|
4
|
+
# Routines for conducting comparisons
|
5
|
+
module Compare
|
6
|
+
# Provides a way to delivering results of element comparisons to test steps
|
7
|
+
class Result
|
8
|
+
# @!attribute [r] reasons
|
9
|
+
# @return [Array] An array of reasons for a comparison result
|
10
|
+
attr_reader :reasons
|
11
|
+
|
12
|
+
# @!attribute [w] equal
|
13
|
+
# @param [Boolean] Indicates if the comparison indicated the elements were equal
|
14
|
+
attr_writer :equal
|
15
|
+
|
16
|
+
# @!attribute [r] keys
|
17
|
+
# @return [Array] An array of keys checked
|
18
|
+
attr_reader :keys
|
19
|
+
|
20
|
+
# Creates the Result object
|
21
|
+
def initialize
|
22
|
+
@equal = true
|
23
|
+
@keys = []
|
24
|
+
@reasons = []
|
25
|
+
end
|
26
|
+
|
27
|
+
# Indicates if the values compared to produced this result were equal
|
28
|
+
#
|
29
|
+
# @return [Boolean] Whether the elements were equal
|
30
|
+
def equal?
|
31
|
+
@equal
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the keys traversed in order to compare the end values
|
35
|
+
#
|
36
|
+
# @return [String] The keypath used in the test
|
37
|
+
def keypath
|
38
|
+
keys.length > 0 ? keys.reverse.join('.') : '<root>'
|
39
|
+
end
|
40
|
+
|
41
|
+
# Provides a standard assertion of equality, with standardised output
|
42
|
+
def assert_equal
|
43
|
+
Maze.check.true(@equal, "The compared fields do not match:\n #{result.reasons.join('\n')}")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class << self
|
48
|
+
# Compares two objects for value equality, traversing to compare each
|
49
|
+
# nested object.
|
50
|
+
#
|
51
|
+
# @param obj1 [Any] The first object to compare
|
52
|
+
# @param obj2 [Any] The second object to compare
|
53
|
+
# @param result [Result|nil] Optional. Used for comparing recursively
|
54
|
+
#
|
55
|
+
# @return [Result] The result of comparing the objects
|
56
|
+
def value(obj1, obj2, result = nil)
|
57
|
+
result ||= Result.new
|
58
|
+
return result if obj1 == 'IGNORE'
|
59
|
+
|
60
|
+
if obj1 == 'NUMBER'
|
61
|
+
if obj2.is_a?(Numeric)
|
62
|
+
return result
|
63
|
+
else
|
64
|
+
result.equal = false
|
65
|
+
result.reasons << "A Number was expected, '#{obj2.class} received"
|
66
|
+
return result
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
unless obj1.class == obj2.class
|
71
|
+
result.equal = false
|
72
|
+
result.reasons << "Object types differ - expected '#{obj1.class}', received '#{obj2.class}'"
|
73
|
+
return result
|
74
|
+
end
|
75
|
+
|
76
|
+
case obj1
|
77
|
+
when Array
|
78
|
+
array(obj1, obj2, result)
|
79
|
+
when Hash
|
80
|
+
hash(obj1, obj2, result)
|
81
|
+
when String
|
82
|
+
string(obj1, obj2, result)
|
83
|
+
else
|
84
|
+
result.reasons << "#{obj1} is not equal to #{obj2}" unless (result.equal = (obj1 == obj2))
|
85
|
+
end
|
86
|
+
result
|
87
|
+
end
|
88
|
+
|
89
|
+
# Compares two arrays for value equality, traversing and comparing each element.
|
90
|
+
# Results are written to the given Result object.
|
91
|
+
#
|
92
|
+
# @param array1 [Array] The first array to compare
|
93
|
+
# @param array2 [Array] The second array to compare
|
94
|
+
# @param result [Result] The Result to store the results
|
95
|
+
def array(array1, array2, result)
|
96
|
+
unless array1.length == array2.length
|
97
|
+
result.equal = false
|
98
|
+
result.reasons << "Expected #{array1.length} items in array, received #{array2.length}"
|
99
|
+
return
|
100
|
+
end
|
101
|
+
|
102
|
+
array1.each_with_index do |obj1, index|
|
103
|
+
value(obj1, array2[index], result)
|
104
|
+
unless result.equal?
|
105
|
+
result.keys << index.to_s
|
106
|
+
break
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Compares two hashes for value equality, traversing and comparing each key-value pair.
|
112
|
+
# Results are written to the given Result object.
|
113
|
+
#
|
114
|
+
# @param hash1 [Hash] The first hash to compare
|
115
|
+
# @param hash2 [Hash] The second hash to compare
|
116
|
+
# @param result [Result] The Result to store the results
|
117
|
+
def hash(hash1, hash2, result)
|
118
|
+
unless hash1.keys.length == hash2.keys.length
|
119
|
+
result.equal = false
|
120
|
+
missing = hash1.keys - hash2.keys
|
121
|
+
unexpected = hash2.keys - hash1.keys
|
122
|
+
result.reasons << "Missing keys from hash: #{missing.join(',')}" unless missing.empty?
|
123
|
+
result.reasons << "Unexpected keys in hash: #{unexpected.join(',')}" unless unexpected.empty?
|
124
|
+
return
|
125
|
+
end
|
126
|
+
|
127
|
+
hash1.each do |key, value|
|
128
|
+
value(value, hash2[key], result)
|
129
|
+
unless result.equal?
|
130
|
+
result.keys << key
|
131
|
+
break
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Compares two strings, writing the results to the given Result object.
|
137
|
+
#
|
138
|
+
# @param template [String] The expected string
|
139
|
+
# @param str2 [String] The string to compare
|
140
|
+
# @param result [Result] The Result to store the results
|
141
|
+
def string(template, str2, result)
|
142
|
+
return if template == str2 || regex_match(template, str2)
|
143
|
+
|
144
|
+
result.equal = false
|
145
|
+
result.reasons << "'#{str2}' does not match '#{template}'"
|
146
|
+
end
|
147
|
+
|
148
|
+
# Matches a string against a regex
|
149
|
+
#
|
150
|
+
# @param template [String] The regex to test with
|
151
|
+
# @param value [String] The value to test
|
152
|
+
#
|
153
|
+
# @return [Boolean] Whether the regex produced a match
|
154
|
+
def regex_match(template, value)
|
155
|
+
regex = template
|
156
|
+
regex = "^#{regex}$" unless regex.start_with?('^') || regex.end_with?('$')
|
157
|
+
value =~ /#{regex}/
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|