@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
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export interface Attachment {
|
|
2
|
+
name?: string;
|
|
3
|
+
type?: string;
|
|
4
|
+
source?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare enum Status {
|
|
7
|
+
FAILED = "failed",
|
|
8
|
+
BROKEN = "broken",
|
|
9
|
+
PASSED = "passed",
|
|
10
|
+
SKIPPED = "skipped"
|
|
11
|
+
}
|
|
12
|
+
export declare enum Stage {
|
|
13
|
+
SCHEDULED = "scheduled",
|
|
14
|
+
RUNNING = "running",
|
|
15
|
+
FINISHED = "finished",
|
|
16
|
+
PENDING = "pending",
|
|
17
|
+
INTERRUPTED = "interrupted"
|
|
18
|
+
}
|
|
19
|
+
export declare enum ParameterMode {
|
|
20
|
+
HIDDEN = "hidden",
|
|
21
|
+
MASKED = "masked",
|
|
22
|
+
DEFAULT = "default"
|
|
23
|
+
}
|
|
24
|
+
export interface Label {
|
|
25
|
+
name?: string;
|
|
26
|
+
value?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface Link {
|
|
29
|
+
name?: string;
|
|
30
|
+
url?: string;
|
|
31
|
+
type?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface Parameter {
|
|
34
|
+
name?: string;
|
|
35
|
+
value?: string;
|
|
36
|
+
excluded?: boolean;
|
|
37
|
+
mode?: ParameterMode;
|
|
38
|
+
}
|
|
39
|
+
export interface StatusDetails {
|
|
40
|
+
message?: string;
|
|
41
|
+
trace?: string;
|
|
42
|
+
known?: boolean;
|
|
43
|
+
muted?: boolean;
|
|
44
|
+
flaky?: boolean;
|
|
45
|
+
actual?: string;
|
|
46
|
+
expected?: string;
|
|
47
|
+
}
|
|
48
|
+
interface ExecutableItem {
|
|
49
|
+
name?: string;
|
|
50
|
+
status?: Status;
|
|
51
|
+
statusDetails?: StatusDetails;
|
|
52
|
+
stage?: Stage;
|
|
53
|
+
description?: string;
|
|
54
|
+
descriptionHtml?: string;
|
|
55
|
+
steps?: StepResult[];
|
|
56
|
+
attachments?: Attachment[];
|
|
57
|
+
parameters?: Parameter[];
|
|
58
|
+
start?: number;
|
|
59
|
+
stop?: number;
|
|
60
|
+
}
|
|
61
|
+
export type FixtureResult = ExecutableItem;
|
|
62
|
+
export type StepResult = ExecutableItem;
|
|
63
|
+
export interface TestResult extends ExecutableItem {
|
|
64
|
+
uuid?: string;
|
|
65
|
+
historyId?: string;
|
|
66
|
+
fullName?: string;
|
|
67
|
+
testCaseId?: string;
|
|
68
|
+
labels?: Label[];
|
|
69
|
+
links?: Link[];
|
|
70
|
+
}
|
|
71
|
+
export interface TestResultContainer {
|
|
72
|
+
uuid?: string;
|
|
73
|
+
name?: string;
|
|
74
|
+
children?: string[];
|
|
75
|
+
befores?: FixtureResult[];
|
|
76
|
+
afters?: FixtureResult[];
|
|
77
|
+
}
|
|
78
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export var Status;
|
|
2
|
+
(function (Status) {
|
|
3
|
+
Status["FAILED"] = "failed";
|
|
4
|
+
Status["BROKEN"] = "broken";
|
|
5
|
+
Status["PASSED"] = "passed";
|
|
6
|
+
Status["SKIPPED"] = "skipped";
|
|
7
|
+
})(Status || (Status = {}));
|
|
8
|
+
export var Stage;
|
|
9
|
+
(function (Stage) {
|
|
10
|
+
Stage["SCHEDULED"] = "scheduled";
|
|
11
|
+
Stage["RUNNING"] = "running";
|
|
12
|
+
Stage["FINISHED"] = "finished";
|
|
13
|
+
Stage["PENDING"] = "pending";
|
|
14
|
+
Stage["INTERRUPTED"] = "interrupted";
|
|
15
|
+
})(Stage || (Stage = {}));
|
|
16
|
+
export var ParameterMode;
|
|
17
|
+
(function (ParameterMode) {
|
|
18
|
+
ParameterMode["HIDDEN"] = "hidden";
|
|
19
|
+
ParameterMode["MASKED"] = "masked";
|
|
20
|
+
ParameterMode["DEFAULT"] = "default";
|
|
21
|
+
})(ParameterMode || (ParameterMode = {}));
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { BufferResultFile } from "@allurereport/reader-api";
|
|
2
|
+
import * as console from "node:console";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { ensureArray, ensureInt, ensureString, isArray, isNonNullObject, isString } from "../utils.js";
|
|
5
|
+
import { STEP_NAME_PLACEHOLDER, TEST_NAME_PLACEHOLDER } from "./model.js";
|
|
6
|
+
const NS_IN_MS = 1000000;
|
|
7
|
+
const readerId = "cucumberjson";
|
|
8
|
+
const allureStepStatusPriorityOrder = {
|
|
9
|
+
failed: 0,
|
|
10
|
+
broken: 1,
|
|
11
|
+
unknown: 2,
|
|
12
|
+
skipped: 3,
|
|
13
|
+
passed: 4,
|
|
14
|
+
};
|
|
15
|
+
const cucumberStatusToAllureStatus = {
|
|
16
|
+
unknown: "unknown",
|
|
17
|
+
passed: "passed",
|
|
18
|
+
skipped: "skipped",
|
|
19
|
+
pending: "skipped",
|
|
20
|
+
["undefined"]: "broken",
|
|
21
|
+
ambiguous: "broken",
|
|
22
|
+
failed: "failed",
|
|
23
|
+
};
|
|
24
|
+
const allureStepMessages = {
|
|
25
|
+
unknown: "The result of the step is unknown",
|
|
26
|
+
passed: "The step passed",
|
|
27
|
+
skipped: "The step was skipped because the previous step hadn't passed",
|
|
28
|
+
pending: "The step signalled pending during execution",
|
|
29
|
+
["undefined"]: "The step didn't match any definition",
|
|
30
|
+
ambiguous: "The step matched more than one definition",
|
|
31
|
+
failed: "The step failed",
|
|
32
|
+
};
|
|
33
|
+
export const cucumberjson = {
|
|
34
|
+
read: async (visitor, data) => {
|
|
35
|
+
const originalFileName = data.getOriginalFileName();
|
|
36
|
+
if (originalFileName.endsWith(".json")) {
|
|
37
|
+
try {
|
|
38
|
+
const parsed = await data.asJson();
|
|
39
|
+
if (parsed) {
|
|
40
|
+
let oneOrMoreFeaturesParsed = false;
|
|
41
|
+
for (const feature of parsed) {
|
|
42
|
+
oneOrMoreFeaturesParsed || (oneOrMoreFeaturesParsed = await processFeature(visitor, originalFileName, feature));
|
|
43
|
+
}
|
|
44
|
+
return oneOrMoreFeaturesParsed;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
console.error("error parsing", originalFileName, e);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
},
|
|
54
|
+
readerId: () => readerId,
|
|
55
|
+
};
|
|
56
|
+
const processFeature = async (visitor, originalFileName, feature) => {
|
|
57
|
+
if (isCucumberFeature(feature)) {
|
|
58
|
+
const preProcessedFeature = preProcessFeature(feature);
|
|
59
|
+
for (const scenario of feature.elements) {
|
|
60
|
+
await processScenario(visitor, originalFileName, preProcessedFeature, scenario);
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
};
|
|
66
|
+
const processScenario = async (visitor, originalFileName, feature, scenario) => {
|
|
67
|
+
const preProcessedScenario = preProcessScenario(scenario);
|
|
68
|
+
if (shouldProcessScenario(preProcessedScenario)) {
|
|
69
|
+
const preProcessedSteps = await preProcessSteps(visitor, scenario.steps ?? []);
|
|
70
|
+
await visitor.visitTestResult(mapCucumberScenarioToAllureTestResult(feature, preProcessedScenario, preProcessedSteps), {
|
|
71
|
+
readerId,
|
|
72
|
+
metadata: { originalFileName },
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const shouldProcessScenario = ({ type }) => type !== "background";
|
|
77
|
+
const preProcessSteps = async (visitor, steps) => {
|
|
78
|
+
const preProcessedSteps = [];
|
|
79
|
+
for (const step of steps) {
|
|
80
|
+
preProcessedSteps.push(await preProcessOneStep(visitor, step));
|
|
81
|
+
}
|
|
82
|
+
return preProcessedSteps;
|
|
83
|
+
};
|
|
84
|
+
const preProcessOneStep = async (visitor, step) => {
|
|
85
|
+
const { keyword, name, result } = step;
|
|
86
|
+
const { status, duration, error_message: errorMessage } = result ?? {};
|
|
87
|
+
return {
|
|
88
|
+
name: ensureString(name)?.trim(),
|
|
89
|
+
keyword: ensureString(keyword)?.trim(),
|
|
90
|
+
status: status ?? "unknown",
|
|
91
|
+
duration: ensureInt(duration),
|
|
92
|
+
errorMessage,
|
|
93
|
+
attachments: await processStepAttachments(visitor, step),
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
const processStepAttachments = async (visitor, step) => [
|
|
97
|
+
await processStepDocStringAttachment(visitor, step.doc_string),
|
|
98
|
+
await processStepDataTableAttachment(visitor, step.rows),
|
|
99
|
+
...(await processCucumberJsStepArguments(visitor, step.arguments)),
|
|
100
|
+
...(await processStepEmbeddingAttachments(visitor, step)),
|
|
101
|
+
].filter((s) => typeof s !== "undefined");
|
|
102
|
+
const processStepDocStringAttachment = async (visitor, docString) => {
|
|
103
|
+
if (docString) {
|
|
104
|
+
const { value, content, content_type: contentType } = docString;
|
|
105
|
+
const resolvedValue = ensureString(value ?? content);
|
|
106
|
+
if (resolvedValue && resolvedValue.trim()) {
|
|
107
|
+
return await visitBufferAttachment(visitor, "Description", Buffer.from(resolvedValue), ensureString(contentType) || "text/markdown");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const processStepDataTableAttachment = async (visitor, rows) => {
|
|
112
|
+
if (isArray(rows)) {
|
|
113
|
+
const content = formatDataTable(rows);
|
|
114
|
+
return await visitBufferAttachment(visitor, "Data", Buffer.from(content), "text/csv");
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const processCucumberJsStepArguments = async (visitor, stepArguments) => {
|
|
118
|
+
const attachments = [];
|
|
119
|
+
if (isArray(stepArguments)) {
|
|
120
|
+
for (const stepArgument of stepArguments) {
|
|
121
|
+
if (isNonNullObject(stepArgument)) {
|
|
122
|
+
if ("content" in stepArgument) {
|
|
123
|
+
attachments.push(await processStepDocStringAttachment(visitor, stepArgument));
|
|
124
|
+
}
|
|
125
|
+
else if ("rows" in stepArgument) {
|
|
126
|
+
attachments.push(await processStepDataTableAttachment(visitor, stepArgument.rows));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return attachments;
|
|
132
|
+
};
|
|
133
|
+
const processStepEmbeddingAttachments = async (visitor, { embeddings }) => {
|
|
134
|
+
const attachments = [];
|
|
135
|
+
const checkedEmbeddings = ensureArray(embeddings) ?? [];
|
|
136
|
+
const getName = checkedEmbeddings.length > 1 ? (i) => `Embedding ${i}` : () => "Embedding";
|
|
137
|
+
const embeddingsWithNames = checkedEmbeddings.map((e, i) => [e, getName(i + 1)]);
|
|
138
|
+
for (const [embedding, fallbackName] of embeddingsWithNames) {
|
|
139
|
+
if (isNonNullObject(embedding)) {
|
|
140
|
+
attachments.push(await visitBufferAttachment(visitor, ensureString(embedding.name, fallbackName), Buffer.from(ensureString(embedding.data, ""), "base64"), ensureString(embedding.mime_type, "application/octet-stream")));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return attachments;
|
|
144
|
+
};
|
|
145
|
+
const visitBufferAttachment = async (visitor, name, content, contentType) => {
|
|
146
|
+
const fileName = randomUUID();
|
|
147
|
+
await visitor.visitAttachmentFile(new BufferResultFile(content, fileName), { readerId });
|
|
148
|
+
return {
|
|
149
|
+
type: "attachment",
|
|
150
|
+
contentType,
|
|
151
|
+
originalFileName: fileName,
|
|
152
|
+
name,
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
const formatDataTable = (rows) => {
|
|
156
|
+
return rows
|
|
157
|
+
.filter((isNonNullObject))
|
|
158
|
+
.map(formatDataTableRow)
|
|
159
|
+
.filter(isString)
|
|
160
|
+
.join("\r\n");
|
|
161
|
+
};
|
|
162
|
+
const formatDataTableRow = ({ cells }) => {
|
|
163
|
+
const checkedCells = ensureArray(cells);
|
|
164
|
+
return checkedCells ? checkedCells.map(formatDataTableCell).join(",") : undefined;
|
|
165
|
+
};
|
|
166
|
+
const formatDataTableCell = (cell) => {
|
|
167
|
+
const escapedCell = ensureString(cell, "").replaceAll(String.raw `"`, String.raw `""`);
|
|
168
|
+
return `"${escapedCell}"`;
|
|
169
|
+
};
|
|
170
|
+
const isCucumberFeature = ({ keyword, elements }) => typeof keyword === "string" && keyword.toLowerCase() === "feature" && Array.isArray(elements);
|
|
171
|
+
const pairWithAllureSteps = (preProcessedCucumberSteps) => preProcessedCucumberSteps.map((c) => {
|
|
172
|
+
return {
|
|
173
|
+
preProcessedStep: c,
|
|
174
|
+
allureStep: createAllureStepResult(c),
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
const mapCucumberScenarioToAllureTestResult = (preProcessedFeature, scenario, preProcessedSteps) => {
|
|
178
|
+
const postProcessedSteps = pairWithAllureSteps(preProcessedSteps);
|
|
179
|
+
return {
|
|
180
|
+
fullName: calculateFullName(preProcessedFeature, scenario),
|
|
181
|
+
name: scenario.name ?? TEST_NAME_PLACEHOLDER,
|
|
182
|
+
description: scenario.description,
|
|
183
|
+
duration: convertDuration(calculateTestDuration(postProcessedSteps)),
|
|
184
|
+
steps: postProcessedSteps.map(({ allureStep }) => allureStep),
|
|
185
|
+
labels: calculateTestLabels(preProcessedFeature, scenario),
|
|
186
|
+
...resolveTestResultStatusProps(postProcessedSteps),
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
const calculateTestLabels = ({ name: featureName, tags: featureTags }, { tags: scenarioTags }) => {
|
|
190
|
+
const labels = [];
|
|
191
|
+
if (featureName) {
|
|
192
|
+
labels.push({ name: "feature", value: featureName });
|
|
193
|
+
}
|
|
194
|
+
labels.push(...featureTags.map((value) => ({ name: "tag", value })), ...scenarioTags.map((value) => ({ name: "tag", value })));
|
|
195
|
+
return labels;
|
|
196
|
+
};
|
|
197
|
+
const preProcessFeature = (feature) => {
|
|
198
|
+
return {
|
|
199
|
+
id: ensureString(feature.id),
|
|
200
|
+
name: ensureString(feature.name),
|
|
201
|
+
uri: ensureString(feature.uri),
|
|
202
|
+
tags: parseTags(feature.tags),
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
const parseTags = (tags) => {
|
|
206
|
+
return (ensureArray(tags) ?? [])
|
|
207
|
+
.filter((isNonNullObject))
|
|
208
|
+
.map(({ name }) => name)
|
|
209
|
+
.filter(isString);
|
|
210
|
+
};
|
|
211
|
+
const preProcessScenario = (scenario) => {
|
|
212
|
+
return {
|
|
213
|
+
id: ensureString(scenario.id),
|
|
214
|
+
name: ensureString(scenario.name),
|
|
215
|
+
description: ensureString(scenario.description),
|
|
216
|
+
tags: parseTags(scenario.tags),
|
|
217
|
+
type: scenario.type,
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
const calculateFullName = ({ uri: featureUri, name: featureName, id: featureId }, { name: scenarioName, id: scenarioId }) => {
|
|
221
|
+
if (!scenarioName && !scenarioId) {
|
|
222
|
+
return randomUUID();
|
|
223
|
+
}
|
|
224
|
+
const featurePart = featureUri || featureName || featureId;
|
|
225
|
+
if (featurePart) {
|
|
226
|
+
const scenarioPart = scenarioName || scenarioId;
|
|
227
|
+
return `${featurePart}#${scenarioPart}`;
|
|
228
|
+
}
|
|
229
|
+
return scenarioId || scenarioName;
|
|
230
|
+
};
|
|
231
|
+
const calculateTestDuration = (cucumberAllureStepData) => cucumberAllureStepData.reduce((testDuration, { preProcessedStep: { duration } }) => typeof testDuration === "undefined" ? duration : testDuration + (duration ?? 0), undefined);
|
|
232
|
+
const resolveTestResultStatusProps = (cucumberAllureSteps) => {
|
|
233
|
+
const stepsData = getCucumberAllureStepWithMaxPriorityStatus(cucumberAllureSteps);
|
|
234
|
+
return stepsData
|
|
235
|
+
? resolveResultOfTestFromStepsData(stepsData)
|
|
236
|
+
: {
|
|
237
|
+
status: "unknown",
|
|
238
|
+
message: "Step results are missing",
|
|
239
|
+
};
|
|
240
|
+
};
|
|
241
|
+
const resolveResultOfTestFromStepsData = ({ preProcessedStep: { status: cucumberStatus, errorMessage }, allureStep: { name, status }, }) => ({
|
|
242
|
+
status: status ?? "unknown",
|
|
243
|
+
...resolveTestMessageAndTrace(name, cucumberStatus, errorMessage),
|
|
244
|
+
});
|
|
245
|
+
const resolveTestMessageAndTrace = (allureStepName, status, errorMessage) => status !== "passed"
|
|
246
|
+
? {
|
|
247
|
+
message: resolveTestMessage(status, allureStepName),
|
|
248
|
+
trace: errorMessage,
|
|
249
|
+
}
|
|
250
|
+
: {};
|
|
251
|
+
const resolveTestMessage = (cucumberStepStatus, allureStepName) => {
|
|
252
|
+
switch (cucumberStepStatus) {
|
|
253
|
+
case "failed":
|
|
254
|
+
return `The step '${allureStepName}' failed`;
|
|
255
|
+
case "skipped":
|
|
256
|
+
return "One or more steps of the scenario were skipped";
|
|
257
|
+
case "pending":
|
|
258
|
+
return `The step '${allureStepName}' signalled pending during execution`;
|
|
259
|
+
case "undefined":
|
|
260
|
+
return `The step '${allureStepName}' didn't match any definition`;
|
|
261
|
+
case "ambiguous":
|
|
262
|
+
return `The step '${allureStepName}' matched more than one definition`;
|
|
263
|
+
case "unknown":
|
|
264
|
+
default:
|
|
265
|
+
return `The result of the step '${allureStepName}' is unknown`;
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
const getCucumberAllureStepWithMaxPriorityStatus = (cucumberAllureSteps) => {
|
|
269
|
+
switch (cucumberAllureSteps.length) {
|
|
270
|
+
case 0:
|
|
271
|
+
return undefined;
|
|
272
|
+
case 1:
|
|
273
|
+
return cucumberAllureSteps[0];
|
|
274
|
+
default:
|
|
275
|
+
return cucumberAllureSteps.reduce(statusPriorityReducingFn);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
const statusPriorityReducingFn = (testDefiningStep, currentStep) => allureStepStatusPriorityOrder[testDefiningStep.allureStep.status] <=
|
|
279
|
+
allureStepStatusPriorityOrder[currentStep.allureStep.status]
|
|
280
|
+
? testDefiningStep
|
|
281
|
+
: currentStep;
|
|
282
|
+
const createAllureStepResult = ({ keyword, name, status, duration, errorMessage, attachments, }) => ({
|
|
283
|
+
type: "step",
|
|
284
|
+
name: getAllureStepName(keyword, name),
|
|
285
|
+
steps: attachments,
|
|
286
|
+
...mapCucumberStepResultToStepProps(status, duration, errorMessage),
|
|
287
|
+
});
|
|
288
|
+
const getAllureStepName = (keyword, name) => {
|
|
289
|
+
if (!name) {
|
|
290
|
+
return keyword ? `${keyword} <${STEP_NAME_PLACEHOLDER.toLowerCase()}>` : STEP_NAME_PLACEHOLDER;
|
|
291
|
+
}
|
|
292
|
+
return keyword ? `${keyword} ${name}` : name;
|
|
293
|
+
};
|
|
294
|
+
const mapCucumberStepResultToStepProps = (status, duration, errorMessage) => ({
|
|
295
|
+
status: cucumberStatusToAllureStatus[status ?? "unknown"] ?? "unknown",
|
|
296
|
+
duration: convertDuration(duration),
|
|
297
|
+
...resolveStepMessageAndTrace(status, errorMessage),
|
|
298
|
+
});
|
|
299
|
+
const resolveStepMessageAndTrace = (status, errorMessage) => status !== "passed" || errorMessage
|
|
300
|
+
? {
|
|
301
|
+
message: allureStepMessages[status ?? "unknown"] ?? allureStepMessages.unknown,
|
|
302
|
+
trace: errorMessage,
|
|
303
|
+
}
|
|
304
|
+
: {};
|
|
305
|
+
const convertDuration = (duration) => typeof duration !== "undefined" ? nsToMs(duration) : undefined;
|
|
306
|
+
const nsToMs = (ns) => Math.round(ns / NS_IN_MS);
|
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
arguments?: unknown;
|
|
36
|
+
};
|
|
37
|
+
export type CucumberDocString = {
|
|
38
|
+
content_type?: string;
|
|
39
|
+
line?: number;
|
|
40
|
+
value?: string;
|
|
41
|
+
content?: string;
|
|
42
|
+
};
|
|
43
|
+
export type CucumberDatatableRow = {
|
|
44
|
+
cells: unknown;
|
|
45
|
+
};
|
|
46
|
+
export type CucumberStepResult = {
|
|
47
|
+
duration?: number;
|
|
48
|
+
error_message?: string;
|
|
49
|
+
status: string;
|
|
50
|
+
};
|
|
51
|
+
export type CucumberStepMatch = {
|
|
52
|
+
location: string;
|
|
53
|
+
};
|
|
54
|
+
export type CucumberTag = {
|
|
55
|
+
line: number;
|
|
56
|
+
name: unknown;
|
|
57
|
+
};
|
|
58
|
+
export type CucumberEmbedding = {
|
|
59
|
+
data: unknown;
|
|
60
|
+
mime_type: unknown;
|
|
61
|
+
name?: unknown;
|
|
62
|
+
};
|
|
63
|
+
export type CucumberJsStepArgument = CucumberDocString | {
|
|
64
|
+
rows: CucumberDatatableRow[];
|
|
65
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from "./allure1/index.js";
|
|
2
|
+
export * from "./allure2/index.js";
|
|
3
|
+
export * from "./cucumberjson/index.js";
|
|
4
|
+
export * from "./junitxml/index.js";
|
|
5
|
+
export * from "./xcresult/index.js";
|
|
6
|
+
export * from "./attachments/index.js";
|
|
7
|
+
export type * from "./model.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { BufferResultFile } from "@allurereport/reader-api";
|
|
2
|
+
import { XMLParser } from "fast-xml-parser";
|
|
3
|
+
import * as console from "node:console";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { ensureString } from "../utils.js";
|
|
6
|
+
import { isEmptyElement, isStringAnyRecord, isStringAnyRecordArray } from "../xml-utils.js";
|
|
7
|
+
const MS_IN_S = 1000;
|
|
8
|
+
const DEFAULT_TEST_NAME = "The test's name is not defined";
|
|
9
|
+
const STDOUT_ATTACHMENT_NAME = "System output";
|
|
10
|
+
const STDERR_ATTACHMENT_NAME = "System error";
|
|
11
|
+
const SUITE_PACKAGE_NAME = "package";
|
|
12
|
+
const SUITE_PARENT_LABEL_NAME = "parentSuite";
|
|
13
|
+
const SUITE_LABEL_NAME = "suite";
|
|
14
|
+
const TEST_CLASS_LABEL_NAME = "testClass";
|
|
15
|
+
const arrayTags = new Set(["testsuite.testcase", "testsuites.testsuite", "testsuites.testsuite.testcase"]);
|
|
16
|
+
const xmlParser = new XMLParser({
|
|
17
|
+
parseTagValue: false,
|
|
18
|
+
ignoreAttributes: false,
|
|
19
|
+
attributeNamePrefix: "",
|
|
20
|
+
removeNSPrefix: true,
|
|
21
|
+
allowBooleanAttributes: true,
|
|
22
|
+
isArray: (tagName, jPath) => arrayTags.has(jPath),
|
|
23
|
+
});
|
|
24
|
+
const readerId = "junit";
|
|
25
|
+
export const junitXml = {
|
|
26
|
+
read: async (visitor, data) => {
|
|
27
|
+
if (data.getOriginalFileName().endsWith(".xml")) {
|
|
28
|
+
try {
|
|
29
|
+
const content = await data.asUtf8String();
|
|
30
|
+
if (!content) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const parsed = xmlParser.parse(content);
|
|
34
|
+
if (!isStringAnyRecord(parsed)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return await parseRootElement(visitor, parsed);
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
console.error("error parsing", data.getOriginalFileName(), e);
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
},
|
|
46
|
+
readerId: () => readerId,
|
|
47
|
+
};
|
|
48
|
+
const parseRootElement = async (visitor, xml) => {
|
|
49
|
+
const { testsuite: testSuite } = xml;
|
|
50
|
+
if (isEmptyElement(testSuite)) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
if (testSuite === undefined) {
|
|
54
|
+
const { testsuites: testSuites } = xml;
|
|
55
|
+
if (isEmptyElement(testSuites)) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
if (!isStringAnyRecord(testSuites)) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
const { testsuite: testSuitesArray } = testSuites;
|
|
62
|
+
if (isEmptyElement(testSuitesArray)) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
if (!isStringAnyRecordArray(testSuitesArray)) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
for (const testSuitesArrayElement of testSuitesArray) {
|
|
69
|
+
await parseTestSuite(visitor, testSuitesArrayElement, true);
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
if (!isStringAnyRecord(testSuite)) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
await parseTestSuite(visitor, testSuite, false);
|
|
77
|
+
return true;
|
|
78
|
+
};
|
|
79
|
+
const parseTestSuite = async (visitor, testSuite, isAggregated) => {
|
|
80
|
+
const { name, package: packageAttribute, testcase } = testSuite;
|
|
81
|
+
if (!isStringAnyRecordArray(testcase)) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
for (const testcaseElement of testcase) {
|
|
85
|
+
await parseTestCase(visitor, { name: ensureString(name), suitePackage: ensureString(packageAttribute) }, testcaseElement, isAggregated);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const parseTestCase = async (visitor, { name: suiteName, suitePackage }, testCase, isAggregated) => {
|
|
89
|
+
const { "name": nameAttribute, failure, error, skipped, "classname": classNameAttribute, time, "system-out": systemOutAttribute, "system-err": systemErrAttribute, } = testCase;
|
|
90
|
+
const name = ensureString(nameAttribute);
|
|
91
|
+
const className = ensureString(classNameAttribute);
|
|
92
|
+
const systemOut = ensureString(systemOutAttribute);
|
|
93
|
+
const systemErr = ensureString(systemErrAttribute);
|
|
94
|
+
const { status, message, trace } = getStatus(failure, error, skipped);
|
|
95
|
+
await visitor.visitTestResult({
|
|
96
|
+
name: name ?? DEFAULT_TEST_NAME,
|
|
97
|
+
fullName: convertFullName(className, name),
|
|
98
|
+
duration: convertDuration(time),
|
|
99
|
+
status,
|
|
100
|
+
message,
|
|
101
|
+
trace,
|
|
102
|
+
steps: await parseAttachments(visitor, systemOut, systemErr),
|
|
103
|
+
labels: convertLabels({ suitePackage, suiteName, className, isAggregated }),
|
|
104
|
+
}, { readerId });
|
|
105
|
+
};
|
|
106
|
+
const convertFullName = (className, name) => (className && name ? `${className}.${name}` : undefined);
|
|
107
|
+
const parseAttachments = async (visitor, systemOut, systemErr) => {
|
|
108
|
+
const attachments = [];
|
|
109
|
+
if (systemOut) {
|
|
110
|
+
attachments.push(await visitPlainTextAttachment(visitor, STDOUT_ATTACHMENT_NAME, systemOut));
|
|
111
|
+
}
|
|
112
|
+
if (systemErr) {
|
|
113
|
+
attachments.push(await visitPlainTextAttachment(visitor, STDERR_ATTACHMENT_NAME, systemErr));
|
|
114
|
+
}
|
|
115
|
+
return attachments;
|
|
116
|
+
};
|
|
117
|
+
const visitPlainTextAttachment = async (visitor, name, content) => {
|
|
118
|
+
const fileName = randomUUID();
|
|
119
|
+
await visitor.visitAttachmentFile(new BufferResultFile(Buffer.from(content), fileName), { readerId });
|
|
120
|
+
return {
|
|
121
|
+
type: "attachment",
|
|
122
|
+
contentType: "text/plain",
|
|
123
|
+
originalFileName: fileName,
|
|
124
|
+
name,
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
const convertLabels = ({ suitePackage, suiteName, className, isAggregated, }) => {
|
|
128
|
+
const labels = [];
|
|
129
|
+
if (suitePackage) {
|
|
130
|
+
labels.push({ name: SUITE_PACKAGE_NAME, value: suitePackage });
|
|
131
|
+
if (isAggregated) {
|
|
132
|
+
labels.push({ name: SUITE_PARENT_LABEL_NAME, value: suitePackage });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (suiteName) {
|
|
136
|
+
labels.push({ name: SUITE_LABEL_NAME, value: suiteName });
|
|
137
|
+
}
|
|
138
|
+
if (className) {
|
|
139
|
+
labels.push({ name: TEST_CLASS_LABEL_NAME, value: className });
|
|
140
|
+
}
|
|
141
|
+
return labels;
|
|
142
|
+
};
|
|
143
|
+
const convertDuration = (timeAttribute) => {
|
|
144
|
+
const time = ensureString(timeAttribute);
|
|
145
|
+
return time ? Math.round(parseFloat(time) * MS_IN_S) : undefined;
|
|
146
|
+
};
|
|
147
|
+
const getStatus = (failure, error, skipped) => maybeParseStatus("failed", failure) ??
|
|
148
|
+
maybeParseStatus("broken", error) ??
|
|
149
|
+
maybeParseStatus("skipped", skipped) ?? { status: "passed" };
|
|
150
|
+
const maybeParseStatus = (status, element) => {
|
|
151
|
+
if (isEmptyElement(element)) {
|
|
152
|
+
return { status };
|
|
153
|
+
}
|
|
154
|
+
if (isStringAnyRecord(element)) {
|
|
155
|
+
const { message, "#text": trace } = element;
|
|
156
|
+
return { status, message: ensureString(message), trace: ensureString(trace) };
|
|
157
|
+
}
|
|
158
|
+
};
|