@allurereport/reader 3.0.0-beta.10
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.
- package/README.md +25 -0
- package/dist/allure1/index.d.ts +2 -0
- package/dist/allure1/index.js +289 -0
- package/dist/allure2/index.d.ts +2 -0
- package/dist/allure2/index.js +306 -0
- package/dist/allure2/model.d.ts +78 -0
- package/dist/allure2/model.js +21 -0
- package/dist/attachments/index.d.ts +2 -0
- package/dist/attachments/index.js +8 -0
- package/dist/cucumberjson/index.d.ts +2 -0
- package/dist/cucumberjson/index.js +306 -0
- package/dist/cucumberjson/model.d.ts +65 -0
- package/dist/cucumberjson/model.js +2 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/junitxml/index.d.ts +2 -0
- package/dist/junitxml/index.js +158 -0
- package/dist/model.d.ts +20 -0
- package/dist/model.js +1 -0
- package/dist/properties.d.ts +1 -0
- package/dist/properties.js +229 -0
- package/dist/toolRunner.d.ts +17 -0
- package/dist/toolRunner.js +143 -0
- package/dist/utils.d.ts +13 -0
- package/dist/utils.js +27 -0
- package/dist/validation.d.ts +45 -0
- package/dist/validation.js +51 -0
- package/dist/xcresult/bundle.d.ts +7 -0
- package/dist/xcresult/bundle.js +63 -0
- package/dist/xcresult/index.d.ts +2 -0
- package/dist/xcresult/index.js +91 -0
- package/dist/xcresult/model.d.ts +61 -0
- package/dist/xcresult/model.js +1 -0
- package/dist/xcresult/utils.d.ts +43 -0
- package/dist/xcresult/utils.js +378 -0
- package/dist/xcresult/xcresulttool/cli.d.ts +10 -0
- package/dist/xcresult/xcresulttool/cli.js +46 -0
- package/dist/xcresult/xcresulttool/index.d.ts +7 -0
- package/dist/xcresult/xcresulttool/index.js +311 -0
- package/dist/xcresult/xcresulttool/legacy/index.d.ts +10 -0
- package/dist/xcresult/xcresulttool/legacy/index.js +456 -0
- package/dist/xcresult/xcresulttool/legacy/model.d.ts +75 -0
- package/dist/xcresult/xcresulttool/legacy/model.js +1 -0
- package/dist/xcresult/xcresulttool/legacy/parsing.d.ts +15 -0
- package/dist/xcresult/xcresulttool/legacy/parsing.js +41 -0
- package/dist/xcresult/xcresulttool/legacy/utils.d.ts +7 -0
- package/dist/xcresult/xcresulttool/legacy/utils.js +33 -0
- package/dist/xcresult/xcresulttool/legacy/xcModel.d.ts +357 -0
- package/dist/xcresult/xcresulttool/legacy/xcModel.js +5 -0
- package/dist/xcresult/xcresulttool/model.d.ts +17 -0
- package/dist/xcresult/xcresulttool/model.js +6 -0
- package/dist/xcresult/xcresulttool/utils.d.ts +3 -0
- package/dist/xcresult/xcresulttool/utils.js +74 -0
- package/dist/xcresult/xcresulttool/xcModel.d.ts +96 -0
- package/dist/xcresult/xcresulttool/xcModel.js +18 -0
- package/dist/xml-utils.d.ts +5 -0
- package/dist/xml-utils.js +29 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Reader
|
|
2
|
+
|
|
3
|
+
[<img src="https://allurereport.org/public/img/allure-report.svg" height="85px" alt="Allure Report logo" align="right" />](https://allurereport.org "Allure Report")
|
|
4
|
+
|
|
5
|
+
- Learn more about Allure Report at https://allurereport.org
|
|
6
|
+
- 📚 [Documentation](https://allurereport.org/docs/) – discover official documentation for Allure Report
|
|
7
|
+
- ❓ [Questions and Support](https://github.com/orgs/allure-framework/discussions/categories/questions-support) – get help from the team and community
|
|
8
|
+
- 📢 [Official announcements](https://github.com/orgs/allure-framework/discussions/categories/announcements) – be in touch with the latest updates
|
|
9
|
+
- 💬 [General Discussion ](https://github.com/orgs/allure-framework/discussions/categories/general-discussion) – engage in casual conversations, share insights and ideas with the community
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
Set of Allure Reader implementations which allow to read different files formats as Allure Results files.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
Use your favorite package manager to install the package:
|
|
20
|
+
|
|
21
|
+
```shell
|
|
22
|
+
npm add @allurereport/reader
|
|
23
|
+
yarn add @allurereport/reader
|
|
24
|
+
pnpm add @allurereport/reader
|
|
25
|
+
```
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { XMLParser } from "fast-xml-parser";
|
|
2
|
+
import * as console from "node:console";
|
|
3
|
+
import { ensureInt, ensureString } from "../utils.js";
|
|
4
|
+
import { cleanBadXmlCharacters, isStringAnyRecord, isStringAnyRecordArray } from "../xml-utils.js";
|
|
5
|
+
const DEFAULT_TEST_NAME = "The test's name is not defined";
|
|
6
|
+
const DEFAULT_STEP_NAME = "The step's name is not defined";
|
|
7
|
+
const SUITE_LABEL_NAME = "suite";
|
|
8
|
+
const TEST_CLASS_LABEL_NAME = "testClass";
|
|
9
|
+
const TEST_METHOD_LABEL_NAME = "testMethod";
|
|
10
|
+
const TEST_ID_LABEL_NAME = "testCaseId";
|
|
11
|
+
const HISTORY_ID_LABEL_NAME = "historyId";
|
|
12
|
+
const STATUS_DETAILS_LABEL_NAME = "status_details";
|
|
13
|
+
const ISSUE_LABEL_NAME = "issue";
|
|
14
|
+
const TMS_LABEL_NAME = "testId";
|
|
15
|
+
const ISSUE_LINK_TYPE = "issue";
|
|
16
|
+
const TMS_LINK_TYPE = "tms";
|
|
17
|
+
const RESERVER_LABEL_NAMES = new Set([
|
|
18
|
+
TEST_CLASS_LABEL_NAME,
|
|
19
|
+
TEST_METHOD_LABEL_NAME,
|
|
20
|
+
TEST_ID_LABEL_NAME,
|
|
21
|
+
HISTORY_ID_LABEL_NAME,
|
|
22
|
+
ISSUE_LABEL_NAME,
|
|
23
|
+
TMS_LABEL_NAME,
|
|
24
|
+
STATUS_DETAILS_LABEL_NAME,
|
|
25
|
+
]);
|
|
26
|
+
const arrayTags = new Set(["attachment", "label", "parameter", "step", "test-case"]);
|
|
27
|
+
const xmlParser = new XMLParser({
|
|
28
|
+
parseTagValue: false,
|
|
29
|
+
ignoreAttributes: false,
|
|
30
|
+
attributeNamePrefix: "",
|
|
31
|
+
removeNSPrefix: true,
|
|
32
|
+
allowBooleanAttributes: true,
|
|
33
|
+
isArray: arrayTags.has.bind(arrayTags),
|
|
34
|
+
});
|
|
35
|
+
const readerId = "allure1";
|
|
36
|
+
export const allure1 = {
|
|
37
|
+
read: async (visitor, data) => {
|
|
38
|
+
if (data.getOriginalFileName().endsWith("-testsuite.xml")) {
|
|
39
|
+
try {
|
|
40
|
+
const asBuffer = await data.asBuffer();
|
|
41
|
+
if (!asBuffer) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const content = cleanBadXmlCharacters(asBuffer).toString("utf-8");
|
|
45
|
+
const parsed = xmlParser.parse(content);
|
|
46
|
+
if (!isStringAnyRecord(parsed)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return await parseRootElement(visitor, parsed);
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
console.error("error parsing", data.getOriginalFileName(), e);
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
},
|
|
58
|
+
readerId: () => readerId,
|
|
59
|
+
};
|
|
60
|
+
const parseRootElement = async (visitor, xml) => {
|
|
61
|
+
const { "test-suite": testSuite } = xml;
|
|
62
|
+
if (!isStringAnyRecord(testSuite)) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
return await parseTestSuite(visitor, testSuite);
|
|
66
|
+
};
|
|
67
|
+
const parseTestSuite = async (visitor, testSuite) => {
|
|
68
|
+
const { "name": testSuiteName, "title": testSuiteTitle, "description": descriptionElement, "test-cases": testCases, "labels": labelsElement, } = testSuite;
|
|
69
|
+
if (!isStringAnyRecord(testCases)) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
const { "test-case": testCase } = testCases;
|
|
73
|
+
if (!isStringAnyRecordArray(testCase)) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
const labels = parseLabels(labelsElement);
|
|
77
|
+
for (const tc of testCase) {
|
|
78
|
+
await parseTestCase(visitor, {
|
|
79
|
+
name: ensureString(testSuiteName),
|
|
80
|
+
title: ensureString(testSuiteTitle),
|
|
81
|
+
...parseDescription(descriptionElement),
|
|
82
|
+
labels,
|
|
83
|
+
}, tc);
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
86
|
+
};
|
|
87
|
+
const parseTestCase = async (visitor, testSuite, testCase) => {
|
|
88
|
+
const { name: suiteName, title: suiteTitle, description: suiteDescription, descriptionHtml: suiteDescriptionHtml, labels: suiteLabels, } = testSuite;
|
|
89
|
+
const { name: nameElement, title: titleElement, description: descriptionElement, status: statusElement, failure: failureElement, parameters: parametersElement, steps: stepsElement, start: startElement, stop: stopElement, attachments: attachmentsElement, labels: labelsElement, } = testCase;
|
|
90
|
+
const testCaseName = ensureString(nameElement);
|
|
91
|
+
const testCaseTitle = ensureString(titleElement);
|
|
92
|
+
const name = testCaseTitle ?? testCaseName ?? DEFAULT_TEST_NAME;
|
|
93
|
+
const status = convertStatus(ensureString(statusElement));
|
|
94
|
+
const { description: testCaseDescription, descriptionHtml: testCaseDescriptionHtml } = parseDescription(descriptionElement);
|
|
95
|
+
const description = combineDescriptions(suiteDescription, testCaseDescription, "\n\n");
|
|
96
|
+
const descriptionHtml = combineDescriptions(suiteDescriptionHtml, testCaseDescriptionHtml, "<br>");
|
|
97
|
+
const { start, stop, duration } = parseTime(startElement, stopElement);
|
|
98
|
+
const testCaseLabels = parseLabels(labelsElement);
|
|
99
|
+
const allLabels = [...suiteLabels, ...testCaseLabels];
|
|
100
|
+
const testId = maybeFindLabelValue(allLabels, TEST_ID_LABEL_NAME);
|
|
101
|
+
const historyId = maybeFindLabelValue(allLabels, HISTORY_ID_LABEL_NAME);
|
|
102
|
+
const testClass = resolveTestClass(testCaseLabels, suiteLabels, suiteName, suiteTitle);
|
|
103
|
+
const testMethod = resolveTestMethod(testCaseLabels, testCaseName, testCaseTitle);
|
|
104
|
+
const fullName = getFullName(testClass, testMethod);
|
|
105
|
+
const statusDetailLabels = findAllLabels(allLabels, STATUS_DETAILS_LABEL_NAME);
|
|
106
|
+
const flaky = labelValueExistsIgnoreCase(statusDetailLabels, "flaky");
|
|
107
|
+
const muted = labelValueExistsIgnoreCase(statusDetailLabels, "muted");
|
|
108
|
+
const known = labelValueExistsIgnoreCase(statusDetailLabels, "known");
|
|
109
|
+
const links = [
|
|
110
|
+
...createLinks(allLabels, ISSUE_LABEL_NAME, ISSUE_LINK_TYPE),
|
|
111
|
+
...createLinks(allLabels, TMS_LABEL_NAME, TMS_LINK_TYPE),
|
|
112
|
+
];
|
|
113
|
+
const labels = composeTestResultLabels(allLabels, testClass, testMethod, suiteTitle ?? suiteName);
|
|
114
|
+
const { message, trace } = parseFailure(failureElement);
|
|
115
|
+
const parameters = parseParameters(parametersElement);
|
|
116
|
+
const steps = [...(parseSteps(stepsElement) ?? []), ...(parseAttachments(attachmentsElement) ?? [])];
|
|
117
|
+
await visitor.visitTestResult({
|
|
118
|
+
name,
|
|
119
|
+
fullName,
|
|
120
|
+
description,
|
|
121
|
+
descriptionHtml,
|
|
122
|
+
testId,
|
|
123
|
+
historyId,
|
|
124
|
+
status,
|
|
125
|
+
start,
|
|
126
|
+
stop,
|
|
127
|
+
duration,
|
|
128
|
+
message,
|
|
129
|
+
trace,
|
|
130
|
+
flaky,
|
|
131
|
+
muted,
|
|
132
|
+
known,
|
|
133
|
+
labels,
|
|
134
|
+
links,
|
|
135
|
+
parameters,
|
|
136
|
+
steps,
|
|
137
|
+
}, { readerId });
|
|
138
|
+
};
|
|
139
|
+
const getFullName = (suiteComponent, testCaseComponent) => suiteComponent && testCaseComponent ? `${suiteComponent}.${testCaseComponent}` : undefined;
|
|
140
|
+
const parseDescription = (element) => {
|
|
141
|
+
if (typeof element === "string") {
|
|
142
|
+
return { description: element };
|
|
143
|
+
}
|
|
144
|
+
if (!isStringAnyRecord(element)) {
|
|
145
|
+
return {};
|
|
146
|
+
}
|
|
147
|
+
const { "#text": value, type } = element;
|
|
148
|
+
const safeValue = ensureString(value);
|
|
149
|
+
return ensureString(type)?.toLowerCase() === "html" ? { descriptionHtml: safeValue } : { description: safeValue };
|
|
150
|
+
};
|
|
151
|
+
const combineDescriptions = (suiteDescription, testDescription, sep) => {
|
|
152
|
+
return [suiteDescription, testDescription].filter(Boolean).join(sep) || undefined;
|
|
153
|
+
};
|
|
154
|
+
const parseFailure = (element) => {
|
|
155
|
+
if (!isStringAnyRecord(element)) {
|
|
156
|
+
return {};
|
|
157
|
+
}
|
|
158
|
+
const { message, "stack-trace": trace } = element;
|
|
159
|
+
return { message: ensureString(message), trace: ensureString(trace) };
|
|
160
|
+
};
|
|
161
|
+
const parseTime = (startElement, stopElement) => {
|
|
162
|
+
const start = ensureInt(startElement);
|
|
163
|
+
const stop = ensureInt(stopElement);
|
|
164
|
+
const duration = stop !== undefined && start !== undefined ? Math.max(0, stop - start) : undefined;
|
|
165
|
+
return { start, stop, duration };
|
|
166
|
+
};
|
|
167
|
+
const parseLabels = (labelsElement) => {
|
|
168
|
+
if (!isStringAnyRecord(labelsElement)) {
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
const { label: labelElements } = labelsElement;
|
|
172
|
+
if (!Array.isArray(labelElements)) {
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
return labelElements.filter(isStringAnyRecord).map(convertLabel);
|
|
176
|
+
};
|
|
177
|
+
const convertLabel = (labelElement) => {
|
|
178
|
+
const { name, value } = labelElement;
|
|
179
|
+
return {
|
|
180
|
+
name: ensureString(name),
|
|
181
|
+
value: ensureString(value),
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
const labelExists = (labels, name) => labels.some((l) => l.name === name);
|
|
185
|
+
const findAllLabels = (labels, name) => labels.filter((l) => l.name === name);
|
|
186
|
+
const maybeFindLabelValue = (labels, name) => labels.find((l) => l.name === name)?.value;
|
|
187
|
+
const labelValueExistsIgnoreCase = (labels, value) => labels.some((l) => l.value?.toLowerCase() === value);
|
|
188
|
+
const createLinks = (labels, labelName, linkType) => findAllLabels(labels, labelName).map(({ value }) => ({ name: value, url: value, type: linkType }));
|
|
189
|
+
const resolveTestClass = (testCaseLabels, suiteLabels, suiteName, suiteTitle) => maybeFindLabelValue(testCaseLabels, TEST_CLASS_LABEL_NAME) ??
|
|
190
|
+
maybeFindLabelValue(suiteLabels, TEST_CLASS_LABEL_NAME) ??
|
|
191
|
+
suiteName ??
|
|
192
|
+
suiteTitle;
|
|
193
|
+
const resolveTestMethod = (testCaseLabels, name, title) => maybeFindLabelValue(testCaseLabels, TEST_METHOD_LABEL_NAME) ?? name ?? title;
|
|
194
|
+
const composeTestResultLabels = (allLabels, testClass, testMethod, suite) => {
|
|
195
|
+
const labels = allLabels.filter(({ name: labelName }) => !labelName || !RESERVER_LABEL_NAMES.has(labelName));
|
|
196
|
+
addLabel(labels, TEST_CLASS_LABEL_NAME, testClass);
|
|
197
|
+
addLabel(labels, TEST_METHOD_LABEL_NAME, testMethod);
|
|
198
|
+
addLabelIfNotExists(labels, SUITE_LABEL_NAME, suite);
|
|
199
|
+
return labels;
|
|
200
|
+
};
|
|
201
|
+
const addLabelIfNotExists = (labels, name, value) => {
|
|
202
|
+
if (!labelExists(labels, name)) {
|
|
203
|
+
addLabel(labels, name, value);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
const addLabel = (labels, name, value) => {
|
|
207
|
+
if (value) {
|
|
208
|
+
labels.push({ name, value });
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
const parseSteps = (element) => {
|
|
212
|
+
if (!isStringAnyRecord(element)) {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
const { step: stepElement } = element;
|
|
216
|
+
if (!isStringAnyRecordArray(stepElement)) {
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
return stepElement.map((step) => {
|
|
220
|
+
const { name, title, status, start: startElement, stop: stopElement, steps: stepsElement, attachments: attachmentsElement, } = step;
|
|
221
|
+
const { start, stop, duration } = parseTime(startElement, stopElement);
|
|
222
|
+
const steps = [...(parseSteps(stepsElement) ?? []), ...(parseAttachments(attachmentsElement) ?? [])];
|
|
223
|
+
return {
|
|
224
|
+
name: ensureString(title) ?? ensureString(name) ?? DEFAULT_STEP_NAME,
|
|
225
|
+
status: convertStatus(ensureString(status)),
|
|
226
|
+
start,
|
|
227
|
+
stop,
|
|
228
|
+
duration,
|
|
229
|
+
steps,
|
|
230
|
+
type: "step",
|
|
231
|
+
};
|
|
232
|
+
});
|
|
233
|
+
};
|
|
234
|
+
const parseAttachments = (element) => {
|
|
235
|
+
if (!isStringAnyRecord(element)) {
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
const { attachment: attachmentElement } = element;
|
|
239
|
+
if (!isStringAnyRecordArray(attachmentElement)) {
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
return attachmentElement.map((attachment) => {
|
|
243
|
+
const { title, source, type } = attachment;
|
|
244
|
+
return {
|
|
245
|
+
type: "attachment",
|
|
246
|
+
name: ensureString(title),
|
|
247
|
+
originalFileName: ensureString(source),
|
|
248
|
+
contentType: ensureString(type),
|
|
249
|
+
};
|
|
250
|
+
});
|
|
251
|
+
};
|
|
252
|
+
const parseParameters = (element) => {
|
|
253
|
+
if (!isStringAnyRecord(element)) {
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
const { parameter } = element;
|
|
257
|
+
if (!isStringAnyRecordArray(parameter)) {
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
return parameter
|
|
261
|
+
.filter((p) => {
|
|
262
|
+
const { kind } = p;
|
|
263
|
+
if (!kind) {
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
const kindString = ensureString(kind);
|
|
267
|
+
return kindString?.toLowerCase() === "argument";
|
|
268
|
+
})
|
|
269
|
+
.map((p) => {
|
|
270
|
+
const { name, value } = p;
|
|
271
|
+
return { name: ensureString(name), value: ensureString(value) };
|
|
272
|
+
});
|
|
273
|
+
};
|
|
274
|
+
const convertStatus = (status) => {
|
|
275
|
+
switch (status?.toLowerCase() ?? "unknown") {
|
|
276
|
+
case "failed":
|
|
277
|
+
return "failed";
|
|
278
|
+
case "broken":
|
|
279
|
+
return "broken";
|
|
280
|
+
case "passed":
|
|
281
|
+
return "passed";
|
|
282
|
+
case "skipped":
|
|
283
|
+
case "canceled":
|
|
284
|
+
case "pending":
|
|
285
|
+
return "skipped";
|
|
286
|
+
default:
|
|
287
|
+
return "unknown";
|
|
288
|
+
}
|
|
289
|
+
};
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { notNull } from "@allurereport/core-api";
|
|
2
|
+
import { XMLParser } from "fast-xml-parser";
|
|
3
|
+
import * as console from "node:console";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { parseProperties } from "../properties.js";
|
|
6
|
+
import { ensureBoolean, ensureInt, ensureString } from "../utils.js";
|
|
7
|
+
import { cleanBadXmlCharacters, isStringAnyRecord, isStringAnyRecordArray } from "../xml-utils.js";
|
|
8
|
+
import { ParameterMode } from "./model.js";
|
|
9
|
+
const arrayTags = new Set(["environment.parameter"]);
|
|
10
|
+
const xmlParser = new XMLParser({
|
|
11
|
+
parseTagValue: false,
|
|
12
|
+
ignoreAttributes: false,
|
|
13
|
+
attributeNamePrefix: "",
|
|
14
|
+
removeNSPrefix: true,
|
|
15
|
+
allowBooleanAttributes: true,
|
|
16
|
+
isArray: (tagName, jPath) => arrayTags.has(jPath),
|
|
17
|
+
});
|
|
18
|
+
const readerId = "allure2";
|
|
19
|
+
export const allure2 = {
|
|
20
|
+
read: async (visitor, data) => {
|
|
21
|
+
if (data.getOriginalFileName().match(/-attachment(?:\..+)?/)) {
|
|
22
|
+
await visitor.visitAttachmentFile(data, { readerId });
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
if (data.getOriginalFileName().endsWith("-result.json")) {
|
|
26
|
+
try {
|
|
27
|
+
const parsed = await data.asJson();
|
|
28
|
+
if (parsed && isStringAnyRecord(parsed)) {
|
|
29
|
+
await processTestResult(visitor, parsed, data.getOriginalFileName());
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
console.error("error parsing", data.getOriginalFileName(), e);
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (data.getOriginalFileName().endsWith("-container.json")) {
|
|
39
|
+
const parsed = await data.asJson();
|
|
40
|
+
if (parsed) {
|
|
41
|
+
await processTestResultContainer(visitor, parsed);
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
if (data.getOriginalFileName() === "executor.json") {
|
|
46
|
+
try {
|
|
47
|
+
const parsed = await data.asJson();
|
|
48
|
+
if (parsed && isStringAnyRecord(parsed)) {
|
|
49
|
+
await processExecutor(visitor, parsed);
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
console.error("error parsing", data.getOriginalFileName(), e);
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (data.getOriginalFileName() === "categories.json") {
|
|
59
|
+
try {
|
|
60
|
+
const parsed = await data.asJson();
|
|
61
|
+
if (parsed && isStringAnyRecordArray(parsed)) {
|
|
62
|
+
await processCategories(visitor, parsed);
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
console.error("error parsing", data.getOriginalFileName(), e);
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (data.getOriginalFileName() === "environment.properties") {
|
|
72
|
+
try {
|
|
73
|
+
const raw = await data.asUtf8String();
|
|
74
|
+
if (raw) {
|
|
75
|
+
const parsed = parseProperties(raw);
|
|
76
|
+
if (parsed && isStringAnyRecord(parsed)) {
|
|
77
|
+
await processEnvironment(visitor, parsed);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
console.error("error parsing", data.getOriginalFileName(), e);
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (data.getOriginalFileName() === "environment.xml") {
|
|
88
|
+
try {
|
|
89
|
+
const asBuffer = await data.asBuffer();
|
|
90
|
+
if (!asBuffer) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
const content = cleanBadXmlCharacters(asBuffer).toString("utf-8");
|
|
94
|
+
const parsed = xmlParser.parse(content);
|
|
95
|
+
if (isStringAnyRecord(parsed)) {
|
|
96
|
+
await processEnvironmentXml(visitor, parsed);
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
console.error("error parsing", data.getOriginalFileName(), e);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
},
|
|
107
|
+
readerId: () => readerId,
|
|
108
|
+
};
|
|
109
|
+
const processTestResult = async (visitor, result, originalFileName) => {
|
|
110
|
+
const dest = {
|
|
111
|
+
uuid: ensureString(result.uuid),
|
|
112
|
+
fullName: ensureString(result.fullName),
|
|
113
|
+
name: ensureString(result.name),
|
|
114
|
+
testId: ensureString(result.testCaseId),
|
|
115
|
+
historyId: ensureString(result.historyId),
|
|
116
|
+
start: ensureInt(result.start),
|
|
117
|
+
stop: ensureInt(result.stop),
|
|
118
|
+
description: ensureString(result.description),
|
|
119
|
+
descriptionHtml: ensureString(result.descriptionHtml),
|
|
120
|
+
status: convertStatus(result.status),
|
|
121
|
+
message: ensureString(result?.statusDetails?.message),
|
|
122
|
+
trace: ensureString(result?.statusDetails?.trace),
|
|
123
|
+
actual: ensureString(result?.statusDetails?.actual, ""),
|
|
124
|
+
expected: ensureString(result?.statusDetails?.expected, ""),
|
|
125
|
+
flaky: ensureBoolean(result?.statusDetails?.flaky),
|
|
126
|
+
known: ensureBoolean(result?.statusDetails?.known),
|
|
127
|
+
muted: ensureBoolean(result?.statusDetails?.muted),
|
|
128
|
+
parameters: result?.parameters?.filter(notNull)?.map(convertParameter),
|
|
129
|
+
steps: [
|
|
130
|
+
...(result?.steps?.filter(notNull)?.map(convertStep) ?? []),
|
|
131
|
+
...(result?.attachments?.filter(notNull)?.map(convertAttachment) ?? []),
|
|
132
|
+
],
|
|
133
|
+
links: result?.links?.filter(notNull)?.map(convertLink),
|
|
134
|
+
labels: result.labels?.filter(notNull)?.map(convertLabel),
|
|
135
|
+
};
|
|
136
|
+
await visitor.visitTestResult(dest, { readerId, metadata: { originalFileName } });
|
|
137
|
+
};
|
|
138
|
+
const processExecutor = async (visitor, result) => {
|
|
139
|
+
await visitor.visitMetadata({
|
|
140
|
+
allure2_executor: {
|
|
141
|
+
name: ensureString(result.name),
|
|
142
|
+
type: ensureString(result.type),
|
|
143
|
+
url: ensureString(result.url),
|
|
144
|
+
buildOrder: ensureInt(result.buildOrder),
|
|
145
|
+
buildName: ensureString(result.buildName),
|
|
146
|
+
buildUrl: ensureString(result.buildUrl),
|
|
147
|
+
reportName: ensureString(result.reportName),
|
|
148
|
+
reportUrl: ensureString(result.reportUrl),
|
|
149
|
+
},
|
|
150
|
+
}, { readerId });
|
|
151
|
+
};
|
|
152
|
+
const processCategories = async (visitor, result) => {
|
|
153
|
+
const data = result.map((value) => ({
|
|
154
|
+
name: ensureString(value.name),
|
|
155
|
+
description: ensureString(value.description),
|
|
156
|
+
descriptionHtml: ensureString(value.descriptionHtml),
|
|
157
|
+
messageRegex: ensureString(value.messageRegex),
|
|
158
|
+
traceRegex: ensureString(value.traceRegex),
|
|
159
|
+
matchedStatuses: Array.isArray(value.matchedStatuses)
|
|
160
|
+
? value.matchedStatuses.map((v) => ensureString(v)).filter(notNull)
|
|
161
|
+
: [],
|
|
162
|
+
flaky: ensureBoolean(value.flaky),
|
|
163
|
+
}));
|
|
164
|
+
await visitor.visitMetadata({
|
|
165
|
+
allure2_categories: data,
|
|
166
|
+
}, { readerId });
|
|
167
|
+
};
|
|
168
|
+
const processEnvironmentXml = async (visitor, result) => {
|
|
169
|
+
const { environment } = result;
|
|
170
|
+
if (!isStringAnyRecord(environment)) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const { parameter: parameters } = environment;
|
|
174
|
+
if (!isStringAnyRecordArray(parameters)) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const data = [];
|
|
178
|
+
parameters.forEach((param) => {
|
|
179
|
+
const { key, value } = param;
|
|
180
|
+
const stringKey = ensureString(key);
|
|
181
|
+
const stringValue = ensureString(value);
|
|
182
|
+
if (stringKey && stringValue) {
|
|
183
|
+
data.push({ name: stringKey, values: [stringValue] });
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
await visitor.visitMetadata({
|
|
187
|
+
allure_environment: data,
|
|
188
|
+
}, {
|
|
189
|
+
readerId,
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
const processEnvironment = async (visitor, result) => {
|
|
193
|
+
const data = Object.keys(result).map((key) => {
|
|
194
|
+
const rawValue = result[key];
|
|
195
|
+
const value = typeof rawValue === "string" ? rawValue : JSON.stringify(rawValue);
|
|
196
|
+
return {
|
|
197
|
+
name: key,
|
|
198
|
+
values: [value],
|
|
199
|
+
};
|
|
200
|
+
});
|
|
201
|
+
await visitor.visitMetadata({
|
|
202
|
+
allure_environment: data,
|
|
203
|
+
}, {
|
|
204
|
+
readerId,
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
const processFixtures = async (visitor, fixtures, type, children) => {
|
|
208
|
+
if (fixtures) {
|
|
209
|
+
for (const fixture of fixtures) {
|
|
210
|
+
const dist = convertFixture(type, children, fixture);
|
|
211
|
+
await visitor.visitTestFixtureResult(dist, { readerId });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
const processTestResultContainer = async (visitor, result) => {
|
|
216
|
+
if (result.children && result.children.length > 0) {
|
|
217
|
+
await processFixtures(visitor, result.befores, "before", result.children);
|
|
218
|
+
await processFixtures(visitor, result.afters, "after", result.children);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
const convertStatus = (status) => {
|
|
222
|
+
switch ((status ?? "unknown").toLowerCase()) {
|
|
223
|
+
case "failed":
|
|
224
|
+
return "failed";
|
|
225
|
+
case "broken":
|
|
226
|
+
return "broken";
|
|
227
|
+
case "passed":
|
|
228
|
+
return "passed";
|
|
229
|
+
case "skipped":
|
|
230
|
+
return "skipped";
|
|
231
|
+
default:
|
|
232
|
+
return "unknown";
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
const convertFixture = (type, children, fixture) => {
|
|
236
|
+
return {
|
|
237
|
+
uuid: randomUUID(),
|
|
238
|
+
testResults: children,
|
|
239
|
+
type,
|
|
240
|
+
name: fixture.name,
|
|
241
|
+
start: ensureInt(fixture.start),
|
|
242
|
+
stop: ensureInt(fixture.stop),
|
|
243
|
+
status: convertStatus(fixture.status),
|
|
244
|
+
message: ensureString(fixture?.statusDetails?.message),
|
|
245
|
+
trace: ensureString(fixture?.statusDetails?.trace),
|
|
246
|
+
steps: [
|
|
247
|
+
...(fixture?.steps?.filter(notNull)?.map(convertStep) ?? []),
|
|
248
|
+
...(fixture?.attachments?.filter(notNull)?.map(convertAttachment) ?? []),
|
|
249
|
+
],
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
const convertStep = (step) => {
|
|
253
|
+
const steps = step?.steps ?? [];
|
|
254
|
+
const attachments = step?.attachments ?? [];
|
|
255
|
+
if (steps.length === 0 && attachments.length === 1 && step.name === attachments[0].name) {
|
|
256
|
+
return {
|
|
257
|
+
...convertAttachment(attachments[0]),
|
|
258
|
+
start: step.start,
|
|
259
|
+
stop: step.stop,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
name: ensureString(step.name),
|
|
264
|
+
parameters: step?.parameters?.filter(notNull)?.map(convertParameter),
|
|
265
|
+
status: convertStatus(step.status),
|
|
266
|
+
message: ensureString(step?.statusDetails?.message),
|
|
267
|
+
trace: ensureString(step?.statusDetails?.trace),
|
|
268
|
+
steps: [
|
|
269
|
+
...(steps.filter(notNull)?.map(convertStep) ?? []),
|
|
270
|
+
...(attachments.filter(notNull)?.map(convertAttachment) ?? []),
|
|
271
|
+
],
|
|
272
|
+
start: ensureInt(step.start),
|
|
273
|
+
stop: ensureInt(step.stop),
|
|
274
|
+
type: "step",
|
|
275
|
+
};
|
|
276
|
+
};
|
|
277
|
+
const convertParameter = ({ mode, name, value, excluded }) => {
|
|
278
|
+
return {
|
|
279
|
+
name: ensureString(name),
|
|
280
|
+
value: ensureString(value),
|
|
281
|
+
excluded: ensureBoolean(excluded),
|
|
282
|
+
hidden: mode === ParameterMode.HIDDEN,
|
|
283
|
+
masked: mode === ParameterMode.MASKED,
|
|
284
|
+
};
|
|
285
|
+
};
|
|
286
|
+
const convertAttachment = ({ name, type, source }) => {
|
|
287
|
+
return {
|
|
288
|
+
name: ensureString(name),
|
|
289
|
+
contentType: ensureString(type),
|
|
290
|
+
originalFileName: ensureString(source),
|
|
291
|
+
type: "attachment",
|
|
292
|
+
};
|
|
293
|
+
};
|
|
294
|
+
const convertLabel = ({ name, value }) => {
|
|
295
|
+
return {
|
|
296
|
+
name: ensureString(name),
|
|
297
|
+
value: ensureString(value),
|
|
298
|
+
};
|
|
299
|
+
};
|
|
300
|
+
const convertLink = ({ name, url, type }) => {
|
|
301
|
+
return {
|
|
302
|
+
name: ensureString(name),
|
|
303
|
+
url: ensureString(url),
|
|
304
|
+
type: ensureString(type),
|
|
305
|
+
};
|
|
306
|
+
};
|