fastlane-plugin-xcresult_to_junit 0.3.2 → 0.4.2
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 +1 -1
- data/lib/fastlane/plugin/xcresult_to_junit/actions/xcresult_to_junit_action.rb +77 -107
- data/lib/fastlane/plugin/xcresult_to_junit/helper/xcresult_to_junit_helper.rb +29 -21
- data/lib/fastlane/plugin/xcresult_to_junit/version.rb +3 -1
- data/lib/fastlane/plugin/xcresult_to_junit.rb +2 -0
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a905df73bf4be24d949ec35d7e1b76f52b2264fe19279b8e5184ee30e277130b
|
4
|
+
data.tar.gz: b2fcd29e29935325fba9b246008a685849d51b1d81cf51f6c8797eab4f75d91e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9136fdc8f5d381444aee0ba6a314a3566acdd2a0cfa50364d4968e2cb19f576c4a9f25718027c7e5e297bb28d107ba17b6cd207b64f14df5a088a78b61a681a6
|
7
|
+
data.tar.gz: af066a12451e578d59f781bfe5fa43f9493a065ee0244e4b87a926e76318d3fee2b95202cccce8eee755efd2164f79e2dadb7084f91980c0fadb7adfd0aedcb4
|
data/README.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'date'
|
2
4
|
require 'fastlane/action'
|
3
5
|
require_relative '../helper/xcresult_to_junit_helper'
|
@@ -6,120 +8,88 @@ module Fastlane
|
|
6
8
|
module Actions
|
7
9
|
class XcresultToJunitAction < Action
|
8
10
|
def self.run(params)
|
9
|
-
UI.message(
|
10
|
-
|
11
|
-
all_results.each do |test_run|
|
12
|
-
next unless test_run['actionResult']['testsRef'] # Skip if section has no testRef data as this means its not a test run
|
13
|
-
map = {}
|
14
|
-
junit_folder = Helper::XcresultToJunitHelper.save_device_details_to_file(params[:output_path], test_run['runDestination'])
|
15
|
-
test_run_id = test_run['actionResult']['testsRef']['id']['_value']
|
16
|
-
all_tests = Helper::XcresultToJunitHelper.load_object(params[:xcresult_path], test_run_id)['summaries']['_values'][0]['testableSummaries']['_values']
|
17
|
-
test_suites = []
|
18
|
-
all_tests.each do |target|
|
19
|
-
target_name = target['targetName']['_value']
|
20
|
-
unless target['tests']
|
21
|
-
failure_summary = target['failureSummaries']['_values'][0]
|
22
|
-
test_suites << { name: target_name, error: failure_summary['message']['_value'] }
|
23
|
-
next
|
24
|
-
end
|
25
|
-
test_classes = []
|
26
|
-
if defined?(target['tests']['_values'][0]['subtests']['_values'][0]['subtests']['_values'])
|
27
|
-
test_classes = target['tests']['_values'][0]['subtests']['_values'][0]['subtests']['_values']
|
28
|
-
end
|
29
|
-
test_classes.each do |test_class|
|
30
|
-
suite_name = "#{target_name}.#{test_class['name']['_value']}"
|
31
|
-
suite = { name: suite_name, cases: [] }
|
32
|
-
if test_class['subtests']
|
33
|
-
test_class['subtests']['_values'].each do |test|
|
34
|
-
duration = 0
|
35
|
-
duration = test['duration']['_value'] if test['duration']
|
36
|
-
testcase_name = test['name']['_value'].tr('()', '')
|
37
|
-
tags = testcase_name.split('_')[1..-1]
|
38
|
-
testcase_name = testcase_name.split('_').first
|
39
|
-
testcase = { name: testcase_name, time: duration }
|
40
|
-
map["#{suite_name}.#{testcase_name}"] = { 'files' => [], 'tags' => tags }
|
11
|
+
UI.message('The xcresult_to_junit plugin has started!')
|
12
|
+
result = Helper::XcresultToJunitHelper.fetch_tests(params[:xcresult_path])
|
41
13
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
next unless summary['attachments']
|
48
|
-
summary['attachments']['_values'].each do |attachment|
|
49
|
-
name = attachment['name']['_value']
|
50
|
-
next unless name != 'kXCTAttachmentLegacyDiagnosticReportData'
|
51
|
-
timestamp = DateTime.parse(attachment['timestamp']['_value']).to_time.to_i
|
52
|
-
filename = attachment['filename']['_value']
|
53
|
-
folder_name = "#{suite_name}.#{testcase_name}"
|
54
|
-
id = attachment.dig('payloadRef', 'id', '_value')
|
55
|
-
next if id.nil?
|
56
|
-
Helper::XcresultToJunitHelper.fetch_screenshot(params[:xcresult_path], "#{junit_folder}/attachments/#{folder_name}", filename.to_s, id)
|
57
|
-
map[folder_name]['files'].push({ 'description' => name, 'mime-type' => 'image/png', 'path' => "#{folder_name}/#{filename}", 'timestamp' => timestamp })
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
14
|
+
devices = result['devices']
|
15
|
+
test_plans = result['testNodes']
|
16
|
+
map = {}
|
17
|
+
junit_folder = Helper::XcresultToJunitHelper.save_device_details_to_file(params[:output_path], devices)
|
18
|
+
test_suites = []
|
61
19
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
20
|
+
test_plans.each do |test_plan|
|
21
|
+
test_plan['children'].each do |test_bundle|
|
22
|
+
test_bundle['children'].each do |test_suite|
|
23
|
+
suite_name = test_suite['name']
|
24
|
+
count = 0
|
25
|
+
passed = 0
|
26
|
+
failed = 0
|
27
|
+
test_cases = []
|
28
|
+
test_suite['children'].each do |test_case|
|
29
|
+
duration = 0.0
|
30
|
+
duration = test_case['duration'].sub('s', '').to_f if test_case['duration']
|
31
|
+
full_testcase_name = test_case['name'].sub('()', '')
|
32
|
+
tags = full_testcase_name.split('_')[1..]
|
33
|
+
testcase = { name: full_testcase_name, time: duration }
|
34
|
+
count += 1
|
35
|
+
if test_case['result'] == 'Passed'
|
36
|
+
passed += 1
|
37
|
+
elsif test_case['result'] == 'Failed'
|
38
|
+
failed += 1
|
39
|
+
testcase[:failure] ||= []
|
40
|
+
testcase[:failure_location] ||= []
|
41
|
+
test_case['children'].each do |failure|
|
42
|
+
if failure['nodeType'] == 'Repetition'
|
43
|
+
failure['children'].each do |retry_failure|
|
44
|
+
testcase[:failure] << retry_failure['name']
|
45
|
+
testcase[:failure_location] << failure['name']
|
87
46
|
end
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
if test['testStatus']['_value'] == 'Failure'
|
92
|
-
failure = Helper::XcresultToJunitHelper.load_object(params[:xcresult_path], test['summaryRef']['id']['_value'])['failureSummaries']['_values'][0]
|
93
|
-
filename = failure.dig('fileName', '_value')
|
94
|
-
message = failure['message']['_value']
|
95
|
-
if filename == '<unknown>' || filename.nil?
|
96
|
-
testcase[:error] = message
|
97
|
-
else
|
98
|
-
testcase[:failure] = message
|
99
|
-
testcase[:failure_location] = "#{filename}:#{failure['lineNumber']['_value']}"
|
47
|
+
elsif failure['nodeType'] == 'Failure Message'
|
48
|
+
testcase[:failure] << failure['name']
|
49
|
+
testcase[:failure_location] << failure['name'].split(': ')[0]
|
100
50
|
end
|
101
51
|
end
|
102
|
-
suite[:cases] << testcase
|
103
52
|
end
|
53
|
+
test_cases << testcase
|
54
|
+
map["#{suite_name}.#{full_testcase_name}"] = { 'files' => [], 'tags' => tags }
|
104
55
|
end
|
105
|
-
suite
|
106
|
-
suite[:failures] = suite[:cases].count { |testcase| testcase[:failure] }
|
107
|
-
suite[:errors] = suite[:cases].count { |testcase| testcase[:error] }
|
56
|
+
suite = { name: suite_name.to_s, count: count, failures: failed, errors: 0, cases: test_cases }
|
108
57
|
test_suites << suite
|
109
58
|
end
|
110
59
|
end
|
111
|
-
Helper::XcresultToJunitHelper.generate_junit(junit_folder, test_suites)
|
112
|
-
Helper::XcresultToJunitHelper.save_screenshot_mapping(map, "#{junit_folder}/attachments/")
|
113
60
|
end
|
114
|
-
|
61
|
+
|
62
|
+
Helper::XcresultToJunitHelper.generate_junit(junit_folder, test_suites)
|
63
|
+
attachments_folder = "#{junit_folder}/attachments"
|
64
|
+
Helper::XcresultToJunitHelper.save_attachments(params[:xcresult_path], attachments_folder)
|
65
|
+
|
66
|
+
test_attachments = Helper::XcresultToJunitHelper.fetch_attachment_manifest(attachments_folder)
|
67
|
+
|
68
|
+
test_attachments.each do |test_attachment|
|
69
|
+
test_identifier = test_attachment['testIdentifier']
|
70
|
+
folder_name = test_identifier.sub('()', '').sub('/', '.')
|
71
|
+
|
72
|
+
test_attachment['attachments'].reverse().each do |attachment|
|
73
|
+
name = attachment['suggestedHumanReadableName']
|
74
|
+
filename = attachment['exportedFileName']
|
75
|
+
mime_type = 'image/png'
|
76
|
+
mime_type = 'text/plain' if filename.end_with?('.txt')
|
77
|
+
timestamp = attachment['timestamp']
|
78
|
+
map[folder_name]['files'].push({ 'description' => name, 'mime-type' => mime_type, 'path' => filename,
|
79
|
+
'timestamp' => timestamp })
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
Helper::XcresultToJunitHelper.save_screenshot_mapping(map, attachments_folder)
|
84
|
+
UI.message('The xcresult_to_junit plugin has finished!')
|
115
85
|
end
|
116
86
|
|
117
87
|
def self.description
|
118
|
-
|
88
|
+
'Produces junit xml files from Xcode 11+ xcresult files'
|
119
89
|
end
|
120
90
|
|
121
91
|
def self.authors
|
122
|
-
[
|
92
|
+
['Shane Birdsall']
|
123
93
|
end
|
124
94
|
|
125
95
|
def self.return_value
|
@@ -127,26 +97,26 @@ module Fastlane
|
|
127
97
|
end
|
128
98
|
|
129
99
|
def self.details
|
130
|
-
|
100
|
+
'By using the xcresulttool this plugin parses xcresult files and generates junit reports to be used with other tools to display iOS test results'
|
131
101
|
end
|
132
102
|
|
133
103
|
def self.available_options
|
134
104
|
[
|
135
105
|
FastlaneCore::ConfigItem.new(key: :xcresult_path,
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
106
|
+
env_name: 'XCRESULT_TO_JUNIT_XCRESULT_PATH',
|
107
|
+
description: 'The path to the xcresult file',
|
108
|
+
optional: false,
|
109
|
+
type: String),
|
140
110
|
FastlaneCore::ConfigItem.new(key: :output_path,
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
111
|
+
env_name: 'XCRESULT_TO_JUNIT_OUTPUT_PATH',
|
112
|
+
description: 'The path where the output will be placed',
|
113
|
+
optional: false,
|
114
|
+
type: String)
|
145
115
|
]
|
146
116
|
end
|
147
117
|
|
148
118
|
def self.is_supported?(platform)
|
149
|
-
[
|
119
|
+
%i[ios mac].include?(platform)
|
150
120
|
end
|
151
121
|
end
|
152
122
|
end
|
@@ -1,25 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'fastlane_core/ui/ui'
|
2
4
|
require 'json'
|
3
5
|
require 'fileutils'
|
4
6
|
|
5
7
|
module Fastlane
|
6
|
-
UI = FastlaneCore::UI unless Fastlane.const_defined?(
|
8
|
+
UI = FastlaneCore::UI unless Fastlane.const_defined?('UI')
|
7
9
|
|
8
10
|
module Helper
|
9
11
|
class XcresultToJunitHelper
|
10
|
-
def self.
|
11
|
-
JSON.parse(FastlaneCore::CommandExecutor.execute(command: "xcrun xcresulttool get
|
12
|
+
def self.fetch_tests(xcresult_path)
|
13
|
+
JSON.parse(FastlaneCore::CommandExecutor.execute(command: "xcrun xcresulttool get test-results tests --path #{xcresult_path}"))
|
12
14
|
end
|
13
15
|
|
14
|
-
def self.
|
15
|
-
|
16
|
+
def self.save_attachments(xcresult_path, output_path)
|
17
|
+
FileUtils.mkdir(output_path) unless File.directory?(output_path)
|
18
|
+
FastlaneCore::CommandExecutor.execute(command: "xcrun xcresulttool export attachments --path #{xcresult_path} --output-path #{output_path}")
|
16
19
|
end
|
17
20
|
|
18
|
-
def self.
|
19
|
-
|
20
|
-
FileUtils.mkdir(output_path)
|
21
|
-
end
|
22
|
-
FastlaneCore::CommandExecutor.execute(command: "xcrun xcresulttool export --legacy --path #{xcresult_path} --output-path \"#{output_path}/#{file_name}\" --id #{id} --type file")
|
21
|
+
def self.fetch_attachment_manifest(attachments_folder)
|
22
|
+
JSON.parse(FastlaneCore::CommandExecutor.execute(command: "cat #{attachments_folder}/manifest.json"))
|
23
23
|
end
|
24
24
|
|
25
25
|
def self.save_screenshot_mapping(map_hash, output_path)
|
@@ -28,21 +28,24 @@ module Fastlane
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
def self.save_device_details_to_file(output_path,
|
32
|
-
|
31
|
+
def self.save_device_details_to_file(output_path, devices)
|
32
|
+
device = devices[0]
|
33
33
|
device_details = {
|
34
|
-
'
|
35
|
-
'
|
36
|
-
'
|
34
|
+
'architecture' => device['architecture'],
|
35
|
+
'udid' => device['deviceId'],
|
36
|
+
'name' => device['deviceName'],
|
37
|
+
'model' => device['modelName'],
|
38
|
+
'os' => device['osVersion'],
|
39
|
+
'platform' => device['platform']
|
37
40
|
}.to_json
|
38
41
|
|
39
|
-
junit_folder = "#{output_path}/ios-#{
|
42
|
+
junit_folder = "#{output_path}/ios-#{device['deviceId']}.junit"
|
40
43
|
FileUtils.rm_rf(junit_folder)
|
41
44
|
FileUtils.mkdir_p("#{junit_folder}/attachments")
|
42
45
|
File.open("#{junit_folder}/device.json", 'w') do |f|
|
43
46
|
f << device_details
|
44
47
|
end
|
45
|
-
|
48
|
+
junit_folder
|
46
49
|
end
|
47
50
|
|
48
51
|
def self.junit_file_start
|
@@ -76,7 +79,14 @@ module Fastlane
|
|
76
79
|
end
|
77
80
|
|
78
81
|
def self.junit_testcase_failure(testcase)
|
79
|
-
|
82
|
+
if testcase[:failure].length != testcase[:failure_location].length
|
83
|
+
raise "Mismatch in lengths: testcase[:failure] and testcase[:failure_location] must have the same length."
|
84
|
+
end
|
85
|
+
for index in 0...testcase[:failure].length
|
86
|
+
failure = testcase[:failure][index]
|
87
|
+
failure_location = testcase[:failure_location][index]
|
88
|
+
puts("<failure message=#{failure.encode(xml: :attr)}>#{failure_location.encode(xml: :text)}</failure>")
|
89
|
+
end
|
80
90
|
end
|
81
91
|
|
82
92
|
def self.junit_testcase_error(testcase)
|
@@ -104,9 +114,7 @@ module Fastlane
|
|
104
114
|
elsif testcase[:error]
|
105
115
|
Helper::XcresultToJunitHelper.junit_testcase_error(testcase)
|
106
116
|
end
|
107
|
-
if testcase[:performance]
|
108
|
-
Helper::XcresultToJunitHelper.junit_testcase_performance(testcase)
|
109
|
-
end
|
117
|
+
Helper::XcresultToJunitHelper.junit_testcase_performance(testcase) if testcase[:performance]
|
110
118
|
Helper::XcresultToJunitHelper.junit_testcase_end
|
111
119
|
end
|
112
120
|
end
|
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fastlane-plugin-xcresult_to_junit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shane Birdsall
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-12-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
@@ -25,7 +25,7 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: pry
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
@@ -39,7 +39,7 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
@@ -53,7 +53,7 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: rspec
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
@@ -67,7 +67,7 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: rspec_junit_formatter
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|