cucumberjs-qase-reporter 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +14 -0
- package/dist/modules/eventStorage.d.ts +27 -0
- package/dist/modules/eventStorage.js +114 -0
- package/dist/modules/profilerTracker.d.ts +11 -0
- package/dist/modules/profilerTracker.js +28 -0
- package/dist/modules/resultBuilder.d.ts +22 -0
- package/dist/modules/resultBuilder.js +63 -0
- package/dist/modules/statusMaps.d.ts +5 -0
- package/dist/modules/statusMaps.js +23 -0
- package/dist/modules/statusTracker.d.ts +10 -0
- package/dist/modules/statusTracker.js +51 -0
- package/dist/modules/stepConverter.d.ts +6 -0
- package/dist/modules/stepConverter.js +38 -0
- package/dist/modules/tagParser.d.ts +6 -0
- package/dist/modules/tagParser.js +99 -0
- package/dist/reporter.d.ts +0 -5
- package/dist/reporter.js +5 -9
- package/dist/storage.d.ts +7 -116
- package/dist/storage.js +57 -464
- package/package.json +1 -1
package/changelog.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# cucumberjs-qase-reporter@2.4.0
|
|
2
|
+
|
|
3
|
+
## Changed
|
|
4
|
+
|
|
5
|
+
- Decomposed `storage.ts` (595 LOC) into a thin facade plus 6 focused modules under `src/modules/`: `TagParser`, `EventStorage`, `StatusTracker`, `StepConverter`, `ProfilerTracker`, `ResultBuilder`, plus `STATUS_MAP` / `STEP_STATUS_MAP` constants. Public contract preserved: `Storage` named export, constructor signature, all 7 instance methods, static `statusMap` and `stepStatusMap`, all 9 user-facing tag patterns.
|
|
6
|
+
|
|
7
|
+
## Bug fixes
|
|
8
|
+
|
|
9
|
+
- Cross-reporter consistency: `testops_id` is now `null` when project mapping (`@qaseid.PROJ(ids)` tag) is set, matching the behavior of mocha / cypress / jest reporters. Previously cucumberjs emitted both `testops_id: ids` and `testops_project_mapping: mapping`, which could lead to ambiguous results in multi-project runs.
|
|
10
|
+
|
|
11
|
+
## Internal
|
|
12
|
+
|
|
13
|
+
- Added 38 per-module unit tests on top of the existing 44 facade integration tests (82 total).
|
|
14
|
+
|
|
1
15
|
# cucumberjs-qase-reporter@2.3.0
|
|
2
16
|
|
|
3
17
|
## What's new
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Attachment as Attach, GherkinDocument, Pickle, TestCaseStarted, TestStepFinished } from '@cucumber/messages';
|
|
2
|
+
import { TestCase } from '@cucumber/messages/dist/esm/src/messages';
|
|
3
|
+
import { Attachment } from 'qase-javascript-commons';
|
|
4
|
+
import { ScenarioData } from '../models';
|
|
5
|
+
export declare class EventStorage {
|
|
6
|
+
private pickles;
|
|
7
|
+
private testCaseStarts;
|
|
8
|
+
private testStepFinished;
|
|
9
|
+
private testCases;
|
|
10
|
+
private scenarios;
|
|
11
|
+
private attachments;
|
|
12
|
+
addPickle(pickle: Pickle): void;
|
|
13
|
+
addScenario(document: GherkinDocument): void;
|
|
14
|
+
addAttachment(attachment: Attach): void;
|
|
15
|
+
addTestCase(testCase: TestCase): void;
|
|
16
|
+
addTestCaseStarted(testCaseStarted: TestCaseStarted): void;
|
|
17
|
+
addTestStepFinished(step: TestStepFinished): void;
|
|
18
|
+
getPickle(id: string): Pickle | undefined;
|
|
19
|
+
getTestCase(id: string): TestCase | undefined;
|
|
20
|
+
getTestCaseStarted(id: string): TestCaseStarted | undefined;
|
|
21
|
+
getScenario(id: string): ScenarioData | undefined;
|
|
22
|
+
getAttachments(key: string): Attachment[];
|
|
23
|
+
getTestStepFinished(id: string): TestStepFinished | undefined;
|
|
24
|
+
getAllTestStepFinished(): Map<string, TestStepFinished>;
|
|
25
|
+
private appendAttachment;
|
|
26
|
+
static getFileNameFromMediaType(mediaType: string): string;
|
|
27
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EventStorage = void 0;
|
|
4
|
+
const uuid_1 = require("uuid");
|
|
5
|
+
class EventStorage {
|
|
6
|
+
pickles = {};
|
|
7
|
+
testCaseStarts = {};
|
|
8
|
+
testStepFinished = {};
|
|
9
|
+
testCases = {};
|
|
10
|
+
scenarios = {};
|
|
11
|
+
attachments = {};
|
|
12
|
+
addPickle(pickle) {
|
|
13
|
+
this.pickles[pickle.id] = pickle;
|
|
14
|
+
}
|
|
15
|
+
addScenario(document) {
|
|
16
|
+
if (!document.feature)
|
|
17
|
+
return;
|
|
18
|
+
const { children, name } = document.feature;
|
|
19
|
+
for (const { scenario } of children) {
|
|
20
|
+
if (!scenario)
|
|
21
|
+
continue;
|
|
22
|
+
const parameters = {};
|
|
23
|
+
scenario.examples?.forEach((example) => {
|
|
24
|
+
if (example.tableHeader) {
|
|
25
|
+
const columnNames = example.tableHeader.cells.map((cell) => cell.value);
|
|
26
|
+
example.tableBody.forEach((row) => {
|
|
27
|
+
const rowParams = {};
|
|
28
|
+
row.cells.forEach((cell, index) => {
|
|
29
|
+
const columnName = columnNames[index];
|
|
30
|
+
if (columnName) {
|
|
31
|
+
rowParams[columnName] = cell.value;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
parameters[row.id] = rowParams;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
this.scenarios[scenario.id] = {
|
|
39
|
+
name,
|
|
40
|
+
parameters,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
addAttachment(attachment) {
|
|
45
|
+
if (attachment.testStepId) {
|
|
46
|
+
this.appendAttachment(attachment.testStepId, attachment);
|
|
47
|
+
}
|
|
48
|
+
if (attachment.testCaseStartedId) {
|
|
49
|
+
this.appendAttachment(attachment.testCaseStartedId, attachment);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
addTestCase(testCase) {
|
|
53
|
+
this.testCases[testCase.id] = testCase;
|
|
54
|
+
}
|
|
55
|
+
addTestCaseStarted(testCaseStarted) {
|
|
56
|
+
this.testCaseStarts[testCaseStarted.id] = testCaseStarted;
|
|
57
|
+
}
|
|
58
|
+
addTestStepFinished(step) {
|
|
59
|
+
this.testStepFinished[step.testStepId] = step;
|
|
60
|
+
}
|
|
61
|
+
getPickle(id) {
|
|
62
|
+
return this.pickles[id];
|
|
63
|
+
}
|
|
64
|
+
getTestCase(id) {
|
|
65
|
+
return this.testCases[id];
|
|
66
|
+
}
|
|
67
|
+
getTestCaseStarted(id) {
|
|
68
|
+
return this.testCaseStarts[id];
|
|
69
|
+
}
|
|
70
|
+
getScenario(id) {
|
|
71
|
+
return this.scenarios[id];
|
|
72
|
+
}
|
|
73
|
+
getAttachments(key) {
|
|
74
|
+
return this.attachments[key] ?? [];
|
|
75
|
+
}
|
|
76
|
+
getTestStepFinished(id) {
|
|
77
|
+
return this.testStepFinished[id];
|
|
78
|
+
}
|
|
79
|
+
getAllTestStepFinished() {
|
|
80
|
+
return new Map(Object.entries(this.testStepFinished));
|
|
81
|
+
}
|
|
82
|
+
appendAttachment(key, attachment) {
|
|
83
|
+
const list = this.attachments[key] ?? [];
|
|
84
|
+
this.attachments[key] = list;
|
|
85
|
+
list.push({
|
|
86
|
+
file_name: EventStorage.getFileNameFromMediaType(attachment.mediaType),
|
|
87
|
+
mime_type: attachment.mediaType,
|
|
88
|
+
file_path: null,
|
|
89
|
+
content: attachment.body,
|
|
90
|
+
size: 0,
|
|
91
|
+
id: (0, uuid_1.v4)(),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
static getFileNameFromMediaType(mediaType) {
|
|
95
|
+
const extensions = {
|
|
96
|
+
'text/plain': 'txt',
|
|
97
|
+
'application/json': 'json',
|
|
98
|
+
'image/png': 'png',
|
|
99
|
+
'image/jpeg': 'jpg',
|
|
100
|
+
'image/gif': 'gif',
|
|
101
|
+
'text/html': 'html',
|
|
102
|
+
'application/pdf': 'pdf',
|
|
103
|
+
'application/xml': 'xml',
|
|
104
|
+
'application/zip': 'zip',
|
|
105
|
+
'application/msword': 'doc',
|
|
106
|
+
'application/vnd.ms-excel': 'xls',
|
|
107
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx',
|
|
108
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xlsx',
|
|
109
|
+
};
|
|
110
|
+
const extension = extensions[mediaType];
|
|
111
|
+
return extension ? `file.${extension}` : 'file';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
exports.EventStorage = EventStorage;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { TestStepType } from 'qase-javascript-commons';
|
|
2
|
+
import { NetworkProfiler } from 'qase-javascript-commons/profilers';
|
|
3
|
+
export declare class ProfilerTracker {
|
|
4
|
+
private readonly profiler;
|
|
5
|
+
private snapshots;
|
|
6
|
+
constructor(profiler: NetworkProfiler | null);
|
|
7
|
+
onTestStart(testCaseStartedId: string): void;
|
|
8
|
+
getEvents(testCaseStartedId: string): TestStepType[];
|
|
9
|
+
reset(testCaseStartedId: string): void;
|
|
10
|
+
restore(): void;
|
|
11
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProfilerTracker = void 0;
|
|
4
|
+
class ProfilerTracker {
|
|
5
|
+
profiler;
|
|
6
|
+
snapshots = {};
|
|
7
|
+
constructor(profiler) {
|
|
8
|
+
this.profiler = profiler;
|
|
9
|
+
}
|
|
10
|
+
onTestStart(testCaseStartedId) {
|
|
11
|
+
if (this.profiler) {
|
|
12
|
+
this.snapshots[testCaseStartedId] = this.profiler.getAllSteps().length;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
getEvents(testCaseStartedId) {
|
|
16
|
+
if (!this.profiler)
|
|
17
|
+
return [];
|
|
18
|
+
const snapshot = this.snapshots[testCaseStartedId] ?? 0;
|
|
19
|
+
return this.profiler.getAllSteps().slice(snapshot);
|
|
20
|
+
}
|
|
21
|
+
reset(testCaseStartedId) {
|
|
22
|
+
delete this.snapshots[testCaseStartedId];
|
|
23
|
+
}
|
|
24
|
+
restore() {
|
|
25
|
+
this.profiler?.restore();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.ProfilerTracker = ProfilerTracker;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Pickle, TestCaseFinished, TestCaseStarted } from '@cucumber/messages';
|
|
2
|
+
import { TestCase } from '@cucumber/messages/dist/esm/src/messages';
|
|
3
|
+
import { Attachment, CompoundError, TestResultType, TestStatusEnum, TestStepType } from 'qase-javascript-commons';
|
|
4
|
+
import { TestMetadata } from '../models';
|
|
5
|
+
export interface BuildArgs {
|
|
6
|
+
testCaseFinished: TestCaseFinished;
|
|
7
|
+
testCaseStarted: TestCaseStarted;
|
|
8
|
+
testCase: TestCase;
|
|
9
|
+
pickle: Pickle;
|
|
10
|
+
metadata: TestMetadata;
|
|
11
|
+
status: TestStatusEnum;
|
|
12
|
+
error: CompoundError | undefined;
|
|
13
|
+
steps: TestStepType[];
|
|
14
|
+
profilerSteps: TestStepType[];
|
|
15
|
+
attachments: Attachment[];
|
|
16
|
+
scenarioName: string | undefined;
|
|
17
|
+
scenarioParameters: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
export declare class ResultBuilder {
|
|
20
|
+
static build(args: BuildArgs): TestResultType;
|
|
21
|
+
static getSignature(pickle: Pickle, ids: number[], parameters?: Record<string, string>): string;
|
|
22
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ResultBuilder = void 0;
|
|
4
|
+
const qase_javascript_commons_1 = require("qase-javascript-commons");
|
|
5
|
+
class ResultBuilder {
|
|
6
|
+
static build(args) {
|
|
7
|
+
const { testCaseFinished, testCaseStarted, pickle, metadata, status, error, steps, profilerSteps, attachments, scenarioName, scenarioParameters, } = args;
|
|
8
|
+
let relations = null;
|
|
9
|
+
if (metadata.suite) {
|
|
10
|
+
const suiteParts = metadata.suite.split('\t').filter((part) => part.trim().length > 0);
|
|
11
|
+
relations = {
|
|
12
|
+
suite: {
|
|
13
|
+
data: suiteParts.map((suite) => ({
|
|
14
|
+
title: suite.trim(),
|
|
15
|
+
public_id: null,
|
|
16
|
+
})),
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
else if (scenarioName !== undefined) {
|
|
21
|
+
relations = {
|
|
22
|
+
suite: {
|
|
23
|
+
data: [
|
|
24
|
+
{ title: scenarioName, public_id: null },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// Merge scenario params with metadata parameters (metadata wins)
|
|
30
|
+
const params = { ...scenarioParameters, ...metadata.parameters };
|
|
31
|
+
const hasProjectMapping = Object.keys(metadata.projectMapping).length > 0;
|
|
32
|
+
return {
|
|
33
|
+
attachments,
|
|
34
|
+
author: null,
|
|
35
|
+
execution: {
|
|
36
|
+
status,
|
|
37
|
+
start_time: testCaseStarted.timestamp.seconds,
|
|
38
|
+
end_time: testCaseFinished.timestamp.seconds,
|
|
39
|
+
duration: Math.abs(testCaseFinished.timestamp.seconds - testCaseStarted.timestamp.seconds) * 1000,
|
|
40
|
+
stacktrace: error?.stacktrace ?? null,
|
|
41
|
+
thread: null,
|
|
42
|
+
},
|
|
43
|
+
fields: metadata.fields,
|
|
44
|
+
message: error?.message ?? null,
|
|
45
|
+
muted: false,
|
|
46
|
+
params,
|
|
47
|
+
group_params: metadata.group_params,
|
|
48
|
+
relations,
|
|
49
|
+
run_id: null,
|
|
50
|
+
signature: ResultBuilder.getSignature(pickle, metadata.ids, params),
|
|
51
|
+
steps: [...steps, ...profilerSteps],
|
|
52
|
+
testops_id: hasProjectMapping ? null : (metadata.ids.length > 0 ? metadata.ids : null),
|
|
53
|
+
testops_project_mapping: hasProjectMapping ? metadata.projectMapping : null,
|
|
54
|
+
id: testCaseStarted.id,
|
|
55
|
+
title: metadata.title ?? pickle.name,
|
|
56
|
+
tags: metadata.tags,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
static getSignature(pickle, ids, parameters = {}) {
|
|
60
|
+
return (0, qase_javascript_commons_1.generateSignature)(ids, [...pickle.uri.split('/'), pickle.name], parameters);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.ResultBuilder = ResultBuilder;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Status } from '@cucumber/cucumber';
|
|
2
|
+
import { StepStatusEnum, TestStatusEnum } from 'qase-javascript-commons';
|
|
3
|
+
export type TestStepResultStatus = (typeof Status)[keyof typeof Status];
|
|
4
|
+
export declare const STATUS_MAP: Record<TestStepResultStatus, TestStatusEnum>;
|
|
5
|
+
export declare const STEP_STATUS_MAP: Record<TestStepResultStatus, StepStatusEnum>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.STEP_STATUS_MAP = exports.STATUS_MAP = void 0;
|
|
4
|
+
const cucumber_1 = require("@cucumber/cucumber");
|
|
5
|
+
const qase_javascript_commons_1 = require("qase-javascript-commons");
|
|
6
|
+
exports.STATUS_MAP = {
|
|
7
|
+
[cucumber_1.Status.PASSED]: qase_javascript_commons_1.TestStatusEnum.passed,
|
|
8
|
+
[cucumber_1.Status.FAILED]: qase_javascript_commons_1.TestStatusEnum.failed,
|
|
9
|
+
[cucumber_1.Status.SKIPPED]: qase_javascript_commons_1.TestStatusEnum.skipped,
|
|
10
|
+
[cucumber_1.Status.AMBIGUOUS]: qase_javascript_commons_1.TestStatusEnum.invalid,
|
|
11
|
+
[cucumber_1.Status.PENDING]: qase_javascript_commons_1.TestStatusEnum.skipped,
|
|
12
|
+
[cucumber_1.Status.UNDEFINED]: qase_javascript_commons_1.TestStatusEnum.skipped,
|
|
13
|
+
[cucumber_1.Status.UNKNOWN]: qase_javascript_commons_1.TestStatusEnum.skipped,
|
|
14
|
+
};
|
|
15
|
+
exports.STEP_STATUS_MAP = {
|
|
16
|
+
[cucumber_1.Status.PASSED]: qase_javascript_commons_1.StepStatusEnum.passed,
|
|
17
|
+
[cucumber_1.Status.FAILED]: qase_javascript_commons_1.StepStatusEnum.failed,
|
|
18
|
+
[cucumber_1.Status.SKIPPED]: qase_javascript_commons_1.StepStatusEnum.skipped,
|
|
19
|
+
[cucumber_1.Status.AMBIGUOUS]: qase_javascript_commons_1.StepStatusEnum.failed,
|
|
20
|
+
[cucumber_1.Status.PENDING]: qase_javascript_commons_1.StepStatusEnum.skipped,
|
|
21
|
+
[cucumber_1.Status.UNDEFINED]: qase_javascript_commons_1.StepStatusEnum.skipped,
|
|
22
|
+
[cucumber_1.Status.UNKNOWN]: qase_javascript_commons_1.StepStatusEnum.skipped,
|
|
23
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { TestStepFinished } from '@cucumber/messages';
|
|
2
|
+
import { CompoundError, TestStatusEnum } from 'qase-javascript-commons';
|
|
3
|
+
export declare class StatusTracker {
|
|
4
|
+
private results;
|
|
5
|
+
private errors;
|
|
6
|
+
onTestStarted(testCaseStartedId: string): void;
|
|
7
|
+
applyStep(step: TestStepFinished): void;
|
|
8
|
+
getStatus(testCaseStartedId: string): TestStatusEnum;
|
|
9
|
+
getErrors(testCaseStartedId: string): CompoundError | undefined;
|
|
10
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StatusTracker = void 0;
|
|
4
|
+
const qase_javascript_commons_1 = require("qase-javascript-commons");
|
|
5
|
+
const statusMaps_1 = require("./statusMaps");
|
|
6
|
+
class StatusTracker {
|
|
7
|
+
results = {};
|
|
8
|
+
errors = {};
|
|
9
|
+
onTestStarted(testCaseStartedId) {
|
|
10
|
+
this.results[testCaseStartedId] = qase_javascript_commons_1.TestStatusEnum.passed;
|
|
11
|
+
}
|
|
12
|
+
applyStep(step) {
|
|
13
|
+
const oldStatus = this.results[step.testCaseStartedId];
|
|
14
|
+
let error = null;
|
|
15
|
+
if (step.testStepResult.message) {
|
|
16
|
+
error = new Error(step.testStepResult.message);
|
|
17
|
+
}
|
|
18
|
+
const newStatus = (0, qase_javascript_commons_1.determineTestStatus)(error, statusMaps_1.STATUS_MAP[step.testStepResult.status]);
|
|
19
|
+
if (newStatus !== qase_javascript_commons_1.TestStatusEnum.passed) {
|
|
20
|
+
if (step.testStepResult.message) {
|
|
21
|
+
if (!this.errors[step.testCaseStartedId]) {
|
|
22
|
+
this.errors[step.testCaseStartedId] = [];
|
|
23
|
+
}
|
|
24
|
+
this.errors[step.testCaseStartedId]?.push(step.testStepResult.message);
|
|
25
|
+
}
|
|
26
|
+
if (oldStatus) {
|
|
27
|
+
if (oldStatus !== qase_javascript_commons_1.TestStatusEnum.failed && oldStatus !== qase_javascript_commons_1.TestStatusEnum.invalid) {
|
|
28
|
+
this.results[step.testCaseStartedId] = newStatus;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
this.results[step.testCaseStartedId] = newStatus;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
getStatus(testCaseStartedId) {
|
|
37
|
+
return this.results[testCaseStartedId] ?? qase_javascript_commons_1.TestStatusEnum.passed;
|
|
38
|
+
}
|
|
39
|
+
getErrors(testCaseStartedId) {
|
|
40
|
+
const messages = this.errors[testCaseStartedId];
|
|
41
|
+
if (!messages)
|
|
42
|
+
return undefined;
|
|
43
|
+
const err = new qase_javascript_commons_1.CompoundError();
|
|
44
|
+
messages.forEach((message) => {
|
|
45
|
+
err.addMessage(message);
|
|
46
|
+
err.addStacktrace(message);
|
|
47
|
+
});
|
|
48
|
+
return err;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.StatusTracker = StatusTracker;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { PickleStep, TestStepFinished } from '@cucumber/messages';
|
|
2
|
+
import { TestCase } from '@cucumber/messages/dist/esm/src/messages';
|
|
3
|
+
import { Attachment, TestStepType } from 'qase-javascript-commons';
|
|
4
|
+
export declare class StepConverter {
|
|
5
|
+
static convert(pickleSteps: readonly PickleStep[], testCase: TestCase, finishedSteps: Map<string, TestStepFinished>, getAttachments: (stepId: string) => Attachment[]): TestStepType[];
|
|
6
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StepConverter = void 0;
|
|
4
|
+
const qase_javascript_commons_1 = require("qase-javascript-commons");
|
|
5
|
+
const statusMaps_1 = require("./statusMaps");
|
|
6
|
+
class StepConverter {
|
|
7
|
+
static convert(pickleSteps, testCase, finishedSteps, getAttachments) {
|
|
8
|
+
const results = [];
|
|
9
|
+
for (const s of testCase.testSteps) {
|
|
10
|
+
const finished = finishedSteps.get(s.id);
|
|
11
|
+
if (!finished)
|
|
12
|
+
continue;
|
|
13
|
+
const step = pickleSteps.find((ps) => ps.id === s.pickleStepId);
|
|
14
|
+
if (!step)
|
|
15
|
+
continue;
|
|
16
|
+
results.push({
|
|
17
|
+
id: s.id,
|
|
18
|
+
step_type: qase_javascript_commons_1.StepType.GHERKIN,
|
|
19
|
+
data: {
|
|
20
|
+
keyword: step.text,
|
|
21
|
+
name: step.text,
|
|
22
|
+
line: 0,
|
|
23
|
+
},
|
|
24
|
+
execution: {
|
|
25
|
+
status: statusMaps_1.STEP_STATUS_MAP[finished.testStepResult.status],
|
|
26
|
+
start_time: null,
|
|
27
|
+
end_time: null,
|
|
28
|
+
duration: finished.testStepResult.duration.seconds * 1000,
|
|
29
|
+
},
|
|
30
|
+
attachments: getAttachments(s.id),
|
|
31
|
+
steps: [],
|
|
32
|
+
parent_id: null,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return results;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.StepConverter = StepConverter;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TagParser = void 0;
|
|
4
|
+
const qase_javascript_commons_1 = require("qase-javascript-commons");
|
|
5
|
+
const qaseIdRegExp = /^@[Qq]-?(\d+)$/;
|
|
6
|
+
const newQaseIdRegExp = /^@[Qq]ase[Ii][Dd]=(\d+(?:,\s*\d+)*)$/;
|
|
7
|
+
const qaseTitleRegExp = /^@[Qq]ase[Tt]itle=(.+)$/;
|
|
8
|
+
const qaseFieldsRegExp = /^@[Qq]ase[Ff]ields=(.+)$/;
|
|
9
|
+
const qaseParametersRegExp = /^@[Qq]ase[Pp]arameters=(.+)$/;
|
|
10
|
+
const qaseGroupParametersRegExp = /^@[Qq]ase[Gg]roup[Pp]arameters=(.+)$/;
|
|
11
|
+
const qaseSuiteRegExp = /^@[Qq]ase[Ss]uite=(.+)$/;
|
|
12
|
+
const qaseIgnoreRegExp = /^@[Qq]ase[Ii][Gg][Nn][Oo][Rr][Ee]$/;
|
|
13
|
+
const qaseTagsRegExp = /^@[Qq]ase[Tt]ags=(.+)$/;
|
|
14
|
+
class TagParser {
|
|
15
|
+
static parse(tags) {
|
|
16
|
+
const tagNames = tags.map((t) => t.name);
|
|
17
|
+
const { legacyIds, projectMapping } = (0, qase_javascript_commons_1.parseProjectMappingFromTags)(tagNames);
|
|
18
|
+
const metadata = {
|
|
19
|
+
ids: [...legacyIds],
|
|
20
|
+
projectMapping: { ...projectMapping },
|
|
21
|
+
fields: {},
|
|
22
|
+
title: null,
|
|
23
|
+
isIgnore: false,
|
|
24
|
+
parameters: {},
|
|
25
|
+
group_params: {},
|
|
26
|
+
suite: null,
|
|
27
|
+
tags: [],
|
|
28
|
+
};
|
|
29
|
+
for (const tag of tags) {
|
|
30
|
+
if (qaseIdRegExp.test(tag.name)) {
|
|
31
|
+
metadata.ids.push(Number(tag.name.replace(/^@[Qq]-?/, '')));
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (newQaseIdRegExp.test(tag.name)) {
|
|
35
|
+
const idsStr = tag.name.replace(/^@[Qq]ase[Ii][Dd]=/, '');
|
|
36
|
+
metadata.ids.push(...idsStr.split(',').map((s) => Number(s.trim())));
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (qaseTitleRegExp.test(tag.name)) {
|
|
40
|
+
metadata.title = tag.name.replace(/^@[Qq]ase[Tt]itle=/, '');
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (qaseFieldsRegExp.test(tag.name)) {
|
|
44
|
+
const value = tag.name.replace(/^@[Qq]ase[Ff]ields=/, '');
|
|
45
|
+
try {
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
47
|
+
const record = JSON.parse(TagParser.normalizeJsonString(value));
|
|
48
|
+
metadata.fields = { ...metadata.fields, ...record };
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// do nothing
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (qaseParametersRegExp.test(tag.name)) {
|
|
55
|
+
const value = tag.name.replace(/^@[Qq]ase[Pp]arameters=/, '');
|
|
56
|
+
try {
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
58
|
+
const record = JSON.parse(TagParser.normalizeJsonString(value));
|
|
59
|
+
metadata.parameters = { ...metadata.parameters, ...record };
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// do nothing
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (qaseGroupParametersRegExp.test(tag.name)) {
|
|
66
|
+
const value = tag.name.replace(/^@[Qq]ase[Gg]roup[Pp]arameters=/, '');
|
|
67
|
+
try {
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
69
|
+
const record = JSON.parse(TagParser.normalizeJsonString(value));
|
|
70
|
+
metadata.group_params = { ...metadata.group_params, ...record };
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// do nothing
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (qaseSuiteRegExp.test(tag.name)) {
|
|
77
|
+
metadata.suite = tag.name.replace(/^@[Qq]ase[Ss]uite=/, '');
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (qaseTagsRegExp.test(tag.name)) {
|
|
81
|
+
const value = tag.name.replace(/^@[Qq]ase[Tt]ags=/, '');
|
|
82
|
+
const parsedTags = value.split(',').map((t) => t.trim()).filter((t) => t.length > 0);
|
|
83
|
+
metadata.tags.push(...parsedTags);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (qaseIgnoreRegExp.test(tag.name)) {
|
|
87
|
+
metadata.isIgnore = true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return metadata;
|
|
91
|
+
}
|
|
92
|
+
static normalizeJsonString(jsonString) {
|
|
93
|
+
if (jsonString.includes("'")) {
|
|
94
|
+
return jsonString.replace(/'/g, '"');
|
|
95
|
+
}
|
|
96
|
+
return jsonString;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
exports.TagParser = TagParser;
|
package/dist/reporter.d.ts
CHANGED
package/dist/reporter.js
CHANGED
|
@@ -20,11 +20,6 @@ class CucumberQaseReporter extends cucumber_1.Formatter {
|
|
|
20
20
|
* @private
|
|
21
21
|
*/
|
|
22
22
|
reporter;
|
|
23
|
-
/**
|
|
24
|
-
* @type {NetworkProfiler | null}
|
|
25
|
-
* @private
|
|
26
|
-
*/
|
|
27
|
-
profiler = null;
|
|
28
23
|
/**
|
|
29
24
|
* @type {EventEmitter}
|
|
30
25
|
* @private
|
|
@@ -45,15 +40,16 @@ class CucumberQaseReporter extends cucumber_1.Formatter {
|
|
|
45
40
|
frameworkName: 'cucumberjs',
|
|
46
41
|
reporterName: 'cucumberjs-qase-reporter',
|
|
47
42
|
});
|
|
43
|
+
let profiler = null;
|
|
48
44
|
if (composedOptions.profilers?.includes('network')) {
|
|
49
|
-
|
|
45
|
+
profiler = new profilers_1.NetworkProfiler({
|
|
50
46
|
skipDomains: composedOptions.networkProfiler?.skip_domains,
|
|
51
47
|
trackOnFail: composedOptions.networkProfiler?.track_on_fail,
|
|
52
48
|
});
|
|
53
|
-
|
|
49
|
+
profiler.enable();
|
|
54
50
|
}
|
|
55
51
|
this.eventBroadcaster = formatterOptions.eventBroadcaster;
|
|
56
|
-
this.storage = new storage_1.Storage(
|
|
52
|
+
this.storage = new storage_1.Storage(profiler);
|
|
57
53
|
this.bindEventListeners();
|
|
58
54
|
}
|
|
59
55
|
/**
|
|
@@ -99,7 +95,7 @@ class CucumberQaseReporter extends cucumber_1.Formatter {
|
|
|
99
95
|
* @private
|
|
100
96
|
*/
|
|
101
97
|
async publishResults() {
|
|
102
|
-
this.
|
|
98
|
+
this.storage.restore();
|
|
103
99
|
await this.reporter.publish();
|
|
104
100
|
}
|
|
105
101
|
/**
|
package/dist/storage.d.ts
CHANGED
|
@@ -2,129 +2,20 @@ import { Attachment as Attach, GherkinDocument, Pickle, TestCaseFinished, TestCa
|
|
|
2
2
|
import { StepStatusEnum, TestResultType, TestStatusEnum } from 'qase-javascript-commons';
|
|
3
3
|
import { NetworkProfiler } from 'qase-javascript-commons/profilers';
|
|
4
4
|
import { TestCase } from '@cucumber/messages/dist/esm/src/messages';
|
|
5
|
-
import {
|
|
6
|
-
type TestStepResultStatus = (typeof Status)[keyof typeof Status];
|
|
5
|
+
import { TestStepResultStatus } from './modules/statusMaps';
|
|
7
6
|
export declare class Storage {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
private
|
|
13
|
-
/**
|
|
14
|
-
* @type {Record<string, number>}
|
|
15
|
-
* @private
|
|
16
|
-
*/
|
|
17
|
-
private profilerStepSnapshots;
|
|
7
|
+
static statusMap: Record<TestStepResultStatus, TestStatusEnum>;
|
|
8
|
+
static stepStatusMap: Record<TestStepResultStatus, StepStatusEnum>;
|
|
9
|
+
private events;
|
|
10
|
+
private statusTracker;
|
|
11
|
+
private profilerTracker;
|
|
18
12
|
constructor(profiler?: NetworkProfiler | null);
|
|
19
|
-
|
|
20
|
-
* @type {Record<string, Pickle>}
|
|
21
|
-
* @private
|
|
22
|
-
*/
|
|
23
|
-
private pickles;
|
|
24
|
-
/**
|
|
25
|
-
* @type {Record<string, TestCaseStarted>}
|
|
26
|
-
* @private
|
|
27
|
-
*/
|
|
28
|
-
private testCaseStarts;
|
|
29
|
-
/**
|
|
30
|
-
* @type {Record<string, TestStepFinished>}
|
|
31
|
-
* @private
|
|
32
|
-
*/
|
|
33
|
-
private testCaseSteps;
|
|
34
|
-
/**
|
|
35
|
-
* @type {Record<string, TestStatusEnum>}
|
|
36
|
-
* @private
|
|
37
|
-
*/
|
|
38
|
-
private testCaseStartedResult;
|
|
39
|
-
/**
|
|
40
|
-
* @type {Record<string, string[]>}
|
|
41
|
-
* @private
|
|
42
|
-
*/
|
|
43
|
-
private testCaseStartedErrors;
|
|
44
|
-
/**
|
|
45
|
-
* @type {Record<string, string>}
|
|
46
|
-
* @private
|
|
47
|
-
*/
|
|
48
|
-
private testCases;
|
|
49
|
-
/**
|
|
50
|
-
* @type {Record<string, string>}
|
|
51
|
-
* @private
|
|
52
|
-
*/
|
|
53
|
-
private scenarios;
|
|
54
|
-
/**
|
|
55
|
-
* @type {Record<string, string>}
|
|
56
|
-
* @private
|
|
57
|
-
*/
|
|
58
|
-
private attachments;
|
|
59
|
-
/**
|
|
60
|
-
* Add pickle to storage
|
|
61
|
-
* @param {Pickle} pickle
|
|
62
|
-
*/
|
|
13
|
+
restore(): void;
|
|
63
14
|
addPickle(pickle: Pickle): void;
|
|
64
|
-
/**
|
|
65
|
-
* Add scenario to storage
|
|
66
|
-
* @param {GherkinDocument} document
|
|
67
|
-
*/
|
|
68
15
|
addScenario(document: GherkinDocument): void;
|
|
69
|
-
/**
|
|
70
|
-
* Add attachment to storage
|
|
71
|
-
* @param {Attach} attachment
|
|
72
|
-
*/
|
|
73
16
|
addAttachment(attachment: Attach): void;
|
|
74
|
-
/**
|
|
75
|
-
* Add test case to storage
|
|
76
|
-
* @param {TestCase} testCase
|
|
77
|
-
*/
|
|
78
17
|
addTestCase(testCase: TestCase): void;
|
|
79
|
-
/**
|
|
80
|
-
* Get all test cases
|
|
81
|
-
* @param {TestCaseStarted} testCaseStarted
|
|
82
|
-
*/
|
|
83
18
|
addTestCaseStarted(testCaseStarted: TestCaseStarted): void;
|
|
84
|
-
/**
|
|
85
|
-
* Add test case step to storage
|
|
86
|
-
* @param {TestStepFinished} testCaseStep
|
|
87
|
-
*/
|
|
88
19
|
addTestCaseStep(testCaseStep: TestStepFinished): void;
|
|
89
|
-
/**
|
|
90
|
-
* Convert test case to test result
|
|
91
|
-
* @param {TestCaseFinished} testCase
|
|
92
|
-
* @returns {undefined | TestResultType}
|
|
93
|
-
*/
|
|
94
20
|
convertTestCase(testCase: TestCaseFinished): undefined | TestResultType;
|
|
95
|
-
/**
|
|
96
|
-
* Convert test steps to test result steps
|
|
97
|
-
* @param {readonly PickleStep[]} steps
|
|
98
|
-
* @param {TestCase} testCase
|
|
99
|
-
* @returns {TestStepType[]}
|
|
100
|
-
* @private
|
|
101
|
-
*/
|
|
102
|
-
private convertSteps;
|
|
103
|
-
/**
|
|
104
|
-
* @type {Record<TestStepResultStatus, TestStatusEnum>}
|
|
105
|
-
*/
|
|
106
|
-
static statusMap: Record<TestStepResultStatus, TestStatusEnum>;
|
|
107
|
-
/**
|
|
108
|
-
* @type {Record<TestStepResultStatus, StepStatusEnum>}
|
|
109
|
-
*/
|
|
110
|
-
static stepStatusMap: Record<TestStepResultStatus, StepStatusEnum>;
|
|
111
|
-
private parseTags;
|
|
112
|
-
/**
|
|
113
|
-
* @param {Pickle} pickle
|
|
114
|
-
* @param {number[]} ids
|
|
115
|
-
* @param {Record<string, string>} parameters
|
|
116
|
-
* @private
|
|
117
|
-
*/
|
|
118
|
-
private getSignature;
|
|
119
|
-
private getError;
|
|
120
|
-
/**
|
|
121
|
-
* Normalize JSON string by converting single quotes to double quotes
|
|
122
|
-
* This allows parsing JSON-like strings with single quotes
|
|
123
|
-
* @param {string} jsonString
|
|
124
|
-
* @returns {string}
|
|
125
|
-
* @private
|
|
126
|
-
*/
|
|
127
|
-
private normalizeJsonString;
|
|
128
|
-
private getFileNameFromMediaType;
|
|
129
21
|
}
|
|
130
|
-
export {};
|
package/dist/storage.js
CHANGED
|
@@ -1,496 +1,89 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Storage = void 0;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const qaseParametersRegExp = /^@[Qq]ase[Pp]arameters=(.+)$/;
|
|
12
|
-
const qaseGroupParametersRegExp = /^@[Qq]ase[Gg]roup[Pp]arameters=(.+)$/;
|
|
13
|
-
const qaseSuiteRegExp = /^@[Qq]ase[Ss]uite=(.+)$/;
|
|
14
|
-
const qaseIgnoreRegExp = /^@[Qq]ase[Ii][Gg][Nn][Oo][Rr][Ee]$/;
|
|
15
|
-
const qaseTagsRegExp = /^@[Qq]ase[Tt]ags=(.+)$/;
|
|
4
|
+
const statusMaps_1 = require("./modules/statusMaps");
|
|
5
|
+
const tagParser_1 = require("./modules/tagParser");
|
|
6
|
+
const eventStorage_1 = require("./modules/eventStorage");
|
|
7
|
+
const statusTracker_1 = require("./modules/statusTracker");
|
|
8
|
+
const stepConverter_1 = require("./modules/stepConverter");
|
|
9
|
+
const profilerTracker_1 = require("./modules/profilerTracker");
|
|
10
|
+
const resultBuilder_1 = require("./modules/resultBuilder");
|
|
16
11
|
class Storage {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @type {Record<string, number>}
|
|
24
|
-
* @private
|
|
25
|
-
*/
|
|
26
|
-
profilerStepSnapshots = {};
|
|
12
|
+
static statusMap = statusMaps_1.STATUS_MAP;
|
|
13
|
+
static stepStatusMap = statusMaps_1.STEP_STATUS_MAP;
|
|
14
|
+
events = new eventStorage_1.EventStorage();
|
|
15
|
+
statusTracker = new statusTracker_1.StatusTracker();
|
|
16
|
+
profilerTracker;
|
|
27
17
|
constructor(profiler = null) {
|
|
28
|
-
this.
|
|
18
|
+
this.profilerTracker = new profilerTracker_1.ProfilerTracker(profiler);
|
|
19
|
+
}
|
|
20
|
+
restore() {
|
|
21
|
+
this.profilerTracker.restore();
|
|
29
22
|
}
|
|
30
|
-
/**
|
|
31
|
-
* @type {Record<string, Pickle>}
|
|
32
|
-
* @private
|
|
33
|
-
*/
|
|
34
|
-
pickles = {};
|
|
35
|
-
/**
|
|
36
|
-
* @type {Record<string, TestCaseStarted>}
|
|
37
|
-
* @private
|
|
38
|
-
*/
|
|
39
|
-
testCaseStarts = {};
|
|
40
|
-
/**
|
|
41
|
-
* @type {Record<string, TestStepFinished>}
|
|
42
|
-
* @private
|
|
43
|
-
*/
|
|
44
|
-
testCaseSteps = {};
|
|
45
|
-
/**
|
|
46
|
-
* @type {Record<string, TestStatusEnum>}
|
|
47
|
-
* @private
|
|
48
|
-
*/
|
|
49
|
-
testCaseStartedResult = {};
|
|
50
|
-
/**
|
|
51
|
-
* @type {Record<string, string[]>}
|
|
52
|
-
* @private
|
|
53
|
-
*/
|
|
54
|
-
testCaseStartedErrors = {};
|
|
55
|
-
/**
|
|
56
|
-
* @type {Record<string, string>}
|
|
57
|
-
* @private
|
|
58
|
-
*/
|
|
59
|
-
testCases = {};
|
|
60
|
-
/**
|
|
61
|
-
* @type {Record<string, string>}
|
|
62
|
-
* @private
|
|
63
|
-
*/
|
|
64
|
-
scenarios = {};
|
|
65
|
-
/**
|
|
66
|
-
* @type {Record<string, string>}
|
|
67
|
-
* @private
|
|
68
|
-
*/
|
|
69
|
-
attachments = {};
|
|
70
|
-
/**
|
|
71
|
-
* Add pickle to storage
|
|
72
|
-
* @param {Pickle} pickle
|
|
73
|
-
*/
|
|
74
23
|
addPickle(pickle) {
|
|
75
|
-
this.
|
|
24
|
+
this.events.addPickle(pickle);
|
|
76
25
|
}
|
|
77
|
-
/**
|
|
78
|
-
* Add scenario to storage
|
|
79
|
-
* @param {GherkinDocument} document
|
|
80
|
-
*/
|
|
81
26
|
addScenario(document) {
|
|
82
|
-
|
|
83
|
-
const { children, name } = document.feature;
|
|
84
|
-
children.forEach(({ scenario }) => {
|
|
85
|
-
if (scenario) {
|
|
86
|
-
const parameters = {};
|
|
87
|
-
scenario.examples?.forEach((example) => {
|
|
88
|
-
if (example.tableHeader && example.tableBody) {
|
|
89
|
-
const columnNames = example.tableHeader.cells.map(cell => cell.value);
|
|
90
|
-
example.tableBody.forEach((row) => {
|
|
91
|
-
const rowParams = {};
|
|
92
|
-
row.cells.forEach((cell, index) => {
|
|
93
|
-
const columnName = columnNames[index];
|
|
94
|
-
if (columnName) {
|
|
95
|
-
rowParams[columnName] = cell.value;
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
parameters[row.id] = rowParams;
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
this.scenarios[scenario.id] = {
|
|
103
|
-
name: name,
|
|
104
|
-
parameters: parameters,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
}
|
|
27
|
+
this.events.addScenario(document);
|
|
109
28
|
}
|
|
110
|
-
/**
|
|
111
|
-
* Add attachment to storage
|
|
112
|
-
* @param {Attach} attachment
|
|
113
|
-
*/
|
|
114
29
|
addAttachment(attachment) {
|
|
115
|
-
|
|
116
|
-
if (!this.attachments[attachment.testStepId]) {
|
|
117
|
-
this.attachments[attachment.testStepId] = [];
|
|
118
|
-
}
|
|
119
|
-
this.attachments[attachment.testStepId]?.push({
|
|
120
|
-
file_name: this.getFileNameFromMediaType(attachment.mediaType),
|
|
121
|
-
mime_type: attachment.mediaType,
|
|
122
|
-
file_path: null,
|
|
123
|
-
content: attachment.body,
|
|
124
|
-
size: 0,
|
|
125
|
-
id: (0, uuid_1.v4)(),
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
if (attachment.testCaseStartedId) {
|
|
129
|
-
if (!this.attachments[attachment.testCaseStartedId]) {
|
|
130
|
-
this.attachments[attachment.testCaseStartedId] = [];
|
|
131
|
-
}
|
|
132
|
-
this.attachments[attachment.testCaseStartedId]?.push({
|
|
133
|
-
file_name: this.getFileNameFromMediaType(attachment.mediaType),
|
|
134
|
-
mime_type: attachment.mediaType,
|
|
135
|
-
file_path: null,
|
|
136
|
-
content: attachment.body,
|
|
137
|
-
size: 0,
|
|
138
|
-
id: (0, uuid_1.v4)(),
|
|
139
|
-
});
|
|
140
|
-
}
|
|
30
|
+
this.events.addAttachment(attachment);
|
|
141
31
|
}
|
|
142
|
-
/**
|
|
143
|
-
* Add test case to storage
|
|
144
|
-
* @param {TestCase} testCase
|
|
145
|
-
*/
|
|
146
32
|
addTestCase(testCase) {
|
|
147
|
-
this.
|
|
33
|
+
this.events.addTestCase(testCase);
|
|
148
34
|
}
|
|
149
|
-
/**
|
|
150
|
-
* Get all test cases
|
|
151
|
-
* @param {TestCaseStarted} testCaseStarted
|
|
152
|
-
*/
|
|
153
35
|
addTestCaseStarted(testCaseStarted) {
|
|
154
|
-
this.
|
|
155
|
-
|
|
156
|
-
this.
|
|
157
|
-
qase_javascript_commons_1.TestStatusEnum.passed;
|
|
158
|
-
if (this.profiler) {
|
|
159
|
-
this.profilerStepSnapshots[testCaseStarted.id] = this.profiler.getAllSteps().length;
|
|
160
|
-
}
|
|
36
|
+
this.events.addTestCaseStarted(testCaseStarted);
|
|
37
|
+
this.statusTracker.onTestStarted(testCaseStarted.id);
|
|
38
|
+
this.profilerTracker.onTestStart(testCaseStarted.id);
|
|
161
39
|
}
|
|
162
|
-
/**
|
|
163
|
-
* Add test case step to storage
|
|
164
|
-
* @param {TestStepFinished} testCaseStep
|
|
165
|
-
*/
|
|
166
40
|
addTestCaseStep(testCaseStep) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
let error = null;
|
|
170
|
-
if (testCaseStep.testStepResult.message) {
|
|
171
|
-
error = new Error(testCaseStep.testStepResult.message);
|
|
172
|
-
}
|
|
173
|
-
// Determine status based on error type
|
|
174
|
-
const newStatus = (0, qase_javascript_commons_1.determineTestStatus)(error, Storage.statusMap[testCaseStep.testStepResult.status]);
|
|
175
|
-
this.testCaseSteps[testCaseStep.testStepId] = testCaseStep;
|
|
176
|
-
if (newStatus !== qase_javascript_commons_1.TestStatusEnum.passed) {
|
|
177
|
-
if (testCaseStep.testStepResult.message) {
|
|
178
|
-
if (!this.testCaseStartedErrors[testCaseStep.testCaseStartedId]) {
|
|
179
|
-
this.testCaseStartedErrors[testCaseStep.testCaseStartedId] = [];
|
|
180
|
-
}
|
|
181
|
-
this.testCaseStartedErrors[testCaseStep.testCaseStartedId]?.push(testCaseStep.testStepResult.message);
|
|
182
|
-
}
|
|
183
|
-
if (oldStatus) {
|
|
184
|
-
if (oldStatus !== qase_javascript_commons_1.TestStatusEnum.failed && oldStatus !== qase_javascript_commons_1.TestStatusEnum.invalid) {
|
|
185
|
-
this.testCaseStartedResult[testCaseStep.testCaseStartedId] = newStatus;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
this.testCaseStartedResult[testCaseStep.testCaseStartedId] = newStatus;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
41
|
+
this.events.addTestStepFinished(testCaseStep);
|
|
42
|
+
this.statusTracker.applyStep(testCaseStep);
|
|
192
43
|
}
|
|
193
|
-
/**
|
|
194
|
-
* Convert test case to test result
|
|
195
|
-
* @param {TestCaseFinished} testCase
|
|
196
|
-
* @returns {undefined | TestResultType}
|
|
197
|
-
*/
|
|
198
44
|
convertTestCase(testCase) {
|
|
199
|
-
const tcs = this.
|
|
200
|
-
if (!tcs)
|
|
45
|
+
const tcs = this.events.getTestCaseStarted(testCase.testCaseStartedId);
|
|
46
|
+
if (!tcs)
|
|
201
47
|
return undefined;
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (!tc) {
|
|
48
|
+
const tc = this.events.getTestCase(tcs.testCaseId);
|
|
49
|
+
if (!tc)
|
|
205
50
|
return undefined;
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (!pickle) {
|
|
51
|
+
const pickle = this.events.getPickle(tc.pickleId);
|
|
52
|
+
if (!pickle)
|
|
209
53
|
return undefined;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (metadata.isIgnore) {
|
|
54
|
+
const metadata = tagParser_1.TagParser.parse(pickle.tags);
|
|
55
|
+
if (metadata.isIgnore)
|
|
213
56
|
return undefined;
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
let relations = null;
|
|
217
|
-
let params = {};
|
|
57
|
+
const error = this.statusTracker.getErrors(tcs.id);
|
|
58
|
+
const status = this.statusTracker.getStatus(testCase.testCaseStartedId);
|
|
218
59
|
const nodeId = pickle.astNodeIds[0];
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
relations = {
|
|
223
|
-
suite: {
|
|
224
|
-
data: suiteParts.map((suite) => ({
|
|
225
|
-
title: suite.trim(),
|
|
226
|
-
public_id: null,
|
|
227
|
-
})),
|
|
228
|
-
},
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
else if (nodeId != undefined && this.scenarios[nodeId] != undefined) {
|
|
232
|
-
// Otherwise, use feature name as suite
|
|
233
|
-
relations = {
|
|
234
|
-
suite: {
|
|
235
|
-
data: [
|
|
236
|
-
{
|
|
237
|
-
title: this.scenarios[nodeId]?.name ?? '',
|
|
238
|
-
public_id: null,
|
|
239
|
-
},
|
|
240
|
-
],
|
|
241
|
-
},
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
// Extract parameters from Gherkin examples
|
|
245
|
-
if (nodeId != undefined && this.scenarios[nodeId] != undefined) {
|
|
60
|
+
const scenario = nodeId !== undefined ? this.events.getScenario(nodeId) : undefined;
|
|
61
|
+
let scenarioParameters = {};
|
|
62
|
+
if (scenario) {
|
|
246
63
|
for (const id of pickle.astNodeIds) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
// Merge parameters from tags with parameters from Gherkin examples
|
|
253
|
-
// Parameters from tags take precedence over Gherkin examples
|
|
254
|
-
params = { ...params, ...metadata.parameters };
|
|
255
|
-
const steps = this.convertSteps(pickle.steps, tc);
|
|
256
|
-
// Collect profiler steps since this test case started
|
|
257
|
-
let profilerSteps = [];
|
|
258
|
-
if (this.profiler) {
|
|
259
|
-
const snapshot = this.profilerStepSnapshots[testCase.testCaseStartedId] ?? 0;
|
|
260
|
-
const allSteps = this.profiler.getAllSteps();
|
|
261
|
-
profilerSteps = allSteps.slice(snapshot);
|
|
262
|
-
delete this.profilerStepSnapshots[testCase.testCaseStartedId];
|
|
263
|
-
}
|
|
264
|
-
const hasProjectMapping = Object.keys(metadata.projectMapping).length > 0;
|
|
265
|
-
const result = {
|
|
266
|
-
attachments: this.attachments[testCase.testCaseStartedId] ?? [],
|
|
267
|
-
author: null,
|
|
268
|
-
execution: {
|
|
269
|
-
status: this.testCaseStartedResult[testCase.testCaseStartedId] ?? qase_javascript_commons_1.TestStatusEnum.passed,
|
|
270
|
-
start_time: tcs.timestamp.seconds,
|
|
271
|
-
end_time: testCase.timestamp.seconds,
|
|
272
|
-
duration: Math.abs(testCase.timestamp.seconds - tcs.timestamp.seconds) * 1000,
|
|
273
|
-
stacktrace: error?.stacktrace ?? null,
|
|
274
|
-
thread: null,
|
|
275
|
-
},
|
|
276
|
-
fields: metadata.fields,
|
|
277
|
-
message: error?.message ?? null,
|
|
278
|
-
muted: false,
|
|
279
|
-
params: params,
|
|
280
|
-
group_params: metadata.group_params,
|
|
281
|
-
relations: relations,
|
|
282
|
-
run_id: null,
|
|
283
|
-
signature: this.getSignature(pickle, metadata.ids, params),
|
|
284
|
-
steps: [...steps, ...profilerSteps],
|
|
285
|
-
testops_id: metadata.ids.length > 0 ? metadata.ids : null,
|
|
286
|
-
testops_project_mapping: hasProjectMapping ? metadata.projectMapping : null,
|
|
287
|
-
id: tcs.id,
|
|
288
|
-
title: metadata.title ?? pickle.name,
|
|
289
|
-
tags: metadata.tags,
|
|
290
|
-
};
|
|
291
|
-
return result;
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
|
-
* Convert test steps to test result steps
|
|
295
|
-
* @param {readonly PickleStep[]} steps
|
|
296
|
-
* @param {TestCase} testCase
|
|
297
|
-
* @returns {TestStepType[]}
|
|
298
|
-
* @private
|
|
299
|
-
*/
|
|
300
|
-
convertSteps(steps, testCase) {
|
|
301
|
-
const results = [];
|
|
302
|
-
for (const s of testCase.testSteps) {
|
|
303
|
-
const finished = this.testCaseSteps[s.id];
|
|
304
|
-
if (!finished) {
|
|
305
|
-
continue;
|
|
306
|
-
}
|
|
307
|
-
const step = steps.find((step) => step.id === s.pickleStepId);
|
|
308
|
-
if (!step) {
|
|
309
|
-
continue;
|
|
310
|
-
}
|
|
311
|
-
const result = {
|
|
312
|
-
id: s.id,
|
|
313
|
-
step_type: qase_javascript_commons_1.StepType.GHERKIN,
|
|
314
|
-
data: {
|
|
315
|
-
keyword: step.text,
|
|
316
|
-
name: step.text,
|
|
317
|
-
line: 0,
|
|
318
|
-
},
|
|
319
|
-
execution: {
|
|
320
|
-
status: Storage.stepStatusMap[finished.testStepResult.status],
|
|
321
|
-
start_time: null,
|
|
322
|
-
end_time: null,
|
|
323
|
-
duration: finished.testStepResult.duration.seconds * 1000,
|
|
324
|
-
},
|
|
325
|
-
attachments: this.attachments[s.id] ?? [],
|
|
326
|
-
steps: [],
|
|
327
|
-
parent_id: null,
|
|
328
|
-
};
|
|
329
|
-
results.push(result);
|
|
330
|
-
}
|
|
331
|
-
return results;
|
|
332
|
-
}
|
|
333
|
-
/**
|
|
334
|
-
* @type {Record<TestStepResultStatus, TestStatusEnum>}
|
|
335
|
-
*/
|
|
336
|
-
static statusMap = {
|
|
337
|
-
[cucumber_1.Status.PASSED]: qase_javascript_commons_1.TestStatusEnum.passed,
|
|
338
|
-
[cucumber_1.Status.FAILED]: qase_javascript_commons_1.TestStatusEnum.failed,
|
|
339
|
-
[cucumber_1.Status.SKIPPED]: qase_javascript_commons_1.TestStatusEnum.skipped,
|
|
340
|
-
[cucumber_1.Status.AMBIGUOUS]: qase_javascript_commons_1.TestStatusEnum.invalid,
|
|
341
|
-
[cucumber_1.Status.PENDING]: qase_javascript_commons_1.TestStatusEnum.skipped,
|
|
342
|
-
[cucumber_1.Status.UNDEFINED]: qase_javascript_commons_1.TestStatusEnum.skipped,
|
|
343
|
-
[cucumber_1.Status.UNKNOWN]: qase_javascript_commons_1.TestStatusEnum.skipped,
|
|
344
|
-
};
|
|
345
|
-
/**
|
|
346
|
-
* @type {Record<TestStepResultStatus, StepStatusEnum>}
|
|
347
|
-
*/
|
|
348
|
-
static stepStatusMap = {
|
|
349
|
-
[cucumber_1.Status.PASSED]: qase_javascript_commons_1.StepStatusEnum.passed,
|
|
350
|
-
[cucumber_1.Status.FAILED]: qase_javascript_commons_1.StepStatusEnum.failed,
|
|
351
|
-
[cucumber_1.Status.SKIPPED]: qase_javascript_commons_1.StepStatusEnum.skipped,
|
|
352
|
-
[cucumber_1.Status.AMBIGUOUS]: qase_javascript_commons_1.StepStatusEnum.failed,
|
|
353
|
-
[cucumber_1.Status.PENDING]: qase_javascript_commons_1.StepStatusEnum.skipped,
|
|
354
|
-
[cucumber_1.Status.UNDEFINED]: qase_javascript_commons_1.StepStatusEnum.skipped,
|
|
355
|
-
[cucumber_1.Status.UNKNOWN]: qase_javascript_commons_1.StepStatusEnum.skipped,
|
|
356
|
-
};
|
|
357
|
-
parseTags(tags) {
|
|
358
|
-
const tagNames = tags.map((t) => t.name);
|
|
359
|
-
const { legacyIds, projectMapping } = (0, qase_javascript_commons_1.parseProjectMappingFromTags)(tagNames);
|
|
360
|
-
const metadata = {
|
|
361
|
-
ids: [...legacyIds],
|
|
362
|
-
projectMapping: { ...projectMapping },
|
|
363
|
-
fields: {},
|
|
364
|
-
title: null,
|
|
365
|
-
isIgnore: false,
|
|
366
|
-
parameters: {},
|
|
367
|
-
group_params: {},
|
|
368
|
-
suite: null,
|
|
369
|
-
tags: [],
|
|
370
|
-
};
|
|
371
|
-
for (const tag of tags) {
|
|
372
|
-
if (qaseIdRegExp.test(tag.name)) {
|
|
373
|
-
metadata.ids.push(Number(tag.name.replace(/^@[Qq]-?/, '')));
|
|
374
|
-
continue;
|
|
375
|
-
}
|
|
376
|
-
if (newQaseIdRegExp.test(tag.name)) {
|
|
377
|
-
const idsStr = tag.name.replace(/^@[Qq]ase[Ii][Dd]=/, '');
|
|
378
|
-
metadata.ids.push(...idsStr.split(',').map((s) => Number(s.trim())));
|
|
379
|
-
continue;
|
|
380
|
-
}
|
|
381
|
-
if (qaseTitleRegExp.test(tag.name)) {
|
|
382
|
-
metadata.title = tag.name.replace(/^@[Qq]ase[Tt]itle=/, '');
|
|
383
|
-
continue;
|
|
384
|
-
}
|
|
385
|
-
if (qaseFieldsRegExp.test(tag.name)) {
|
|
386
|
-
const value = tag.name.replace(/^@[Qq]ase[Ff]ields=/, '');
|
|
387
|
-
try {
|
|
388
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
389
|
-
const record = JSON.parse(this.normalizeJsonString(value));
|
|
390
|
-
metadata.fields = { ...metadata.fields, ...record };
|
|
391
|
-
}
|
|
392
|
-
catch (e) {
|
|
393
|
-
// do nothing
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
if (qaseParametersRegExp.test(tag.name)) {
|
|
397
|
-
const value = tag.name.replace(/^@[Qq]ase[Pp]arameters=/, '');
|
|
398
|
-
try {
|
|
399
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
400
|
-
const record = JSON.parse(this.normalizeJsonString(value));
|
|
401
|
-
metadata.parameters = { ...metadata.parameters, ...record };
|
|
402
|
-
}
|
|
403
|
-
catch (e) {
|
|
404
|
-
// do nothing
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
if (qaseGroupParametersRegExp.test(tag.name)) {
|
|
408
|
-
const value = tag.name.replace(/^@[Qq]ase[Gg]roup[Pp]arameters=/, '');
|
|
409
|
-
try {
|
|
410
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
411
|
-
const record = JSON.parse(this.normalizeJsonString(value));
|
|
412
|
-
metadata.group_params = { ...metadata.group_params, ...record };
|
|
64
|
+
const params = scenario.parameters[id];
|
|
65
|
+
if (params) {
|
|
66
|
+
scenarioParameters = { ...scenarioParameters, ...params };
|
|
413
67
|
}
|
|
414
|
-
catch (e) {
|
|
415
|
-
// do nothing
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
if (qaseSuiteRegExp.test(tag.name)) {
|
|
419
|
-
metadata.suite = tag.name.replace(/^@[Qq]ase[Ss]uite=/, '');
|
|
420
|
-
continue;
|
|
421
|
-
}
|
|
422
|
-
if (qaseTagsRegExp.test(tag.name)) {
|
|
423
|
-
const value = tag.name.replace(/^@[Qq]ase[Tt]ags=/, '');
|
|
424
|
-
const parsedTags = value.split(',').map(t => t.trim()).filter(t => t.length > 0);
|
|
425
|
-
metadata.tags.push(...parsedTags);
|
|
426
|
-
continue;
|
|
427
|
-
}
|
|
428
|
-
if (qaseIgnoreRegExp.test(tag.name)) {
|
|
429
|
-
metadata.isIgnore = true;
|
|
430
68
|
}
|
|
431
69
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const error = new qase_javascript_commons_1.CompoundError();
|
|
449
|
-
testErrors.forEach((message) => {
|
|
450
|
-
error.addMessage(message);
|
|
451
|
-
error.addStacktrace(message);
|
|
70
|
+
const steps = stepConverter_1.StepConverter.convert(pickle.steps, tc, this.events.getAllTestStepFinished(), (stepId) => this.events.getAttachments(stepId));
|
|
71
|
+
const profilerSteps = this.profilerTracker.getEvents(testCase.testCaseStartedId);
|
|
72
|
+
this.profilerTracker.reset(testCase.testCaseStartedId);
|
|
73
|
+
return resultBuilder_1.ResultBuilder.build({
|
|
74
|
+
testCaseFinished: testCase,
|
|
75
|
+
testCaseStarted: tcs,
|
|
76
|
+
testCase: tc,
|
|
77
|
+
pickle,
|
|
78
|
+
metadata,
|
|
79
|
+
status,
|
|
80
|
+
error,
|
|
81
|
+
steps,
|
|
82
|
+
profilerSteps,
|
|
83
|
+
attachments: this.events.getAttachments(testCase.testCaseStartedId),
|
|
84
|
+
scenarioName: scenario?.name,
|
|
85
|
+
scenarioParameters,
|
|
452
86
|
});
|
|
453
|
-
return error;
|
|
454
|
-
}
|
|
455
|
-
/**
|
|
456
|
-
* Normalize JSON string by converting single quotes to double quotes
|
|
457
|
-
* This allows parsing JSON-like strings with single quotes
|
|
458
|
-
* @param {string} jsonString
|
|
459
|
-
* @returns {string}
|
|
460
|
-
* @private
|
|
461
|
-
*/
|
|
462
|
-
normalizeJsonString(jsonString) {
|
|
463
|
-
// If the string contains single quotes, convert them to double quotes
|
|
464
|
-
// This handles cases like {'key':'value'} which should be {"key":"value"}
|
|
465
|
-
if (jsonString.includes("'")) {
|
|
466
|
-
return jsonString.replace(/'/g, '"');
|
|
467
|
-
}
|
|
468
|
-
// If no single quotes, return as-is (already valid JSON or will fail with proper error)
|
|
469
|
-
return jsonString;
|
|
470
|
-
}
|
|
471
|
-
getFileNameFromMediaType(mediaType) {
|
|
472
|
-
const extensions = {
|
|
473
|
-
'text/plain': 'txt',
|
|
474
|
-
'application/json': 'json',
|
|
475
|
-
'image/png': 'png',
|
|
476
|
-
'image/jpeg': 'jpg',
|
|
477
|
-
'image/gif': 'gif',
|
|
478
|
-
'text/html': 'html',
|
|
479
|
-
'application/pdf': 'pdf',
|
|
480
|
-
'application/xml': 'xml',
|
|
481
|
-
'application/zip': 'zip',
|
|
482
|
-
'application/msword': 'doc',
|
|
483
|
-
'application/vnd.ms-excel': 'xls',
|
|
484
|
-
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx',
|
|
485
|
-
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xlsx',
|
|
486
|
-
};
|
|
487
|
-
const extension = extensions[mediaType];
|
|
488
|
-
if (extension) {
|
|
489
|
-
return `file.${extension}`;
|
|
490
|
-
}
|
|
491
|
-
else {
|
|
492
|
-
return 'file';
|
|
493
|
-
}
|
|
494
87
|
}
|
|
495
88
|
}
|
|
496
89
|
exports.Storage = Storage;
|