fastlane 2.225.0 → 2.227.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 +4 -4
- data/README.md +98 -98
- data/cert/lib/cert/options.rb +7 -2
- data/cert/lib/cert/runner.rb +23 -11
- data/deliver/lib/deliver/options.rb +1 -1
- data/fastlane/lib/fastlane/actions/app_store_build_number.rb +1 -1
- data/fastlane/lib/fastlane/actions/docs/sync_code_signing.md +1 -1
- data/fastlane/lib/fastlane/actions/import_from_git.rb +11 -4
- data/fastlane/lib/fastlane/actions/latest_testflight_build_number.rb +1 -1
- data/fastlane/lib/fastlane/actions/notarize.rb +4 -0
- data/fastlane/lib/fastlane/actions/onesignal.rb +1 -1
- data/fastlane/lib/fastlane/actions/register_device.rb +1 -1
- data/fastlane/lib/fastlane/actions/register_devices.rb +1 -1
- data/fastlane/lib/fastlane/actions/setup_ci.rb +14 -4
- data/fastlane/lib/fastlane/actions/testfairy.rb +41 -4
- data/fastlane/lib/fastlane/actions/unlock_keychain.rb +6 -1
- data/fastlane/lib/fastlane/fast_file.rb +9 -6
- data/fastlane/lib/fastlane/version.rb +1 -1
- data/fastlane/swift/Actions.swift +1 -1
- data/fastlane/swift/Appfile.swift +1 -1
- data/fastlane/swift/ArgumentProcessor.swift +1 -1
- data/fastlane/swift/Atomic.swift +1 -1
- data/fastlane/swift/ControlCommand.swift +1 -1
- data/fastlane/swift/Deliverfile.swift +2 -2
- data/fastlane/swift/DeliverfileProtocol.swift +2 -2
- data/fastlane/swift/Fastlane.swift +39 -13
- data/fastlane/swift/Gymfile.swift +2 -2
- data/fastlane/swift/GymfileProtocol.swift +2 -2
- data/fastlane/swift/LaneFileProtocol.swift +1 -1
- data/fastlane/swift/MainProcess.swift +1 -1
- data/fastlane/swift/Matchfile.swift +2 -2
- data/fastlane/swift/MatchfileProtocol.swift +2 -2
- data/fastlane/swift/OptionalConfigValue.swift +1 -1
- data/fastlane/swift/Plugins.swift +1 -1
- data/fastlane/swift/Precheckfile.swift +2 -2
- data/fastlane/swift/PrecheckfileProtocol.swift +2 -2
- data/fastlane/swift/RubyCommand.swift +1 -1
- data/fastlane/swift/RubyCommandable.swift +1 -1
- data/fastlane/swift/Runner.swift +1 -1
- data/fastlane/swift/RunnerArgument.swift +1 -1
- data/fastlane/swift/Scanfile.swift +2 -2
- data/fastlane/swift/ScanfileProtocol.swift +2 -2
- data/fastlane/swift/Screengrabfile.swift +2 -2
- data/fastlane/swift/ScreengrabfileProtocol.swift +2 -2
- data/fastlane/swift/Snapshotfile.swift +2 -2
- data/fastlane/swift/SnapshotfileProtocol.swift +2 -2
- data/fastlane/swift/SocketClient.swift +1 -1
- data/fastlane/swift/SocketClientDelegateProtocol.swift +1 -1
- data/fastlane/swift/SocketResponse.swift +1 -1
- data/fastlane/swift/main.swift +1 -1
- data/fastlane_core/lib/fastlane_core/helper.rb +6 -1
- data/match/lib/assets/READMETemplate.md +2 -2
- data/match/lib/match/generator.rb +2 -2
- data/match/lib/match/runner.rb +1 -1
- data/precheck/lib/precheck/options.rb +1 -1
- data/produce/lib/produce/options.rb +1 -1
- data/spaceship/lib/spaceship/connect_api/models/certificate.rb +1 -0
- data/supply/lib/supply/uploader.rb +22 -11
- data/trainer/lib/trainer/legacy_xcresult.rb +586 -0
- data/trainer/lib/trainer/options.rb +5 -0
- data/trainer/lib/trainer/plist_test_summary_parser.rb +84 -0
- data/trainer/lib/trainer/test_parser.rb +12 -293
- data/trainer/lib/trainer/xcresult/helper.rb +53 -0
- data/trainer/lib/trainer/xcresult/repetition.rb +39 -0
- data/trainer/lib/trainer/xcresult/test_case.rb +221 -0
- data/trainer/lib/trainer/xcresult/test_case_attributes.rb +49 -0
- data/trainer/lib/trainer/xcresult/test_plan.rb +91 -0
- data/trainer/lib/trainer/xcresult/test_suite.rb +134 -0
- data/trainer/lib/trainer/xcresult.rb +31 -388
- data/trainer/lib/trainer.rb +3 -1
- metadata +31 -23
@@ -0,0 +1,221 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
require_relative 'test_case_attributes'
|
3
|
+
require_relative 'repetition'
|
4
|
+
|
5
|
+
module Trainer
|
6
|
+
module XCResult
|
7
|
+
# Represents a test case, including its retries (aka repetitions)
|
8
|
+
class TestCase
|
9
|
+
include TestCaseAttributes
|
10
|
+
|
11
|
+
attr_reader :name
|
12
|
+
attr_reader :identifier
|
13
|
+
attr_reader :duration
|
14
|
+
attr_reader :result
|
15
|
+
attr_reader :classname
|
16
|
+
attr_reader :argument
|
17
|
+
# @return [Array<Repetition>] Array of retry attempts for this test case, **including the initial attempt**
|
18
|
+
# This will be `nil` if the test case was not run multiple times, but will contain all repetitions if it was run more than once.
|
19
|
+
attr_reader :retries
|
20
|
+
attr_reader :failure_messages
|
21
|
+
attr_reader :source_references
|
22
|
+
attr_reader :attachments
|
23
|
+
attr_reader :tags
|
24
|
+
|
25
|
+
def initialize(
|
26
|
+
name:, identifier:, duration:, result:, classname:, argument: nil, tags: [], retries: nil,
|
27
|
+
failure_messages: [], source_references: [], attachments: []
|
28
|
+
)
|
29
|
+
@name = name
|
30
|
+
@identifier = identifier
|
31
|
+
@duration = duration
|
32
|
+
@result = result
|
33
|
+
@classname = classname
|
34
|
+
@argument = argument
|
35
|
+
@tags = tags
|
36
|
+
@retries = retries
|
37
|
+
@failure_messages = failure_messages
|
38
|
+
@source_references = source_references
|
39
|
+
@attachments = attachments
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.from_json(node:)
|
43
|
+
# Handle test case arguments
|
44
|
+
argument_nodes = Helper.find_json_children(node, 'Arguments')
|
45
|
+
argument_nodes = [nil] if argument_nodes.empty?
|
46
|
+
|
47
|
+
# Generate test cases for each argument
|
48
|
+
argument_nodes.map do |arg_node|
|
49
|
+
# For repetition nodes, failure messages, source refs, attachments and result attributes,
|
50
|
+
# Search them as children of the argument child node if present, of the test case node otherwise.
|
51
|
+
node_for_attributes = arg_node || node
|
52
|
+
|
53
|
+
retries = Helper.find_json_children(node_for_attributes, 'Repetition', 'Test Case Run')
|
54
|
+
&.map { |rep_node| Repetition.from_json(node: rep_node) } || []
|
55
|
+
|
56
|
+
failure_messages = if retries.empty?
|
57
|
+
extract_failure_messages(node_for_attributes)
|
58
|
+
else
|
59
|
+
retries.flat_map(&:failure_messages)
|
60
|
+
end
|
61
|
+
|
62
|
+
source_references = if retries.empty?
|
63
|
+
extract_source_references(node_for_attributes)
|
64
|
+
else
|
65
|
+
retries.flat_map(&:source_references)
|
66
|
+
end
|
67
|
+
|
68
|
+
attachments = if retries.empty?
|
69
|
+
extract_attachments(node_for_attributes)
|
70
|
+
else
|
71
|
+
retries.flat_map(&:attachments)
|
72
|
+
end
|
73
|
+
|
74
|
+
new(
|
75
|
+
name: node['name'],
|
76
|
+
identifier: node['nodeIdentifier'],
|
77
|
+
duration: parse_duration(node['duration']),
|
78
|
+
result: node_for_attributes['result'],
|
79
|
+
classname: extract_classname(node),
|
80
|
+
argument: arg_node&.[]('name'), # Only set if there is an argument
|
81
|
+
tags: node['tags'] || [],
|
82
|
+
retries: retries,
|
83
|
+
failure_messages: failure_messages,
|
84
|
+
source_references: source_references,
|
85
|
+
attachments: attachments
|
86
|
+
)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Generates XML nodes for the test case
|
91
|
+
#
|
92
|
+
# @return [Array<REXML::Element>] An array of XML <testcase> elements
|
93
|
+
#
|
94
|
+
# - If no retries, the array contains a single <testcase> element
|
95
|
+
# - If retries, the array contains one <testcase> element per retry
|
96
|
+
def to_xml_nodes
|
97
|
+
runs = @retries.nil? || @retries.empty? ? [nil] : @retries
|
98
|
+
|
99
|
+
runs.map do |run|
|
100
|
+
Helper.create_xml_element('testcase',
|
101
|
+
name: if @argument.nil?
|
102
|
+
@name
|
103
|
+
else
|
104
|
+
@name.match?(/(\(.*\))/) ? @name.gsub(/(\(.*\))/, "(#{@argument})") : "#{@name} (#{@argument})"
|
105
|
+
end,
|
106
|
+
classname: @classname,
|
107
|
+
time: (run || self).duration.to_s).tap do |testcase|
|
108
|
+
add_xml_result_elements(testcase, run || self)
|
109
|
+
add_properties_to_xml(testcase, repetition_name: run&.name)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def retries_count
|
115
|
+
@retries&.count || 0
|
116
|
+
end
|
117
|
+
|
118
|
+
def total_tests_count
|
119
|
+
retries_count > 0 ? retries_count : 1
|
120
|
+
end
|
121
|
+
|
122
|
+
def total_failures_count
|
123
|
+
if retries_count > 0
|
124
|
+
@retries.count(&:failed?)
|
125
|
+
elsif failed?
|
126
|
+
1
|
127
|
+
else
|
128
|
+
0
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.extract_classname(node)
|
133
|
+
return nil if node['nodeIdentifier'].nil?
|
134
|
+
|
135
|
+
parts = node['nodeIdentifier'].split('/')
|
136
|
+
parts[0...-1].join('.')
|
137
|
+
end
|
138
|
+
private_class_method :extract_classname
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
# Adds <properties> element to the XML <testcase> element
|
143
|
+
#
|
144
|
+
# @param testcase [REXML::Element] The XML testcase element to add properties to
|
145
|
+
# @param repetition_name [String, nil] Name of the retry attempt, if this is a retry
|
146
|
+
#
|
147
|
+
# Properties added:
|
148
|
+
# - if argument is present:
|
149
|
+
# - `testname`: Raw test name (as in such case, <testcase name="…"> would contain a mix of the test name and the argument)
|
150
|
+
# - `argument`: Test argument value
|
151
|
+
# - `repetitionN`: Name of the retry attempt if present
|
152
|
+
# - `source_referenceN`: Source code references (file/line) for failures
|
153
|
+
# - `attachmentN`: Test attachments like screenshots
|
154
|
+
# - `tagN`: Test tags/categories
|
155
|
+
#
|
156
|
+
# <properties> element is only added to the XML if at least one property exists
|
157
|
+
def add_properties_to_xml(testcase, repetition_name: nil)
|
158
|
+
properties = Helper.create_xml_element('properties')
|
159
|
+
|
160
|
+
# Add argument as property
|
161
|
+
if @argument
|
162
|
+
name_prop = Helper.create_xml_element('property', name: "testname", value: @name)
|
163
|
+
properties.add_element(name_prop)
|
164
|
+
prop = Helper.create_xml_element('property', name: "argument", value: @argument)
|
165
|
+
properties.add_element(prop)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Add repetition as property
|
169
|
+
if repetition_name
|
170
|
+
prop = Helper.create_xml_element('property', name: "repetition", value: repetition_name)
|
171
|
+
properties.add_element(prop)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Add source references as properties
|
175
|
+
(@source_references || []).each_with_index do |ref, index|
|
176
|
+
prop = Helper.create_xml_element('property', name: "source_reference#{index + 1}", value: ref)
|
177
|
+
properties.add_element(prop)
|
178
|
+
end
|
179
|
+
|
180
|
+
# Add attachments as properties
|
181
|
+
(@attachments || []).each_with_index do |attachment, index|
|
182
|
+
prop = Helper.create_xml_element('property', name: "attachment#{index + 1}", value: attachment)
|
183
|
+
properties.add_element(prop)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Add tags as properties
|
187
|
+
(@tags || []).sort.each_with_index do |tag, index|
|
188
|
+
prop = Helper.create_xml_element('property', name: "tag#{index + 1}", value: tag)
|
189
|
+
properties.add_element(prop)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Only add properties to testcase if it has child elements
|
193
|
+
testcase.add_element(properties) if properties.elements.any?
|
194
|
+
end
|
195
|
+
|
196
|
+
# Adds <failure> and <skipped> elements to the XML <testcase> element based on test status
|
197
|
+
#
|
198
|
+
# @param testcase [REXML::Element] The XML testcase element to add result elements to
|
199
|
+
# @param test_obj [Repetition, TestCase] Object representing the test result
|
200
|
+
# This can be either a Repetition object or the TestCase itself.
|
201
|
+
# Must respond to the following methods:
|
202
|
+
# - failed? [Boolean]: Indicates if the test failed
|
203
|
+
# - skipped? [Boolean]: Indicates if the test was skipped
|
204
|
+
# - failure_messages [Array<String>, nil]: List of failure messages (optional)
|
205
|
+
#
|
206
|
+
# Adds:
|
207
|
+
# - <failure> elements with messages for failed tests
|
208
|
+
# - <skipped> element for skipped tests
|
209
|
+
# - No elements added for passed tests
|
210
|
+
def add_xml_result_elements(testcase, test_obj)
|
211
|
+
if test_obj.failed?
|
212
|
+
(test_obj.failure_messages&.any? ? test_obj.failure_messages : [nil]).each do |msg|
|
213
|
+
testcase.add_element(Helper.create_xml_element('failure', message: msg))
|
214
|
+
end
|
215
|
+
elsif test_obj.skipped?
|
216
|
+
testcase.add_element(Helper.create_xml_element('skipped', message: test_obj.failure_messages&.first))
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Trainer
|
2
|
+
module XCResult
|
3
|
+
# Mixin for shared attributes between TestCase and Repetition
|
4
|
+
module TestCaseAttributes
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
def passed?
|
10
|
+
@result == 'Passed'
|
11
|
+
end
|
12
|
+
|
13
|
+
def failed?
|
14
|
+
@result == 'Failed'
|
15
|
+
end
|
16
|
+
|
17
|
+
def skipped?
|
18
|
+
@result == 'Skipped'
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
def parse_duration(duration_str)
|
23
|
+
return 0.0 if duration_str.nil?
|
24
|
+
|
25
|
+
# Handle comma-separated duration, and remove 's' suffix
|
26
|
+
duration_str.gsub(',', '.').chomp('s').to_f
|
27
|
+
end
|
28
|
+
|
29
|
+
def extract_failure_messages(node)
|
30
|
+
node['children']
|
31
|
+
&.select { |child| child['nodeType'] == 'Failure Message' }
|
32
|
+
&.map { |msg| msg['name'] } || []
|
33
|
+
end
|
34
|
+
|
35
|
+
def extract_source_references(node)
|
36
|
+
node['children']
|
37
|
+
&.select { |child| child['nodeType'] == 'Source Code Reference' }
|
38
|
+
&.map { |ref| ref['name'] } || []
|
39
|
+
end
|
40
|
+
|
41
|
+
def extract_attachments(node)
|
42
|
+
node['children']
|
43
|
+
&.select { |child| child['nodeType'] == 'Attachment' }
|
44
|
+
&.map { |attachment| attachment['name'] } || []
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require_relative 'test_suite'
|
2
|
+
|
3
|
+
module Trainer
|
4
|
+
module XCResult
|
5
|
+
# Represents a collection of test suites + the configuration, and device used to run them
|
6
|
+
class TestPlan
|
7
|
+
attr_reader :test_suites, :name, :configurations, :devices
|
8
|
+
attr_accessor :output_remove_retry_attempts
|
9
|
+
|
10
|
+
def initialize(test_suites:, configurations: [], devices: [], output_remove_retry_attempts: false)
|
11
|
+
@test_suites = test_suites
|
12
|
+
@configurations = configurations
|
13
|
+
@devices = devices
|
14
|
+
@output_remove_retry_attempts = output_remove_retry_attempts
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.from_json(json:)
|
18
|
+
# Extract configurations and devices
|
19
|
+
configurations = json['testPlanConfigurations'] || []
|
20
|
+
devices = json['devices'] || []
|
21
|
+
|
22
|
+
# Find the test plan node (root of test results)
|
23
|
+
test_plan_node = json['testNodes']&.find { |node| node['nodeType'] == 'Test Plan' }
|
24
|
+
return new(test_suites: []) if test_plan_node.nil?
|
25
|
+
|
26
|
+
# Convert test plan node's children (test bundles) to TestSuite objects
|
27
|
+
test_suites = test_plan_node['children']&.map do |test_bundle|
|
28
|
+
TestSuite.from_json(
|
29
|
+
node: test_bundle
|
30
|
+
)
|
31
|
+
end || []
|
32
|
+
|
33
|
+
new(
|
34
|
+
test_suites: test_suites,
|
35
|
+
configurations: configurations,
|
36
|
+
devices: devices
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Allows iteration over test suites. Used by TestParser to collect test results
|
41
|
+
include Enumerable
|
42
|
+
def each(&block)
|
43
|
+
test_suites.map(&:to_hash).each(&block)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Generates a JUnit-compatible XML representation of the test plan
|
47
|
+
# See https://github.com/testmoapp/junitxml/
|
48
|
+
def to_xml
|
49
|
+
# Create the root testsuites element with calculated summary attributes
|
50
|
+
testsuites = Helper.create_xml_element('testsuites',
|
51
|
+
tests: test_suites.sum(&:test_cases_count).to_s,
|
52
|
+
failures: test_suites.sum(&:failures_count).to_s,
|
53
|
+
skipped: test_suites.sum(&:skipped_count).to_s,
|
54
|
+
time: test_suites.sum(&:duration).to_s)
|
55
|
+
|
56
|
+
# Create <properties> node for configuration and device, to be applied to each suite node
|
57
|
+
properties = Helper.create_xml_element('properties').tap do |node|
|
58
|
+
@configurations.each do |config|
|
59
|
+
config_prop = Helper.create_xml_element('property', name: 'Configuration', value: config['configurationName'])
|
60
|
+
node.add_element(config_prop)
|
61
|
+
end
|
62
|
+
|
63
|
+
@devices.each do |device|
|
64
|
+
device_prop = Helper.create_xml_element('property', name: 'device', value: "#{device.fetch('modelName', 'Unknown Device')} (#{device.fetch('osVersion', 'Unknown OS Version')})")
|
65
|
+
node.add_element(device_prop)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Add each test suite to the root
|
70
|
+
test_suites.each do |suite|
|
71
|
+
suite_node = suite.to_xml(output_remove_retry_attempts: output_remove_retry_attempts)
|
72
|
+
# In JUnit conventions, the <testsuites> root element can't have properties
|
73
|
+
# So we add the <properties> node to each child <testsuite> node instead
|
74
|
+
suite_node.add_element(properties.dup) if properties.elements.any?
|
75
|
+
testsuites.add_element(suite_node)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Convert to XML string with prologue
|
79
|
+
doc = REXML::Document.new
|
80
|
+
doc << REXML::XMLDecl.new('1.0', 'UTF-8')
|
81
|
+
|
82
|
+
doc.add(testsuites)
|
83
|
+
|
84
|
+
formatter = REXML::Formatters::Pretty.new
|
85
|
+
output = String.new
|
86
|
+
formatter.write(doc, output)
|
87
|
+
output
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require_relative 'test_case'
|
2
|
+
|
3
|
+
module Trainer
|
4
|
+
module XCResult
|
5
|
+
# Represents a test suite, including its test cases and sub-suites
|
6
|
+
class TestSuite
|
7
|
+
attr_reader :name
|
8
|
+
attr_reader :identifier
|
9
|
+
attr_reader :type
|
10
|
+
attr_reader :result
|
11
|
+
attr_reader :test_cases
|
12
|
+
attr_reader :sub_suites
|
13
|
+
attr_reader :tags
|
14
|
+
|
15
|
+
def initialize(name:, identifier:, type:, result:, tags: [], test_cases: [], sub_suites: [])
|
16
|
+
@name = name
|
17
|
+
@identifier = identifier
|
18
|
+
@type = type
|
19
|
+
@result = result
|
20
|
+
@tags = tags
|
21
|
+
@test_cases = test_cases
|
22
|
+
@sub_suites = sub_suites
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.from_json(node:)
|
26
|
+
# Create initial TestSuite with basic attributes
|
27
|
+
test_suite = new(
|
28
|
+
name: node['name'],
|
29
|
+
identifier: node['nodeIdentifier'],
|
30
|
+
type: node['nodeType'],
|
31
|
+
result: node['result'],
|
32
|
+
tags: node['tags'] || []
|
33
|
+
)
|
34
|
+
|
35
|
+
# Process children to populate test_cases and sub_suites
|
36
|
+
test_suite.process_children(node['children'] || [])
|
37
|
+
|
38
|
+
test_suite
|
39
|
+
end
|
40
|
+
|
41
|
+
def passed?
|
42
|
+
@result == 'Passed'
|
43
|
+
end
|
44
|
+
|
45
|
+
def failed?
|
46
|
+
@result == 'Failed'
|
47
|
+
end
|
48
|
+
|
49
|
+
def skipped?
|
50
|
+
@result == 'Skipped'
|
51
|
+
end
|
52
|
+
|
53
|
+
def duration
|
54
|
+
@duration ||= @test_cases.sum(&:duration) + @sub_suites.sum(&:duration)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_cases_count
|
58
|
+
@test_cases_count ||= @test_cases.count + @sub_suites.sum(&:test_cases_count)
|
59
|
+
end
|
60
|
+
|
61
|
+
def failures_count
|
62
|
+
@failures_count ||= @test_cases.count(&:failed?) + @sub_suites.sum(&:failures_count)
|
63
|
+
end
|
64
|
+
|
65
|
+
def skipped_count
|
66
|
+
@skipped_count ||= @test_cases.count(&:skipped?) + @sub_suites.sum(&:skipped_count)
|
67
|
+
end
|
68
|
+
|
69
|
+
def total_tests_count
|
70
|
+
@test_cases.sum(&:total_tests_count) +
|
71
|
+
@sub_suites.sum(&:total_tests_count)
|
72
|
+
end
|
73
|
+
|
74
|
+
def total_failures_count
|
75
|
+
@test_cases.sum(&:total_failures_count) +
|
76
|
+
@sub_suites.sum(&:total_failures_count)
|
77
|
+
end
|
78
|
+
|
79
|
+
def total_retries_count
|
80
|
+
@test_cases.sum(&:retries_count) +
|
81
|
+
@sub_suites.sum(&:total_retries_count)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Hash representation used by TestParser to collect test results
|
85
|
+
def to_hash
|
86
|
+
{
|
87
|
+
number_of_tests: total_tests_count,
|
88
|
+
number_of_failures: total_failures_count,
|
89
|
+
number_of_tests_excluding_retries: test_cases_count,
|
90
|
+
number_of_failures_excluding_retries: failures_count,
|
91
|
+
number_of_retries: total_retries_count,
|
92
|
+
number_of_skipped: skipped_count
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
# Generates a JUnit-compatible XML representation of the test suite
|
97
|
+
# See https://github.com/testmoapp/junitxml/
|
98
|
+
def to_xml(output_remove_retry_attempts: false)
|
99
|
+
testsuite = Helper.create_xml_element('testsuite',
|
100
|
+
name: @name,
|
101
|
+
time: duration.to_s,
|
102
|
+
tests: test_cases_count.to_s,
|
103
|
+
failures: failures_count.to_s,
|
104
|
+
skipped: skipped_count.to_s)
|
105
|
+
|
106
|
+
# Add test cases
|
107
|
+
@test_cases.each do |test_case|
|
108
|
+
runs = test_case.to_xml_nodes
|
109
|
+
runs = runs.last(1) if output_remove_retry_attempts
|
110
|
+
runs.each { |node| testsuite.add_element(node) }
|
111
|
+
end
|
112
|
+
|
113
|
+
# Add sub-suites
|
114
|
+
@sub_suites.each do |sub_suite|
|
115
|
+
testsuite.add_element(sub_suite.to_xml(output_remove_retry_attempts: output_remove_retry_attempts))
|
116
|
+
end
|
117
|
+
|
118
|
+
testsuite
|
119
|
+
end
|
120
|
+
|
121
|
+
def process_children(children)
|
122
|
+
children.each do |child|
|
123
|
+
case child['nodeType']
|
124
|
+
when 'Test Case'
|
125
|
+
# Use from_json to generate multiple test cases if needed
|
126
|
+
@test_cases.concat(TestCase.from_json(node: child))
|
127
|
+
when 'Test Suite', 'Unit test bundle', 'UI test bundle'
|
128
|
+
@sub_suites << TestSuite.from_json(node: child)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|