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 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,6 @@
1
+ import { PickleTag } from '@cucumber/messages';
2
+ import { TestMetadata } from '../models';
3
+ export declare class TagParser {
4
+ static parse(tags: readonly PickleTag[]): TestMetadata;
5
+ static normalizeJsonString(jsonString: string): string;
6
+ }
@@ -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;
@@ -18,11 +18,6 @@ export declare class CucumberQaseReporter extends Formatter {
18
18
  * @private
19
19
  */
20
20
  private reporter;
21
- /**
22
- * @type {NetworkProfiler | null}
23
- * @private
24
- */
25
- private profiler;
26
21
  /**
27
22
  * @type {EventEmitter}
28
23
  * @private
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
- this.profiler = new profilers_1.NetworkProfiler({
45
+ profiler = new profilers_1.NetworkProfiler({
50
46
  skipDomains: composedOptions.networkProfiler?.skip_domains,
51
47
  trackOnFail: composedOptions.networkProfiler?.track_on_fail,
52
48
  });
53
- this.profiler.enable();
49
+ profiler.enable();
54
50
  }
55
51
  this.eventBroadcaster = formatterOptions.eventBroadcaster;
56
- this.storage = new storage_1.Storage(this.profiler);
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.profiler?.restore();
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 { Status } from '@cucumber/cucumber';
6
- type TestStepResultStatus = (typeof Status)[keyof typeof Status];
5
+ import { TestStepResultStatus } from './modules/statusMaps';
7
6
  export declare class Storage {
8
- /**
9
- * @type {NetworkProfiler | null}
10
- * @private
11
- */
12
- private profiler;
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 qase_javascript_commons_1 = require("qase-javascript-commons");
5
- const cucumber_1 = require("@cucumber/cucumber");
6
- const uuid_1 = require("uuid");
7
- const qaseIdRegExp = /^@[Qq]-?(\d+)$/;
8
- const newQaseIdRegExp = /^@[Qq]ase[Ii][Dd]=(\d+(?:,\s*\d+)*)$/;
9
- const qaseTitleRegExp = /^@[Qq]ase[Tt]itle=(.+)$/;
10
- const qaseFieldsRegExp = /^@[Qq]ase[Ff]ields=(.+)$/;
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
- * @type {NetworkProfiler | null}
19
- * @private
20
- */
21
- profiler;
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.profiler = profiler;
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.pickles[pickle.id] = pickle;
24
+ this.events.addPickle(pickle);
76
25
  }
77
- /**
78
- * Add scenario to storage
79
- * @param {GherkinDocument} document
80
- */
81
26
  addScenario(document) {
82
- if (document.feature) {
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
- if (attachment.testStepId) {
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.testCases[testCase.id] = testCase;
33
+ this.events.addTestCase(testCase);
148
34
  }
149
- /**
150
- * Get all test cases
151
- * @param {TestCaseStarted} testCaseStarted
152
- */
153
35
  addTestCaseStarted(testCaseStarted) {
154
- this.testCaseStarts[testCaseStarted.id] =
155
- testCaseStarted;
156
- this.testCaseStartedResult[testCaseStarted.id] =
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
- const oldStatus = this.testCaseStartedResult[testCaseStep.testCaseStartedId];
168
- // Create error object for status determination
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.testCaseStarts[testCase.testCaseStartedId];
200
- if (!tcs) {
45
+ const tcs = this.events.getTestCaseStarted(testCase.testCaseStartedId);
46
+ if (!tcs)
201
47
  return undefined;
202
- }
203
- const tc = this.testCases[tcs.testCaseId];
204
- if (!tc) {
48
+ const tc = this.events.getTestCase(tcs.testCaseId);
49
+ if (!tc)
205
50
  return undefined;
206
- }
207
- const pickle = this.pickles[tc.pickleId];
208
- if (!pickle) {
51
+ const pickle = this.events.getPickle(tc.pickleId);
52
+ if (!pickle)
209
53
  return undefined;
210
- }
211
- const metadata = this.parseTags(pickle.tags);
212
- if (metadata.isIgnore) {
54
+ const metadata = tagParser_1.TagParser.parse(pickle.tags);
55
+ if (metadata.isIgnore)
213
56
  return undefined;
214
- }
215
- const error = this.getError(tcs.id);
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
- // If suite is specified in metadata, use it (split by tab for sub-suites)
220
- if (metadata.suite) {
221
- const suiteParts = metadata.suite.split('\t').filter(part => part.trim().length > 0);
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
- if (this.scenarios[nodeId]?.parameters[id] != undefined) {
248
- params = { ...params, ...this.scenarios[nodeId]?.parameters[id] };
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
- return metadata;
433
- }
434
- /**
435
- * @param {Pickle} pickle
436
- * @param {number[]} ids
437
- * @param {Record<string, string>} parameters
438
- * @private
439
- */
440
- getSignature(pickle, ids, parameters = {}) {
441
- return (0, qase_javascript_commons_1.generateSignature)(ids, [...pickle.uri.split('/'), pickle.name], parameters);
442
- }
443
- getError(testCaseId) {
444
- const testErrors = this.testCaseStartedErrors[testCaseId];
445
- if (!testErrors) {
446
- return undefined;
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cucumberjs-qase-reporter",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "Qase TMS CucumberJS Reporter",
5
5
  "homepage": "https://github.com/qase-tms/qase-javascript",
6
6
  "main": "./dist/index.js",