fastlane-plugin-xcresult_to_junit 0.3.1 → 0.4.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 +7 -3
- data/lib/fastlane/plugin/xcresult_to_junit/actions/xcresult_to_junit_action.rb +70 -109
- data/lib/fastlane/plugin/xcresult_to_junit/helper/xcresult_to_junit_helper.rb +21 -20
- data/lib/fastlane/plugin/xcresult_to_junit/version.rb +3 -1
- data/lib/fastlane/plugin/xcresult_to_junit.rb +2 -0
- metadata +8 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 26d80a12ddd11ffb1a610f03fbe2b553e429e50318230aab292e05a7af7c562d
|
4
|
+
data.tar.gz: ead5297fa4ef5adcf6225ba13229202143a51ae2b0194f45d1452fcd1c878cef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 889460cd463ecfabf20d363fcb1cd8940eb2e9390950403fcd5409d3a9e566b0dee6aa3200cba0b0869776c87063da9af3a78a4418c0a435c04a8b7fe16fda97
|
7
|
+
data.tar.gz: 0f69ba041ff93232e524f11039b8e703ec06d0d1a26dc3a92132749f81613a4f036afaa8acd74249108d53af3608e9e7eee2ca8ff7139a30969a22a1b4ab78b5
|
data/README.md
CHANGED
@@ -31,17 +31,20 @@ The below table captures the current requirements this plugin meets. If you have
|
|
31
31
|
Check out the [example `Fastfile`](fastlane/Fastfile) to see how to use this plugin. Try it by cloning the repo, running `fastlane install_plugins` and `bundle exec fastlane test`.
|
32
32
|
|
33
33
|
Ensure you specify the following when you call scan
|
34
|
+
|
34
35
|
```ruby
|
35
36
|
fail_build: false, # Allows to continue after scan
|
36
37
|
result_bundle: true # Generate xcresult in output directory
|
37
38
|
```
|
38
39
|
|
39
40
|
After scan you can call the plugin, which will look something like this
|
41
|
+
|
40
42
|
```ruby
|
41
43
|
xcresult_to_junit(xcresult_path: './fastlane/test_output/MyScheme.test_result.xcresult', output_path: './fastlane/test_output/')
|
42
44
|
```
|
43
45
|
|
44
46
|
We can get a list of the junit reports generated by doing something like so
|
47
|
+
|
45
48
|
```ruby
|
46
49
|
junit_reports = Dir['./test_output/*.junit']
|
47
50
|
# Now we can iterate over the reports and send them where they need to go
|
@@ -52,13 +55,14 @@ We can get a list of the junit reports generated by doing something like so
|
|
52
55
|
|
53
56
|
To run both the tests, and code style validation, run
|
54
57
|
|
55
|
-
```
|
58
|
+
```bash
|
56
59
|
rake
|
57
60
|
```
|
58
61
|
|
59
62
|
To automatically fix many of the styling issues, use
|
60
|
-
|
61
|
-
|
63
|
+
|
64
|
+
```bash
|
65
|
+
rubocop -A
|
62
66
|
```
|
63
67
|
|
64
68
|
## Issues and Feedback
|
@@ -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,79 @@ 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
|
-
end
|
85
|
-
performancemetric = "\nMetric: #{metricname}\nResult: #{metricresult}%\nAverage: #{metricave}#{metricunit}\nBaseline: #{metricbaseline}#{metricunit}\nMax Deviation: #{metricmaxdev}%\n\n"
|
86
|
-
performancemetrics << performancemetric
|
87
|
-
end
|
88
|
-
testcase[:performance] = performancemetrics
|
89
|
-
end
|
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
|
+
test_case['children'].each do |failure|
|
40
|
+
testcase[:failure] = failure['name'].split(': ')[1]
|
41
|
+
testcase[:failure_location] = failure['name'].split(': ')[0]
|
90
42
|
end
|
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']}"
|
100
|
-
end
|
101
|
-
end
|
102
|
-
suite[:cases] << testcase
|
103
43
|
end
|
44
|
+
test_cases << testcase
|
45
|
+
map["#{suite_name}.#{full_testcase_name}"] = { 'files' => [], 'tags' => tags }
|
104
46
|
end
|
105
|
-
suite
|
106
|
-
suite[:failures] = suite[:cases].count { |testcase| testcase[:failure] }
|
107
|
-
suite[:errors] = suite[:cases].count { |testcase| testcase[:error] }
|
47
|
+
suite = { name: suite_name.to_s, count: count, failures: failed, errors: 0, cases: test_cases }
|
108
48
|
test_suites << suite
|
109
49
|
end
|
110
50
|
end
|
111
|
-
Helper::XcresultToJunitHelper.generate_junit(junit_folder, test_suites)
|
112
|
-
Helper::XcresultToJunitHelper.save_screenshot_mapping(map, "#{junit_folder}/attachments/")
|
113
51
|
end
|
114
|
-
|
52
|
+
|
53
|
+
Helper::XcresultToJunitHelper.generate_junit(junit_folder, test_suites)
|
54
|
+
attachments_folder = "#{junit_folder}/attachments"
|
55
|
+
Helper::XcresultToJunitHelper.save_attachments(params[:xcresult_path], attachments_folder)
|
56
|
+
|
57
|
+
test_attachments = Helper::XcresultToJunitHelper.fetch_attachment_manifest(attachments_folder)
|
58
|
+
|
59
|
+
test_attachments.each do |test_attachment|
|
60
|
+
test_identifier = test_attachment['testIdentifier']
|
61
|
+
folder_name = test_identifier.sub('()', '').sub('/', '.')
|
62
|
+
|
63
|
+
test_attachment['attachments'].each do |attachment|
|
64
|
+
name = attachment['suggestedHumanReadableName']
|
65
|
+
filename = attachment['exportedFileName']
|
66
|
+
mime_type = 'image/png'
|
67
|
+
mime_type = 'text/plain' if filename.end_with?('.txt')
|
68
|
+
timestamp = attachment['timestamp']
|
69
|
+
map[folder_name]['files'].push({ 'description' => name, 'mime-type' => mime_type, 'path' => filename,
|
70
|
+
'timestamp' => timestamp })
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
Helper::XcresultToJunitHelper.save_screenshot_mapping(map, attachments_folder)
|
75
|
+
UI.message('The xcresult_to_junit plugin has finished!')
|
115
76
|
end
|
116
77
|
|
117
78
|
def self.description
|
118
|
-
|
79
|
+
'Produces junit xml files from Xcode 11+ xcresult files'
|
119
80
|
end
|
120
81
|
|
121
82
|
def self.authors
|
122
|
-
[
|
83
|
+
['Shane Birdsall']
|
123
84
|
end
|
124
85
|
|
125
86
|
def self.return_value
|
@@ -127,26 +88,26 @@ module Fastlane
|
|
127
88
|
end
|
128
89
|
|
129
90
|
def self.details
|
130
|
-
|
91
|
+
'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
92
|
end
|
132
93
|
|
133
94
|
def self.available_options
|
134
95
|
[
|
135
96
|
FastlaneCore::ConfigItem.new(key: :xcresult_path,
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
97
|
+
env_name: 'XCRESULT_TO_JUNIT_XCRESULT_PATH',
|
98
|
+
description: 'The path to the xcresult file',
|
99
|
+
optional: false,
|
100
|
+
type: String),
|
140
101
|
FastlaneCore::ConfigItem.new(key: :output_path,
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
102
|
+
env_name: 'XCRESULT_TO_JUNIT_OUTPUT_PATH',
|
103
|
+
description: 'The path where the output will be placed',
|
104
|
+
optional: false,
|
105
|
+
type: String)
|
145
106
|
]
|
146
107
|
end
|
147
108
|
|
148
109
|
def self.is_supported?(platform)
|
149
|
-
[
|
110
|
+
%i[ios mac].include?(platform)
|
150
111
|
end
|
151
112
|
end
|
152
113
|
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 --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
|
@@ -104,9 +107,7 @@ module Fastlane
|
|
104
107
|
elsif testcase[:error]
|
105
108
|
Helper::XcresultToJunitHelper.junit_testcase_error(testcase)
|
106
109
|
end
|
107
|
-
if testcase[:performance]
|
108
|
-
Helper::XcresultToJunitHelper.junit_testcase_performance(testcase)
|
109
|
-
end
|
110
|
+
Helper::XcresultToJunitHelper.junit_testcase_performance(testcase) if testcase[:performance]
|
110
111
|
Helper::XcresultToJunitHelper.junit_testcase_end
|
111
112
|
end
|
112
113
|
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.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shane Birdsall
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-11-18 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
|
- - ">="
|
@@ -122,20 +122,6 @@ dependencies:
|
|
122
122
|
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
|
-
- !ruby/object:Gem::Dependency
|
126
|
-
name: fastlane
|
127
|
-
requirement: !ruby/object:Gem::Requirement
|
128
|
-
requirements:
|
129
|
-
- - ">="
|
130
|
-
- !ruby/object:Gem::Version
|
131
|
-
version: 2.134.0
|
132
|
-
type: :development
|
133
|
-
prerelease: false
|
134
|
-
version_requirements: !ruby/object:Gem::Requirement
|
135
|
-
requirements:
|
136
|
-
- - ">="
|
137
|
-
- !ruby/object:Gem::Version
|
138
|
-
version: 2.134.0
|
139
125
|
description:
|
140
126
|
email: shane.birdsall@fiserv.com
|
141
127
|
executables: []
|
@@ -167,8 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
167
153
|
- !ruby/object:Gem::Version
|
168
154
|
version: '0'
|
169
155
|
requirements: []
|
170
|
-
|
171
|
-
rubygems_version: 2.7.6.2
|
156
|
+
rubygems_version: 3.4.10
|
172
157
|
signing_key:
|
173
158
|
specification_version: 4
|
174
159
|
summary: Produces junit xml files from Xcode 11+ xcresult files
|