@allurereport/reader 3.0.0-beta.3 → 3.0.0-beta.4
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 +8 -3
- package/dist/allure2/index.js +7 -8
- package/dist/attachments/index.d.ts +1 -1
- package/dist/attachments/index.js +1 -1
- package/dist/cucumberjson/index.d.ts +2 -0
- package/dist/cucumberjson/index.js +285 -0
- package/dist/cucumberjson/model.d.ts +60 -0
- package/dist/cucumberjson/model.js +2 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/junitxml/index.js +7 -3
- package/dist/properties.js +0 -63
- package/dist/utils.d.ts +13 -0
- package/dist/utils.js +27 -0
- package/dist/xml-utils.d.ts +0 -3
- package/dist/xml-utils.js +2 -19
- package/package.json +6 -4
package/dist/allure1/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { XMLParser } from "fast-xml-parser";
|
|
2
|
-
import
|
|
2
|
+
import * as console from "node:console";
|
|
3
|
+
import { ensureInt, ensureString } from "../utils.js";
|
|
4
|
+
import { cleanBadXmlCharacters, isStringAnyRecord, isStringAnyRecordArray } from "../xml-utils.js";
|
|
3
5
|
const arrayTags = new Set([
|
|
4
6
|
"test-suite.test-cases.test-case",
|
|
5
7
|
"test-suite.test-cases.test-case.steps.step",
|
|
@@ -16,7 +18,7 @@ const xmlParser = new XMLParser({
|
|
|
16
18
|
});
|
|
17
19
|
const readerId = "allure1";
|
|
18
20
|
export const allure1 = {
|
|
19
|
-
async
|
|
21
|
+
read: async (visitor, data) => {
|
|
20
22
|
if (data.getOriginalFileName().endsWith("-testsuite.xml")) {
|
|
21
23
|
try {
|
|
22
24
|
const asBuffer = await data.asBuffer();
|
|
@@ -25,6 +27,9 @@ export const allure1 = {
|
|
|
25
27
|
}
|
|
26
28
|
const content = cleanBadXmlCharacters(asBuffer).toString("utf-8");
|
|
27
29
|
const parsed = xmlParser.parse(content);
|
|
30
|
+
if (!isStringAnyRecord(parsed)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
28
33
|
return await parseRootElement(visitor, parsed);
|
|
29
34
|
}
|
|
30
35
|
catch (e) {
|
|
@@ -93,7 +98,7 @@ const parseSteps = (element) => {
|
|
|
93
98
|
const { start, stop, duration } = parseTime(startElement, stopElement);
|
|
94
99
|
const steps = parseSteps(stepsElement);
|
|
95
100
|
return {
|
|
96
|
-
name: ensureString(title
|
|
101
|
+
name: ensureString(title) ?? ensureString(name),
|
|
97
102
|
status: convertStatus(ensureString(status)),
|
|
98
103
|
start,
|
|
99
104
|
stop,
|
package/dist/allure2/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { notNull } from "@allurereport/core-api";
|
|
2
2
|
import { XMLParser } from "fast-xml-parser";
|
|
3
|
+
import * as console from "node:console";
|
|
3
4
|
import { randomUUID } from "node:crypto";
|
|
4
5
|
import { parseProperties } from "../properties.js";
|
|
5
|
-
import {
|
|
6
|
+
import { ensureBoolean, ensureInt, ensureString } from "../utils.js";
|
|
7
|
+
import { cleanBadXmlCharacters, isStringAnyRecord, isStringAnyRecordArray } from "../xml-utils.js";
|
|
6
8
|
import { ParameterMode } from "./model.js";
|
|
7
9
|
const arrayTags = new Set(["environment.parameter"]);
|
|
8
10
|
const xmlParser = new XMLParser({
|
|
@@ -15,7 +17,7 @@ const xmlParser = new XMLParser({
|
|
|
15
17
|
});
|
|
16
18
|
const readerId = "allure2";
|
|
17
19
|
export const allure2 = {
|
|
18
|
-
async
|
|
20
|
+
read: async (visitor, data) => {
|
|
19
21
|
if (data.getOriginalFileName().match(/.*-attachment(\..+)?/)) {
|
|
20
22
|
await visitor.visitAttachmentFile(data, { readerId });
|
|
21
23
|
return true;
|
|
@@ -153,10 +155,7 @@ const processCategories = async (visitor, result) => {
|
|
|
153
155
|
messageRegex: ensureString(value.messageRegex),
|
|
154
156
|
traceRegex: ensureString(value.traceRegex),
|
|
155
157
|
matchedStatuses: Array.isArray(value.matchedStatuses)
|
|
156
|
-
? value.matchedStatuses
|
|
157
|
-
.map((v) => ensureString(v))
|
|
158
|
-
.filter(notNull)
|
|
159
|
-
.map((v) => v)
|
|
158
|
+
? value.matchedStatuses.map((v) => ensureString(v)).filter(notNull)
|
|
160
159
|
: [],
|
|
161
160
|
flaky: ensureBoolean(value.flaky),
|
|
162
161
|
}));
|
|
@@ -203,14 +202,14 @@ const processEnvironment = async (visitor, result) => {
|
|
|
203
202
|
readerId,
|
|
204
203
|
});
|
|
205
204
|
};
|
|
206
|
-
async
|
|
205
|
+
const processFixtures = async (visitor, fixtures, type, children) => {
|
|
207
206
|
if (fixtures) {
|
|
208
207
|
for (const fixture of fixtures) {
|
|
209
208
|
const dist = convertFixture(type, children, fixture);
|
|
210
209
|
await visitor.visitTestFixtureResult(dist, { readerId });
|
|
211
210
|
}
|
|
212
211
|
}
|
|
213
|
-
}
|
|
212
|
+
};
|
|
214
213
|
const processTestResultContainer = async (visitor, result) => {
|
|
215
214
|
if (result.children && result.children.length > 0) {
|
|
216
215
|
await processFixtures(visitor, result.befores, "before", result.children);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { ResultsReader } from "@allurereport/reader-api";
|
|
1
|
+
import type { ResultsReader } from "@allurereport/reader-api";
|
|
2
2
|
export declare const attachments: ResultsReader;
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { BufferResultFile } from "@allurereport/reader-api";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { ensureArray, ensureInt, ensureString, isArray, isNonNullObject, isString } from "../utils.js";
|
|
4
|
+
import { STEP_NAME_PLACEHOLDER, TEST_NAME_PLACEHOLDER } from "./model.js";
|
|
5
|
+
const NS_IN_MS = 1000000;
|
|
6
|
+
const readerId = "cucumberjson";
|
|
7
|
+
const allureStepStatusPriorityOrder = {
|
|
8
|
+
failed: 0,
|
|
9
|
+
broken: 1,
|
|
10
|
+
unknown: 2,
|
|
11
|
+
skipped: 3,
|
|
12
|
+
passed: 4,
|
|
13
|
+
};
|
|
14
|
+
const cucumberStatusToAllureStatus = {
|
|
15
|
+
unknown: "unknown",
|
|
16
|
+
passed: "passed",
|
|
17
|
+
skipped: "skipped",
|
|
18
|
+
pending: "skipped",
|
|
19
|
+
["undefined"]: "broken",
|
|
20
|
+
ambiguous: "broken",
|
|
21
|
+
failed: "failed",
|
|
22
|
+
};
|
|
23
|
+
const allureStepMessages = {
|
|
24
|
+
unknown: "The result of the step is unknown",
|
|
25
|
+
passed: "The step passed",
|
|
26
|
+
skipped: "The step was skipped because the previous step hadn't passed",
|
|
27
|
+
pending: "The step signalled pending during execution",
|
|
28
|
+
["undefined"]: "The step didn't match any definition",
|
|
29
|
+
ambiguous: "The step matched more than one definition",
|
|
30
|
+
failed: "The step failed",
|
|
31
|
+
};
|
|
32
|
+
export const cucumberjson = {
|
|
33
|
+
read: async (visitor, data) => {
|
|
34
|
+
const originalFileName = data.getOriginalFileName();
|
|
35
|
+
try {
|
|
36
|
+
const parsed = await data.asJson();
|
|
37
|
+
if (parsed) {
|
|
38
|
+
let oneOrMoreFeaturesParsed = false;
|
|
39
|
+
for (const feature of parsed) {
|
|
40
|
+
oneOrMoreFeaturesParsed || (oneOrMoreFeaturesParsed = await processFeature(visitor, originalFileName, feature));
|
|
41
|
+
}
|
|
42
|
+
return oneOrMoreFeaturesParsed;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
console.error("error parsing", originalFileName, e);
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
},
|
|
51
|
+
readerId: () => readerId,
|
|
52
|
+
};
|
|
53
|
+
const processFeature = async (visitor, originalFileName, feature) => {
|
|
54
|
+
if (isCucumberFeature(feature)) {
|
|
55
|
+
const preProcessedFeature = preProcessFeature(feature);
|
|
56
|
+
for (const scenario of feature.elements) {
|
|
57
|
+
await processScenario(visitor, originalFileName, preProcessedFeature, scenario);
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
};
|
|
63
|
+
const processScenario = async (visitor, originalFileName, feature, scenario) => {
|
|
64
|
+
const preProcessedScenario = preProcessScenario(scenario);
|
|
65
|
+
if (shouldProcessScenario(preProcessedScenario)) {
|
|
66
|
+
const preProcessedSteps = await preProcessSteps(visitor, scenario.steps ?? []);
|
|
67
|
+
await visitor.visitTestResult(mapCucumberScenarioToAllureTestResult(feature, preProcessedScenario, preProcessedSteps), {
|
|
68
|
+
readerId,
|
|
69
|
+
metadata: { originalFileName },
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const shouldProcessScenario = ({ type }) => type !== "background";
|
|
74
|
+
const preProcessSteps = async (visitor, steps) => {
|
|
75
|
+
const preProcessedSteps = [];
|
|
76
|
+
for (const step of steps) {
|
|
77
|
+
preProcessedSteps.push(await preProcessOneStep(visitor, step));
|
|
78
|
+
}
|
|
79
|
+
return preProcessedSteps;
|
|
80
|
+
};
|
|
81
|
+
const preProcessOneStep = async (visitor, step) => {
|
|
82
|
+
const { keyword, name, result } = step;
|
|
83
|
+
const { status, duration, error_message: errorMessage } = result ?? {};
|
|
84
|
+
return {
|
|
85
|
+
name: ensureString(name)?.trim(),
|
|
86
|
+
keyword: ensureString(keyword)?.trim(),
|
|
87
|
+
status: status ?? "unknown",
|
|
88
|
+
duration: ensureInt(duration),
|
|
89
|
+
errorMessage,
|
|
90
|
+
attachments: await processStepAttachments(visitor, step),
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
const processStepAttachments = async (visitor, step) => [
|
|
94
|
+
await processStepDocStringAttachment(visitor, step),
|
|
95
|
+
await processStepDataTableAttachment(visitor, step),
|
|
96
|
+
...(await processStepEmbeddingAttachments(visitor, step)),
|
|
97
|
+
].filter((s) => typeof s !== "undefined");
|
|
98
|
+
const processStepDocStringAttachment = async (visitor, { doc_string: docString }) => {
|
|
99
|
+
if (docString) {
|
|
100
|
+
const { value, content_type: contentType } = docString;
|
|
101
|
+
if (value && value.trim()) {
|
|
102
|
+
return await visitBufferAttachment(visitor, "Description", Buffer.from(value), contentType || "text/markdown");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const processStepDataTableAttachment = async (visitor, { rows }) => {
|
|
107
|
+
if (isArray(rows)) {
|
|
108
|
+
const content = formatDataTable(rows);
|
|
109
|
+
return await visitBufferAttachment(visitor, "Data", Buffer.from(content), "text/csv");
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
const processStepEmbeddingAttachments = async (visitor, { embeddings }) => {
|
|
113
|
+
const attachments = [];
|
|
114
|
+
const checkedEmbeddings = ensureArray(embeddings) ?? [];
|
|
115
|
+
const getName = checkedEmbeddings.length > 1 ? (i) => `Embedding ${i}` : () => "Embedding";
|
|
116
|
+
const embeddingsWithNames = checkedEmbeddings.map((e, i) => [e, getName(i + 1)]);
|
|
117
|
+
for (const [embedding, fallbackName] of embeddingsWithNames) {
|
|
118
|
+
if (isNonNullObject(embedding)) {
|
|
119
|
+
attachments.push(await visitBufferAttachment(visitor, ensureString(embedding.name, fallbackName), Buffer.from(ensureString(embedding.data, ""), "base64"), ensureString(embedding.mime_type, "application/octet-stream")));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return attachments;
|
|
123
|
+
};
|
|
124
|
+
const visitBufferAttachment = async (visitor, name, content, contentType) => {
|
|
125
|
+
const fileName = randomUUID();
|
|
126
|
+
await visitor.visitAttachmentFile(new BufferResultFile(content, fileName), { readerId });
|
|
127
|
+
return {
|
|
128
|
+
type: "attachment",
|
|
129
|
+
contentType,
|
|
130
|
+
originalFileName: fileName,
|
|
131
|
+
name,
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
const formatDataTable = (rows) => {
|
|
135
|
+
return rows
|
|
136
|
+
.filter((isNonNullObject))
|
|
137
|
+
.map(formatDataTableRow)
|
|
138
|
+
.filter(isString)
|
|
139
|
+
.join("\r\n");
|
|
140
|
+
};
|
|
141
|
+
const formatDataTableRow = ({ cells }) => {
|
|
142
|
+
const checkedCells = ensureArray(cells);
|
|
143
|
+
return checkedCells ? checkedCells.map(formatDataTableCell).join(",") : undefined;
|
|
144
|
+
};
|
|
145
|
+
const formatDataTableCell = (cell) => {
|
|
146
|
+
const escapedCell = ensureString(cell, "").replaceAll(String.raw `"`, String.raw `""`);
|
|
147
|
+
return `"${escapedCell}"`;
|
|
148
|
+
};
|
|
149
|
+
const isCucumberFeature = ({ keyword, elements }) => typeof keyword === "string" && keyword.toLowerCase() === "feature" && Array.isArray(elements);
|
|
150
|
+
const pairWithAllureSteps = (preProcessedCucumberSteps) => preProcessedCucumberSteps.map((c) => {
|
|
151
|
+
return {
|
|
152
|
+
preProcessedStep: c,
|
|
153
|
+
allureStep: createAllureStepResult(c),
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
const mapCucumberScenarioToAllureTestResult = (preProcessedFeature, scenario, preProcessedSteps) => {
|
|
157
|
+
const postProcessedSteps = pairWithAllureSteps(preProcessedSteps);
|
|
158
|
+
return {
|
|
159
|
+
fullName: calculateFullName(preProcessedFeature, scenario),
|
|
160
|
+
name: scenario.name ?? TEST_NAME_PLACEHOLDER,
|
|
161
|
+
description: scenario.description,
|
|
162
|
+
duration: convertDuration(calculateTestDuration(postProcessedSteps)),
|
|
163
|
+
steps: postProcessedSteps.map(({ allureStep }) => allureStep),
|
|
164
|
+
labels: calculateTestLabels(preProcessedFeature, scenario),
|
|
165
|
+
...resolveTestResultStatusProps(postProcessedSteps),
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
const calculateTestLabels = ({ name: featureName, tags: featureTags }, { tags: scenarioTags }) => {
|
|
169
|
+
const labels = [];
|
|
170
|
+
if (featureName) {
|
|
171
|
+
labels.push({ name: "feature", value: featureName });
|
|
172
|
+
}
|
|
173
|
+
labels.push(...featureTags.map((value) => ({ name: "tag", value })), ...scenarioTags.map((value) => ({ name: "tag", value })));
|
|
174
|
+
return labels;
|
|
175
|
+
};
|
|
176
|
+
const preProcessFeature = (feature) => {
|
|
177
|
+
return {
|
|
178
|
+
id: ensureString(feature.id),
|
|
179
|
+
name: ensureString(feature.name),
|
|
180
|
+
uri: ensureString(feature.uri),
|
|
181
|
+
tags: parseTags(feature.tags),
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
const parseTags = (tags) => {
|
|
185
|
+
return (ensureArray(tags) ?? [])
|
|
186
|
+
.filter((isNonNullObject))
|
|
187
|
+
.map(({ name }) => name)
|
|
188
|
+
.filter(isString);
|
|
189
|
+
};
|
|
190
|
+
const preProcessScenario = (scenario) => {
|
|
191
|
+
return {
|
|
192
|
+
id: ensureString(scenario.id),
|
|
193
|
+
name: ensureString(scenario.name),
|
|
194
|
+
description: ensureString(scenario.description),
|
|
195
|
+
tags: parseTags(scenario.tags),
|
|
196
|
+
type: scenario.type,
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
const calculateFullName = ({ uri: featureUri, name: featureName, id: featureId }, { name: scenarioName, id: scenarioId }) => {
|
|
200
|
+
if (!scenarioName && !scenarioId) {
|
|
201
|
+
return randomUUID();
|
|
202
|
+
}
|
|
203
|
+
const featurePart = featureUri || featureName || featureId;
|
|
204
|
+
if (featurePart) {
|
|
205
|
+
const scenarioPart = scenarioName || scenarioId;
|
|
206
|
+
return `${featurePart}#${scenarioPart}`;
|
|
207
|
+
}
|
|
208
|
+
return scenarioId || scenarioName;
|
|
209
|
+
};
|
|
210
|
+
const calculateTestDuration = (cucumberAllureStepData) => cucumberAllureStepData.reduce((testDuration, { preProcessedStep: { duration } }) => typeof testDuration === "undefined" ? duration : testDuration + (duration ?? 0), undefined);
|
|
211
|
+
const resolveTestResultStatusProps = (cucumberAllureSteps) => {
|
|
212
|
+
const stepsData = getCucumberAllureStepWithMaxPriorityStatus(cucumberAllureSteps);
|
|
213
|
+
return stepsData
|
|
214
|
+
? resolveResultOfTestFromStepsData(stepsData)
|
|
215
|
+
: {
|
|
216
|
+
status: "unknown",
|
|
217
|
+
message: "Step results are missing",
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
const resolveResultOfTestFromStepsData = ({ preProcessedStep: { status: cucumberStatus, errorMessage }, allureStep: { name, status }, }) => ({
|
|
221
|
+
status: status ?? "unknown",
|
|
222
|
+
...resolveTestMessageAndTrace(name, cucumberStatus, errorMessage),
|
|
223
|
+
});
|
|
224
|
+
const resolveTestMessageAndTrace = (allureStepName, status, errorMessage) => status !== "passed"
|
|
225
|
+
? {
|
|
226
|
+
message: resolveTestMessage(status, allureStepName),
|
|
227
|
+
trace: errorMessage,
|
|
228
|
+
}
|
|
229
|
+
: {};
|
|
230
|
+
const resolveTestMessage = (cucumberStepStatus, allureStepName) => {
|
|
231
|
+
switch (cucumberStepStatus) {
|
|
232
|
+
case "failed":
|
|
233
|
+
return `The step '${allureStepName}' failed`;
|
|
234
|
+
case "skipped":
|
|
235
|
+
return "One or more steps of the scenario were skipped";
|
|
236
|
+
case "pending":
|
|
237
|
+
return `The step '${allureStepName}' signalled pending during execution`;
|
|
238
|
+
case "undefined":
|
|
239
|
+
return `The step '${allureStepName}' didn't match any definition`;
|
|
240
|
+
case "ambiguous":
|
|
241
|
+
return `The step '${allureStepName}' matched more than one definition`;
|
|
242
|
+
case "unknown":
|
|
243
|
+
default:
|
|
244
|
+
return `The result of the step '${allureStepName}' is unknown`;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
const getCucumberAllureStepWithMaxPriorityStatus = (cucumberAllureSteps) => {
|
|
248
|
+
switch (cucumberAllureSteps.length) {
|
|
249
|
+
case 0:
|
|
250
|
+
return undefined;
|
|
251
|
+
case 1:
|
|
252
|
+
return cucumberAllureSteps[0];
|
|
253
|
+
default:
|
|
254
|
+
return cucumberAllureSteps.reduce(statusPriorityReducingFn);
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
const statusPriorityReducingFn = (testDefiningStep, currentStep) => allureStepStatusPriorityOrder[testDefiningStep.allureStep.status] <=
|
|
258
|
+
allureStepStatusPriorityOrder[currentStep.allureStep.status]
|
|
259
|
+
? testDefiningStep
|
|
260
|
+
: currentStep;
|
|
261
|
+
const createAllureStepResult = ({ keyword, name, status, duration, errorMessage, attachments, }) => ({
|
|
262
|
+
type: "step",
|
|
263
|
+
name: getAllureStepName(keyword, name),
|
|
264
|
+
steps: attachments,
|
|
265
|
+
...mapCucumberStepResultToStepProps(status, duration, errorMessage),
|
|
266
|
+
});
|
|
267
|
+
const getAllureStepName = (keyword, name) => {
|
|
268
|
+
if (!name) {
|
|
269
|
+
return keyword ? `${keyword} <${STEP_NAME_PLACEHOLDER.toLowerCase()}>` : STEP_NAME_PLACEHOLDER;
|
|
270
|
+
}
|
|
271
|
+
return keyword ? `${keyword} ${name}` : name;
|
|
272
|
+
};
|
|
273
|
+
const mapCucumberStepResultToStepProps = (status, duration, errorMessage) => ({
|
|
274
|
+
status: cucumberStatusToAllureStatus[status ?? "unknown"] ?? "unknown",
|
|
275
|
+
duration: convertDuration(duration),
|
|
276
|
+
...resolveStepMessageAndTrace(status, errorMessage),
|
|
277
|
+
});
|
|
278
|
+
const resolveStepMessageAndTrace = (status, errorMessage) => status !== "passed" || errorMessage
|
|
279
|
+
? {
|
|
280
|
+
message: allureStepMessages[status ?? "unknown"] ?? allureStepMessages.unknown,
|
|
281
|
+
trace: errorMessage,
|
|
282
|
+
}
|
|
283
|
+
: {};
|
|
284
|
+
const convertDuration = (duration) => typeof duration !== "undefined" ? nsToMs(duration) : undefined;
|
|
285
|
+
const nsToMs = (ns) => Math.round(ns / NS_IN_MS);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export declare const TEST_NAME_PLACEHOLDER = "The scenario's name is not defined";
|
|
2
|
+
export declare const STEP_NAME_PLACEHOLDER = "The step's name is not defined";
|
|
3
|
+
export type CucumberFeature = {
|
|
4
|
+
description: string;
|
|
5
|
+
elements: CucumberFeatureElement[];
|
|
6
|
+
id: string;
|
|
7
|
+
keyword: string;
|
|
8
|
+
line: number;
|
|
9
|
+
name: string;
|
|
10
|
+
tags?: unknown;
|
|
11
|
+
uri: string;
|
|
12
|
+
};
|
|
13
|
+
export type CucumberFeatureElement = {
|
|
14
|
+
after?: CucumberStep[];
|
|
15
|
+
before?: CucumberStep[];
|
|
16
|
+
description: string;
|
|
17
|
+
id?: string;
|
|
18
|
+
keyword: string;
|
|
19
|
+
line: number;
|
|
20
|
+
name: string;
|
|
21
|
+
steps?: CucumberStep[];
|
|
22
|
+
tags?: unknown;
|
|
23
|
+
type: string;
|
|
24
|
+
};
|
|
25
|
+
export type CucumberStep = {
|
|
26
|
+
doc_string?: CucumberDocString;
|
|
27
|
+
embeddings?: unknown;
|
|
28
|
+
keyword?: string;
|
|
29
|
+
line?: number;
|
|
30
|
+
match?: CucumberStepMatch;
|
|
31
|
+
name?: string;
|
|
32
|
+
output?: string[];
|
|
33
|
+
result: CucumberStepResult;
|
|
34
|
+
rows?: unknown;
|
|
35
|
+
};
|
|
36
|
+
export type CucumberDocString = {
|
|
37
|
+
content_type?: string;
|
|
38
|
+
line?: number;
|
|
39
|
+
value?: string;
|
|
40
|
+
};
|
|
41
|
+
export type CucumberDatatableRow = {
|
|
42
|
+
cells: unknown;
|
|
43
|
+
};
|
|
44
|
+
export type CucumberStepResult = {
|
|
45
|
+
duration?: number;
|
|
46
|
+
error_message?: string;
|
|
47
|
+
status: string;
|
|
48
|
+
};
|
|
49
|
+
export type CucumberStepMatch = {
|
|
50
|
+
location: string;
|
|
51
|
+
};
|
|
52
|
+
export type CucumberTag = {
|
|
53
|
+
line: number;
|
|
54
|
+
name: unknown;
|
|
55
|
+
};
|
|
56
|
+
export type CucumberEmbedding = {
|
|
57
|
+
data: unknown;
|
|
58
|
+
mime_type: unknown;
|
|
59
|
+
name?: unknown;
|
|
60
|
+
};
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/junitxml/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { XMLParser } from "fast-xml-parser";
|
|
2
2
|
import * as console from "node:console";
|
|
3
|
-
import { ensureString
|
|
3
|
+
import { ensureString } from "../utils.js";
|
|
4
|
+
import { isEmptyElement, isStringAnyRecord, isStringAnyRecordArray } from "../xml-utils.js";
|
|
4
5
|
const arrayTags = new Set(["testsuite.testcase", "testsuites.testsuite", "testsuites.testsuite.testcase"]);
|
|
5
6
|
const xmlParser = new XMLParser({
|
|
6
7
|
parseTagValue: false,
|
|
@@ -12,7 +13,7 @@ const xmlParser = new XMLParser({
|
|
|
12
13
|
});
|
|
13
14
|
const readerId = "junit";
|
|
14
15
|
export const junitXml = {
|
|
15
|
-
async
|
|
16
|
+
read: async (visitor, data) => {
|
|
16
17
|
if (data.getOriginalFileName().endsWith(".xml")) {
|
|
17
18
|
try {
|
|
18
19
|
const content = await data.asUtf8String();
|
|
@@ -20,6 +21,9 @@ export const junitXml = {
|
|
|
20
21
|
return false;
|
|
21
22
|
}
|
|
22
23
|
const parsed = xmlParser.parse(content);
|
|
24
|
+
if (!isStringAnyRecord(parsed)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
23
27
|
return await parseRootElement(visitor, parsed);
|
|
24
28
|
}
|
|
25
29
|
catch (e) {
|
|
@@ -72,7 +76,7 @@ const parseTestSuite = async (visitor, testSuite) => {
|
|
|
72
76
|
}
|
|
73
77
|
};
|
|
74
78
|
const parseTestCase = async (visitor, suite, testCase) => {
|
|
75
|
-
const { name,
|
|
79
|
+
const { name, failure, skipped } = testCase;
|
|
76
80
|
const { status, message, trace } = getStatus(failure, skipped);
|
|
77
81
|
await visitor.visitTestResult({
|
|
78
82
|
name: ensureString(name),
|
package/dist/properties.js
CHANGED
|
@@ -202,63 +202,6 @@ const hex = (char) => {
|
|
|
202
202
|
}
|
|
203
203
|
throw new Error(`Non-hex char ${char}`);
|
|
204
204
|
};
|
|
205
|
-
const escapeKey = (key) => {
|
|
206
|
-
return mapcatChars(key, (char, code) => {
|
|
207
|
-
if (isSeparator(char)) {
|
|
208
|
-
return `\\${char}`;
|
|
209
|
-
}
|
|
210
|
-
return escapeCharacter(char, code, true);
|
|
211
|
-
});
|
|
212
|
-
};
|
|
213
|
-
const escapeValue = (value) => {
|
|
214
|
-
let escapeWhitespace = true;
|
|
215
|
-
return mapcatChars(value, (char, code) => {
|
|
216
|
-
if (!isWhitespace(char)) {
|
|
217
|
-
escapeWhitespace = false;
|
|
218
|
-
}
|
|
219
|
-
return escapeCharacter(char, code, escapeWhitespace);
|
|
220
|
-
});
|
|
221
|
-
};
|
|
222
|
-
const mapcatChars = (value, fn) => {
|
|
223
|
-
let result = "";
|
|
224
|
-
for (let i = 0; i < value.length; i++) {
|
|
225
|
-
const char = value[i];
|
|
226
|
-
const code = value.charCodeAt(i);
|
|
227
|
-
result += fn(char, code);
|
|
228
|
-
}
|
|
229
|
-
return result;
|
|
230
|
-
};
|
|
231
|
-
const escapeCharacter = (char, code, escapeWhitespace) => {
|
|
232
|
-
if (isAsciiPrintable(code)) {
|
|
233
|
-
if (char === " " && escapeWhitespace) {
|
|
234
|
-
return "\\ ";
|
|
235
|
-
}
|
|
236
|
-
else if (char === "\\") {
|
|
237
|
-
return "\\\\";
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
return char;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
else if (char === "\t") {
|
|
244
|
-
return "\\t";
|
|
245
|
-
}
|
|
246
|
-
else if (char === "\n") {
|
|
247
|
-
return "\\n";
|
|
248
|
-
}
|
|
249
|
-
else if (char === "\f") {
|
|
250
|
-
return "\\f";
|
|
251
|
-
}
|
|
252
|
-
else if (char === "\r") {
|
|
253
|
-
return "\\r";
|
|
254
|
-
}
|
|
255
|
-
else if (code < 160 || code >= 256) {
|
|
256
|
-
return escapeUnicode(code);
|
|
257
|
-
}
|
|
258
|
-
else {
|
|
259
|
-
return char;
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
205
|
const isWhitespace = (char) => {
|
|
263
206
|
switch (char) {
|
|
264
207
|
case "\t":
|
|
@@ -268,12 +211,6 @@ const isWhitespace = (char) => {
|
|
|
268
211
|
}
|
|
269
212
|
return false;
|
|
270
213
|
};
|
|
271
|
-
const isAsciiPrintable = (code) => code > 31 && code < 127;
|
|
272
|
-
const escapeUnicode = (code) => {
|
|
273
|
-
const unicode = code.toString(16);
|
|
274
|
-
const prefix = "0".repeat(4 - unicode.length);
|
|
275
|
-
return `\\u${prefix}${unicode}`;
|
|
276
|
-
};
|
|
277
214
|
const isSeparator = (char) => {
|
|
278
215
|
switch (char) {
|
|
279
216
|
case "=":
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const isBoolean: (value: unknown) => value is boolean;
|
|
2
|
+
export declare const isString: (value: unknown) => value is string;
|
|
3
|
+
export declare const isArray: <T = unknown>(value: unknown) => value is T[];
|
|
4
|
+
export declare const isNonNullObject: <T extends object = object>(value: unknown) => value is T;
|
|
5
|
+
export declare function ensureBoolean(value: unknown): boolean | undefined;
|
|
6
|
+
export declare function ensureBoolean(value: unknown, fallback: boolean): boolean;
|
|
7
|
+
export declare const ensureInt: (obj: unknown) => number | undefined;
|
|
8
|
+
export declare function ensureString(value: unknown): string | undefined;
|
|
9
|
+
export declare function ensureString(value: unknown, fallback: string): string;
|
|
10
|
+
export declare function ensureArray<T = unknown>(value: unknown): T[] | undefined;
|
|
11
|
+
export declare function ensureArray<T = unknown>(value: unknown, fallback: T[]): T[];
|
|
12
|
+
export declare function ensureObject<T extends object = object>(value: unknown): T | undefined;
|
|
13
|
+
export declare function ensureObject<T extends object = object>(value: unknown, fallback: T): T;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const isBoolean = (value) => typeof value === "boolean";
|
|
2
|
+
export const isString = (value) => typeof value === "string";
|
|
3
|
+
export const isArray = (value) => Array.isArray(value);
|
|
4
|
+
export const isNonNullObject = (value) => typeof value === "object" && value !== null;
|
|
5
|
+
export function ensureBoolean(value, fallback) {
|
|
6
|
+
return isBoolean(value) ? value : fallback;
|
|
7
|
+
}
|
|
8
|
+
export const ensureInt = (obj) => {
|
|
9
|
+
if (typeof obj === "number") {
|
|
10
|
+
return obj;
|
|
11
|
+
}
|
|
12
|
+
const stringValue = ensureString(obj);
|
|
13
|
+
if (!stringValue) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
const parsed = parseInt(stringValue, 10);
|
|
17
|
+
return isNaN(parsed) ? undefined : parsed;
|
|
18
|
+
};
|
|
19
|
+
export function ensureString(value, fallback) {
|
|
20
|
+
return isString(value) ? value : fallback;
|
|
21
|
+
}
|
|
22
|
+
export function ensureArray(value, fallback) {
|
|
23
|
+
return isArray(value) ? value : fallback;
|
|
24
|
+
}
|
|
25
|
+
export function ensureObject(value, fallback) {
|
|
26
|
+
return isNonNullObject(value) ? value : fallback;
|
|
27
|
+
}
|
package/dist/xml-utils.d.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
export declare const isEmptyElement: (obj: unknown) => obj is "";
|
|
2
|
-
export declare const ensureBoolean: (obj: unknown, fallback?: boolean) => boolean | undefined;
|
|
3
|
-
export declare const ensureString: (obj: unknown, fallback?: string) => string | undefined;
|
|
4
|
-
export declare const ensureInt: (obj: unknown) => number | undefined;
|
|
5
2
|
export declare const isStringAnyRecord: (obj: unknown) => obj is Record<string, any>;
|
|
6
3
|
export declare const isStringAnyRecordArray: (obj: unknown) => obj is Record<string, any>[];
|
|
7
4
|
export declare const isBadXmlCharacter: (c: number) => boolean;
|
package/dist/xml-utils.js
CHANGED
|
@@ -1,23 +1,6 @@
|
|
|
1
1
|
export const isEmptyElement = (obj) => {
|
|
2
2
|
return obj === "";
|
|
3
3
|
};
|
|
4
|
-
export const ensureBoolean = (obj, fallback) => {
|
|
5
|
-
return typeof obj === "boolean" ? obj : fallback;
|
|
6
|
-
};
|
|
7
|
-
export const ensureString = (obj, fallback) => {
|
|
8
|
-
return typeof obj === "string" ? obj : fallback;
|
|
9
|
-
};
|
|
10
|
-
export const ensureInt = (obj) => {
|
|
11
|
-
if (typeof obj === "number") {
|
|
12
|
-
return obj;
|
|
13
|
-
}
|
|
14
|
-
const stringValue = ensureString(obj);
|
|
15
|
-
if (!stringValue) {
|
|
16
|
-
return undefined;
|
|
17
|
-
}
|
|
18
|
-
const parsed = parseInt(stringValue);
|
|
19
|
-
return isNaN(parsed) ? undefined : parsed;
|
|
20
|
-
};
|
|
21
4
|
export const isStringAnyRecord = (obj) => {
|
|
22
5
|
if (typeof obj !== "object") {
|
|
23
6
|
return false;
|
|
@@ -31,9 +14,9 @@ export const isStringAnyRecordArray = (obj) => {
|
|
|
31
14
|
return Array.isArray(obj) && obj.every((item) => isStringAnyRecord(item));
|
|
32
15
|
};
|
|
33
16
|
export const isBadXmlCharacter = (c) => {
|
|
34
|
-
let cDataCharacter = c < 32 && c
|
|
17
|
+
let cDataCharacter = c < 32 && c !== 9 && c !== 13 && c !== 10;
|
|
35
18
|
cDataCharacter || (cDataCharacter = c >= 55296 && c < 57344);
|
|
36
|
-
cDataCharacter || (cDataCharacter = c
|
|
19
|
+
cDataCharacter || (cDataCharacter = c === 65534 || c === 65535);
|
|
37
20
|
return cDataCharacter;
|
|
38
21
|
};
|
|
39
22
|
export const cleanBadXmlCharacters = (input) => {
|
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.4",
|
|
4
4
|
"description": "Collection of utilities which helps to process different kind of test results as Allure Results",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"allure",
|
|
@@ -21,12 +21,14 @@
|
|
|
21
21
|
"scripts": {
|
|
22
22
|
"build": "run clean && tsc --project ./tsconfig.json",
|
|
23
23
|
"clean": "rimraf ./dist",
|
|
24
|
+
"eslint": "eslint ./src/**/*.{js,jsx,ts,tsx}",
|
|
25
|
+
"eslint:format": "eslint --fix ./src/**/*.{js,jsx,ts,tsx}",
|
|
24
26
|
"test": "vitest run"
|
|
25
27
|
},
|
|
26
28
|
"dependencies": {
|
|
27
|
-
"@allurereport/core-api": "3.0.0-beta.
|
|
28
|
-
"@allurereport/plugin-api": "3.0.0-beta.
|
|
29
|
-
"@allurereport/reader-api": "3.0.0-beta.
|
|
29
|
+
"@allurereport/core-api": "3.0.0-beta.4",
|
|
30
|
+
"@allurereport/plugin-api": "3.0.0-beta.4",
|
|
31
|
+
"@allurereport/reader-api": "3.0.0-beta.4",
|
|
30
32
|
"fast-xml-parser": "^4.5.0"
|
|
31
33
|
},
|
|
32
34
|
"devDependencies": {
|