@allurereport/reader 3.0.0-beta.5 → 3.0.0-beta.6
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/dist/allure1/index.js +156 -15
- package/package.json +4 -4
package/dist/allure1/index.js
CHANGED
|
@@ -2,19 +2,35 @@ import { XMLParser } from "fast-xml-parser";
|
|
|
2
2
|
import * as console from "node:console";
|
|
3
3
|
import { ensureInt, ensureString } from "../utils.js";
|
|
4
4
|
import { cleanBadXmlCharacters, isStringAnyRecord, isStringAnyRecordArray } from "../xml-utils.js";
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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,
|
|
10
25
|
]);
|
|
26
|
+
const arrayTags = new Set(["attachment", "label", "parameter", "step", "test-case"]);
|
|
11
27
|
const xmlParser = new XMLParser({
|
|
12
28
|
parseTagValue: false,
|
|
13
29
|
ignoreAttributes: false,
|
|
14
30
|
attributeNamePrefix: "",
|
|
15
31
|
removeNSPrefix: true,
|
|
16
32
|
allowBooleanAttributes: true,
|
|
17
|
-
isArray:
|
|
33
|
+
isArray: arrayTags.has.bind(arrayTags),
|
|
18
34
|
});
|
|
19
35
|
const readerId = "allure1";
|
|
20
36
|
export const allure1 = {
|
|
@@ -49,7 +65,7 @@ const parseRootElement = async (visitor, xml) => {
|
|
|
49
65
|
return await parseTestSuite(visitor, testSuite);
|
|
50
66
|
};
|
|
51
67
|
const parseTestSuite = async (visitor, testSuite) => {
|
|
52
|
-
const { "name": testSuiteName, "test-cases": testCases } = testSuite;
|
|
68
|
+
const { "name": testSuiteName, "title": testSuiteTitle, "description": descriptionElement, "test-cases": testCases, "labels": labelsElement, } = testSuite;
|
|
53
69
|
if (!isStringAnyRecord(testCases)) {
|
|
54
70
|
return false;
|
|
55
71
|
}
|
|
@@ -57,20 +73,83 @@ const parseTestSuite = async (visitor, testSuite) => {
|
|
|
57
73
|
if (!isStringAnyRecordArray(testCase)) {
|
|
58
74
|
return false;
|
|
59
75
|
}
|
|
76
|
+
const labels = parseLabels(labelsElement);
|
|
60
77
|
for (const tc of testCase) {
|
|
61
|
-
await parseTestCase(visitor, {
|
|
78
|
+
await parseTestCase(visitor, {
|
|
79
|
+
name: ensureString(testSuiteName),
|
|
80
|
+
title: ensureString(testSuiteTitle),
|
|
81
|
+
...parseDescription(descriptionElement),
|
|
82
|
+
labels,
|
|
83
|
+
}, tc);
|
|
62
84
|
}
|
|
63
85
|
return true;
|
|
64
86
|
};
|
|
65
87
|
const parseTestCase = async (visitor, testSuite, testCase) => {
|
|
66
|
-
const { name:
|
|
67
|
-
const name =
|
|
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;
|
|
68
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>");
|
|
69
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);
|
|
70
114
|
const { message, trace } = parseFailure(failureElement);
|
|
71
115
|
const parameters = parseParameters(parametersElement);
|
|
72
|
-
const steps = parseSteps(stepsElement);
|
|
73
|
-
await visitor.visitTestResult({
|
|
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;
|
|
74
153
|
};
|
|
75
154
|
const parseFailure = (element) => {
|
|
76
155
|
if (!isStringAnyRecord(element)) {
|
|
@@ -85,6 +164,50 @@ const parseTime = (startElement, stopElement) => {
|
|
|
85
164
|
const duration = stop !== undefined && start !== undefined ? Math.max(0, stop - start) : undefined;
|
|
86
165
|
return { start, stop, duration };
|
|
87
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
|
+
};
|
|
88
211
|
const parseSteps = (element) => {
|
|
89
212
|
if (!isStringAnyRecord(element)) {
|
|
90
213
|
return undefined;
|
|
@@ -94,11 +217,11 @@ const parseSteps = (element) => {
|
|
|
94
217
|
return undefined;
|
|
95
218
|
}
|
|
96
219
|
return stepElement.map((step) => {
|
|
97
|
-
const { name, title, status, start: startElement, stop: stopElement, steps: stepsElement } = step;
|
|
220
|
+
const { name, title, status, start: startElement, stop: stopElement, steps: stepsElement, attachments: attachmentsElement, } = step;
|
|
98
221
|
const { start, stop, duration } = parseTime(startElement, stopElement);
|
|
99
|
-
const steps = parseSteps(stepsElement);
|
|
222
|
+
const steps = [...(parseSteps(stepsElement) ?? []), ...(parseAttachments(attachmentsElement) ?? [])];
|
|
100
223
|
return {
|
|
101
|
-
name: ensureString(title) ?? ensureString(name),
|
|
224
|
+
name: ensureString(title) ?? ensureString(name) ?? DEFAULT_STEP_NAME,
|
|
102
225
|
status: convertStatus(ensureString(status)),
|
|
103
226
|
start,
|
|
104
227
|
stop,
|
|
@@ -108,6 +231,24 @@ const parseSteps = (element) => {
|
|
|
108
231
|
};
|
|
109
232
|
});
|
|
110
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
|
+
};
|
|
111
252
|
const parseParameters = (element) => {
|
|
112
253
|
if (!isStringAnyRecord(element)) {
|
|
113
254
|
return undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@allurereport/reader",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.6",
|
|
4
4
|
"description": "Collection of utilities which helps to process different kind of test results as Allure Results",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"allure",
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
"test": "vitest run"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@allurereport/core-api": "3.0.0-beta.
|
|
30
|
-
"@allurereport/plugin-api": "3.0.0-beta.
|
|
31
|
-
"@allurereport/reader-api": "3.0.0-beta.
|
|
29
|
+
"@allurereport/core-api": "3.0.0-beta.6",
|
|
30
|
+
"@allurereport/plugin-api": "3.0.0-beta.6",
|
|
31
|
+
"@allurereport/reader-api": "3.0.0-beta.6",
|
|
32
32
|
"fast-xml-parser": "^4.5.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|