cypress-qase-reporter 2.2.3 → 2.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -1
- package/changelog.md +12 -0
- package/dist/cucumber.d.ts +1 -0
- package/dist/cucumber.js +25 -0
- package/dist/fileSearcher.d.ts +11 -0
- package/dist/fileSearcher.js +63 -0
- package/dist/index.cjs.d.ts +1 -0
- package/dist/index.cjs.js +3 -1
- package/dist/metadata/manager.d.ts +1 -0
- package/dist/metadata/manager.js +19 -1
- package/dist/metadata/models.d.ts +1 -0
- package/dist/metadata.js +6 -0
- package/dist/reporter.d.ts +10 -8
- package/dist/reporter.js +105 -49
- package/dist/utils/tagParser.d.ts +7 -0
- package/dist/utils/tagParser.js +61 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -165,7 +165,7 @@ the [Configuration reference](../qase-javascript-commons/README.md#configuration
|
|
|
165
165
|
|
|
166
166
|
Example `cypress.config.js` config:
|
|
167
167
|
|
|
168
|
-
```
|
|
168
|
+
```javascript
|
|
169
169
|
import cypress from 'cypress';
|
|
170
170
|
|
|
171
171
|
module.exports = cypress.defineConfig({
|
|
@@ -211,6 +211,66 @@ module.exports = cypress.defineConfig({
|
|
|
211
211
|
Check out the example of configuration for multiple reporters in the
|
|
212
212
|
[demo project](../examples/cypress/cypress.config.js).
|
|
213
213
|
|
|
214
|
+
If you use Cucumber, you need to add the following configuration in `cypress.config.js`:
|
|
215
|
+
|
|
216
|
+
```javascript
|
|
217
|
+
import cypress from 'cypress';
|
|
218
|
+
|
|
219
|
+
const cucumber = require('cypress-cucumber-preprocessor').default;
|
|
220
|
+
|
|
221
|
+
module.exports = cypress.defineConfig({
|
|
222
|
+
reporter: 'cypress-multi-reporters',
|
|
223
|
+
reporterOptions: {
|
|
224
|
+
reporterEnabled: 'cypress-mochawesome-reporter, cypress-qase-reporter',
|
|
225
|
+
cypressMochawesomeReporterReporterOptions: {
|
|
226
|
+
charts: true,
|
|
227
|
+
},
|
|
228
|
+
cypressQaseReporterReporterOptions: {
|
|
229
|
+
debug: true,
|
|
230
|
+
|
|
231
|
+
testops: {
|
|
232
|
+
api: {
|
|
233
|
+
token: 'api_key',
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
project: 'project_code',
|
|
237
|
+
uploadAttachments: true,
|
|
238
|
+
|
|
239
|
+
run: {
|
|
240
|
+
complete: true,
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
framework: {
|
|
245
|
+
cypress: {
|
|
246
|
+
screenshotsFolder: 'cypress/screenshots',
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
video: false,
|
|
252
|
+
e2e: {
|
|
253
|
+
setupNodeEvents(on, config) {
|
|
254
|
+
on('file:preprocessor', cucumber());
|
|
255
|
+
require('cypress-qase-reporter/plugin')(on, config);
|
|
256
|
+
require('cypress-qase-reporter/metadata')(on);
|
|
257
|
+
},
|
|
258
|
+
specPattern: 'cypress/e2e/*.feature',
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
And add the following lines in `support/e2e.js` file:
|
|
264
|
+
|
|
265
|
+
```javascript
|
|
266
|
+
import { enableCucumberSupport } from "cypress-qase-reporter";
|
|
267
|
+
|
|
268
|
+
enableCucumberSupport();
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Check out the example of configuration for multiple reporters in the
|
|
272
|
+
[demo project](../examples/cypressCucumber/cypress.config.js).
|
|
273
|
+
|
|
214
274
|
## Requirements
|
|
215
275
|
|
|
216
276
|
We maintain the reporter on [LTS versions of Node](https://nodejs.org/en/about/releases/).
|
package/changelog.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
# cypress-qase-reporter@2.2.5
|
|
2
|
+
|
|
3
|
+
## What's new
|
|
4
|
+
|
|
5
|
+
Support Cucumber tests in Cypress with the `cypress-cucumber-preprocessor` plugin.
|
|
6
|
+
|
|
7
|
+
# cypress-qase-reporter@2.2.4
|
|
8
|
+
|
|
9
|
+
## What's new
|
|
10
|
+
|
|
11
|
+
Fixed an issue with screenshots not being uploaded to Qase for failed tests.
|
|
12
|
+
|
|
1
13
|
# cypress-qase-reporter@2.2.3
|
|
2
14
|
|
|
3
15
|
## What's new
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const enableCucumberSupport: () => void;
|
package/dist/cucumber.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enableCucumberSupport = void 0;
|
|
4
|
+
const CUCUMBER_TASK_NAME = 'qaseCucumberStepStart';
|
|
5
|
+
const enableCucumberSupport = () => {
|
|
6
|
+
registerCypressEventListeners();
|
|
7
|
+
};
|
|
8
|
+
exports.enableCucumberSupport = enableCucumberSupport;
|
|
9
|
+
const registerCypressEventListeners = () => {
|
|
10
|
+
Cypress.on('log:added', handleLogAdded);
|
|
11
|
+
};
|
|
12
|
+
const handleLogAdded = (_, entry) => {
|
|
13
|
+
if (isCucumberStep(entry)) {
|
|
14
|
+
processCucumberStep(entry);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const isCucumberStep = ({ attributes: { name, event, instrument } }) => {
|
|
18
|
+
return instrument === 'command' && !event && name === 'step';
|
|
19
|
+
};
|
|
20
|
+
const processCucumberStep = ({ attributes: { displayName, message } }) => {
|
|
21
|
+
sendTaskMessage(`${displayName ?? ''} ${message}`);
|
|
22
|
+
};
|
|
23
|
+
const sendTaskMessage = (message) => {
|
|
24
|
+
cy.task(CUCUMBER_TASK_NAME, message, { log: false });
|
|
25
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class FileSearcher {
|
|
2
|
+
/**
|
|
3
|
+
* Finds all files in the given directory and its subdirectories
|
|
4
|
+
* that were created after the specified time.
|
|
5
|
+
*
|
|
6
|
+
* @param folderPath Relative path to the folder.
|
|
7
|
+
* @param time Time threshold as a Date object.
|
|
8
|
+
* @returns Array of absolute paths to the matching files.
|
|
9
|
+
*/
|
|
10
|
+
static findFilesBeforeTime(folderPath: string, time: Date): string[];
|
|
11
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.FileSearcher = void 0;
|
|
27
|
+
const fs = __importStar(require("fs"));
|
|
28
|
+
const path = __importStar(require("path"));
|
|
29
|
+
class FileSearcher {
|
|
30
|
+
/**
|
|
31
|
+
* Finds all files in the given directory and its subdirectories
|
|
32
|
+
* that were created after the specified time.
|
|
33
|
+
*
|
|
34
|
+
* @param folderPath Relative path to the folder.
|
|
35
|
+
* @param time Time threshold as a Date object.
|
|
36
|
+
* @returns Array of absolute paths to the matching files.
|
|
37
|
+
*/
|
|
38
|
+
static findFilesBeforeTime(folderPath, time) {
|
|
39
|
+
const absolutePath = path.resolve(process.cwd(), folderPath);
|
|
40
|
+
const result = [];
|
|
41
|
+
const searchFiles = (dir) => {
|
|
42
|
+
if (!fs.existsSync(dir)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
const entryPath = path.join(dir, entry.name);
|
|
48
|
+
if (entry.isDirectory()) {
|
|
49
|
+
searchFiles(entryPath);
|
|
50
|
+
}
|
|
51
|
+
else if (entry.isFile()) {
|
|
52
|
+
const stats = fs.statSync(entryPath);
|
|
53
|
+
if (stats.birthtime > time) {
|
|
54
|
+
result.push(entryPath);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
searchFiles(absolutePath);
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.FileSearcher = FileSearcher;
|
package/dist/index.cjs.d.ts
CHANGED
package/dist/index.cjs.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.qase = void 0;
|
|
3
|
+
exports.enableCucumberSupport = exports.qase = void 0;
|
|
4
4
|
var mocha_1 = require("./mocha");
|
|
5
5
|
Object.defineProperty(exports, "qase", { enumerable: true, get: function () { return mocha_1.qase; } });
|
|
6
|
+
var cucumber_1 = require("./cucumber");
|
|
7
|
+
Object.defineProperty(exports, "enableCucumberSupport", { enumerable: true, get: function () { return cucumber_1.enableCucumberSupport; } });
|
|
@@ -6,6 +6,7 @@ export declare class MetadataManager {
|
|
|
6
6
|
static setIgnore(): void;
|
|
7
7
|
static addStepStart(name: string): void;
|
|
8
8
|
static addStepEnd(status: string): void;
|
|
9
|
+
static addCucumberStep(name: string): void;
|
|
9
10
|
static addAttach(attach: Attach): void;
|
|
10
11
|
static setSuite(suite: string): void;
|
|
11
12
|
static setComment(comment: string): void;
|
package/dist/metadata/manager.js
CHANGED
|
@@ -10,6 +10,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
10
10
|
const qase_javascript_commons_1 = require("qase-javascript-commons");
|
|
11
11
|
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
|
12
12
|
class MetadataManager {
|
|
13
|
+
static metadataPath = path_1.default.resolve(__dirname, 'qaseMetadata');
|
|
13
14
|
static getMetadata() {
|
|
14
15
|
if (!this.isExists()) {
|
|
15
16
|
return undefined;
|
|
@@ -23,6 +24,7 @@ class MetadataManager {
|
|
|
23
24
|
suite: undefined,
|
|
24
25
|
comment: undefined,
|
|
25
26
|
steps: [],
|
|
27
|
+
cucumberSteps: [],
|
|
26
28
|
currentStepId: undefined,
|
|
27
29
|
firstStepName: undefined,
|
|
28
30
|
attachments: [],
|
|
@@ -70,6 +72,23 @@ class MetadataManager {
|
|
|
70
72
|
metadata.currentStepId = parentId;
|
|
71
73
|
this.setMetadata(metadata);
|
|
72
74
|
}
|
|
75
|
+
static addCucumberStep(name) {
|
|
76
|
+
const metadata = this.getMetadata() ?? {};
|
|
77
|
+
if (metadata.firstStepName === name) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (!metadata.cucumberSteps) {
|
|
81
|
+
metadata.cucumberSteps = [];
|
|
82
|
+
}
|
|
83
|
+
const id = (0, uuid_1.v4)();
|
|
84
|
+
const parentId = metadata.currentStepId ?? undefined;
|
|
85
|
+
metadata.cucumberSteps.push({ timestamp: Date.now(), name, id: id, parentId: parentId });
|
|
86
|
+
metadata.currentStepId = id;
|
|
87
|
+
if (!metadata.firstStepName) {
|
|
88
|
+
metadata.firstStepName = name;
|
|
89
|
+
}
|
|
90
|
+
this.setMetadata(metadata);
|
|
91
|
+
}
|
|
73
92
|
static addAttach(attach) {
|
|
74
93
|
const metadata = this.getMetadata() ?? {};
|
|
75
94
|
if (!metadata.attachments) {
|
|
@@ -189,4 +208,3 @@ class MetadataManager {
|
|
|
189
208
|
}
|
|
190
209
|
}
|
|
191
210
|
exports.MetadataManager = MetadataManager;
|
|
192
|
-
MetadataManager.metadataPath = path_1.default.resolve(__dirname, 'qaseMetadata');
|
|
@@ -9,6 +9,7 @@ export interface Metadata {
|
|
|
9
9
|
suite?: string | undefined;
|
|
10
10
|
comment?: string | undefined;
|
|
11
11
|
steps?: (StepStart | StepEnd)[];
|
|
12
|
+
cucumberSteps?: StepStart[];
|
|
12
13
|
currentStepId?: string | undefined;
|
|
13
14
|
firstStepName?: string | undefined;
|
|
14
15
|
attachments?: Attachment[];
|
package/dist/metadata.js
CHANGED
package/dist/reporter.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="cypress" />
|
|
2
2
|
import { MochaOptions, reporters, Runner } from 'mocha';
|
|
3
|
-
import { ConfigLoader,
|
|
3
|
+
import { ConfigLoader, FrameworkOptionsType, TestStatusEnum } from 'qase-javascript-commons';
|
|
4
4
|
import { ReporterOptionsType } from './options';
|
|
5
5
|
type CypressState = 'failed' | 'passed' | 'pending';
|
|
6
6
|
export type CypressQaseOptionsType = Omit<MochaOptions, 'reporterOptions'> & {
|
|
@@ -25,13 +25,6 @@ export declare class CypressQaseReporter extends reporters.Base {
|
|
|
25
25
|
* @private
|
|
26
26
|
*/
|
|
27
27
|
private static getCaseId;
|
|
28
|
-
/**
|
|
29
|
-
* @param {number[]} ids
|
|
30
|
-
* @param {string} dir
|
|
31
|
-
* @returns {Attachment[]}
|
|
32
|
-
* @private
|
|
33
|
-
*/
|
|
34
|
-
private static findAttachments;
|
|
35
28
|
/**
|
|
36
29
|
* @type {string | undefined}
|
|
37
30
|
* @private
|
|
@@ -42,6 +35,7 @@ export declare class CypressQaseReporter extends reporters.Base {
|
|
|
42
35
|
* @private
|
|
43
36
|
*/
|
|
44
37
|
private reporter;
|
|
38
|
+
private testBeginTime;
|
|
45
39
|
private options;
|
|
46
40
|
/**
|
|
47
41
|
* @param {Runner} runner
|
|
@@ -65,6 +59,7 @@ export declare class CypressQaseReporter extends reporters.Base {
|
|
|
65
59
|
* @private
|
|
66
60
|
*/
|
|
67
61
|
private getSignature;
|
|
62
|
+
private getTestFileName;
|
|
68
63
|
/**
|
|
69
64
|
* @param {Suite} suite
|
|
70
65
|
* @private
|
|
@@ -76,6 +71,13 @@ export declare class CypressQaseReporter extends reporters.Base {
|
|
|
76
71
|
* @private
|
|
77
72
|
*/
|
|
78
73
|
private removeQaseIdsFromTitle;
|
|
74
|
+
/**
|
|
75
|
+
* Extracts numbers from @qaseid tags, regardless of case.
|
|
76
|
+
* @param tags - An array of tags to process.
|
|
77
|
+
* @returns An array of numbers extracted from the tags.
|
|
78
|
+
*/
|
|
79
|
+
private extractQaseIds;
|
|
80
|
+
private convertCypressMessages;
|
|
79
81
|
private getSteps;
|
|
80
82
|
}
|
|
81
83
|
export {};
|
package/dist/reporter.js
CHANGED
|
@@ -9,15 +9,28 @@ const uuid_1 = require("uuid");
|
|
|
9
9
|
const child_process_1 = require("child_process");
|
|
10
10
|
const mocha_1 = require("mocha");
|
|
11
11
|
const qase_javascript_commons_1 = require("qase-javascript-commons");
|
|
12
|
-
const traverse_dir_1 = require("./utils/traverse-dir");
|
|
13
12
|
const configSchema_1 = require("./configSchema");
|
|
14
13
|
const manager_1 = require("./metadata/manager");
|
|
15
|
-
const
|
|
14
|
+
const fileSearcher_1 = require("./fileSearcher");
|
|
15
|
+
const tagParser_1 = require("./utils/tagParser");
|
|
16
|
+
const { EVENT_TEST_FAIL, EVENT_TEST_PASS, EVENT_TEST_PENDING, EVENT_RUN_END, EVENT_TEST_BEGIN, } = mocha_1.Runner.constants;
|
|
16
17
|
/**
|
|
17
18
|
* @class CypressQaseReporter
|
|
18
19
|
* @extends reporters.Base
|
|
19
20
|
*/
|
|
20
21
|
class CypressQaseReporter extends mocha_1.reporters.Base {
|
|
22
|
+
/**
|
|
23
|
+
* @type {RegExp}
|
|
24
|
+
*/
|
|
25
|
+
static qaseIdRegExp = /\(Qase ID:? ([\d,]+)\)/;
|
|
26
|
+
/**
|
|
27
|
+
* @type {Record<CypressState, TestStatusEnum>}
|
|
28
|
+
*/
|
|
29
|
+
static statusMap = {
|
|
30
|
+
failed: qase_javascript_commons_1.TestStatusEnum.failed,
|
|
31
|
+
passed: qase_javascript_commons_1.TestStatusEnum.passed,
|
|
32
|
+
pending: qase_javascript_commons_1.TestStatusEnum.skipped,
|
|
33
|
+
};
|
|
21
34
|
/**
|
|
22
35
|
* @param {string} title
|
|
23
36
|
* @returns {number[]}
|
|
@@ -28,31 +41,17 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
|
|
|
28
41
|
return ids ? ids.split(',').map((id) => Number(id)) : [];
|
|
29
42
|
}
|
|
30
43
|
/**
|
|
31
|
-
* @
|
|
32
|
-
* @param {string} dir
|
|
33
|
-
* @returns {Attachment[]}
|
|
44
|
+
* @type {string | undefined}
|
|
34
45
|
* @private
|
|
35
46
|
*/
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
id: (0, uuid_1.v4)(),
|
|
45
|
-
mime_type: '', size: 0,
|
|
46
|
-
file_name: path_1.default.basename(filePath),
|
|
47
|
-
file_path: filePath,
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
catch (error) { /* ignore */
|
|
53
|
-
}
|
|
54
|
-
return attachments;
|
|
55
|
-
}
|
|
47
|
+
screenshotsFolder;
|
|
48
|
+
/**
|
|
49
|
+
* @type {ReporterInterface}
|
|
50
|
+
* @private
|
|
51
|
+
*/
|
|
52
|
+
reporter;
|
|
53
|
+
testBeginTime = Date.now();
|
|
54
|
+
options;
|
|
56
55
|
/**
|
|
57
56
|
* @param {Runner} runner
|
|
58
57
|
* @param {CypressQaseOptionsType} options
|
|
@@ -81,6 +80,10 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
|
|
|
81
80
|
runner.on(EVENT_TEST_PASS, (test) => this.addTestResult(test));
|
|
82
81
|
runner.on(EVENT_TEST_PENDING, (test) => this.addTestResult(test));
|
|
83
82
|
runner.on(EVENT_TEST_FAIL, (test) => this.addTestResult(test));
|
|
83
|
+
runner.on(EVENT_TEST_BEGIN, () => {
|
|
84
|
+
this.testBeginTime = Date.now();
|
|
85
|
+
manager_1.MetadataManager.clear();
|
|
86
|
+
});
|
|
84
87
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
85
88
|
runner.once(EVENT_RUN_END, () => {
|
|
86
89
|
const results = this.reporter.getResults();
|
|
@@ -104,10 +107,19 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
|
|
|
104
107
|
return;
|
|
105
108
|
}
|
|
106
109
|
const ids = CypressQaseReporter.getCaseId(test.title);
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
const testFileName = this.getTestFileName(test);
|
|
111
|
+
const files = this.screenshotsFolder ?
|
|
112
|
+
fileSearcher_1.FileSearcher.findFilesBeforeTime(path_1.default.join(this.screenshotsFolder, testFileName), new Date(this.testBeginTime))
|
|
113
|
+
: [];
|
|
114
|
+
const attachments = files.map((file) => ({
|
|
115
|
+
content: '',
|
|
116
|
+
id: (0, uuid_1.v4)(),
|
|
117
|
+
mime_type: 'image/png',
|
|
118
|
+
size: 0,
|
|
119
|
+
file_name: path_1.default.basename(file),
|
|
120
|
+
file_path: file,
|
|
121
|
+
}));
|
|
122
|
+
attachments.push(...(metadata?.attachments ?? []));
|
|
111
123
|
let relations = {};
|
|
112
124
|
if (test.parent !== undefined) {
|
|
113
125
|
const data = [];
|
|
@@ -135,15 +147,21 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
|
|
|
135
147
|
},
|
|
136
148
|
};
|
|
137
149
|
}
|
|
138
|
-
let message =
|
|
139
|
-
if (metadata?.comment) {
|
|
140
|
-
message = metadata.comment;
|
|
141
|
-
}
|
|
150
|
+
let message = metadata?.comment ?? '';
|
|
142
151
|
if (test.err?.message) {
|
|
143
|
-
|
|
144
|
-
|
|
152
|
+
message += message ? `\n\n${test.err.message}` : test.err.message;
|
|
153
|
+
}
|
|
154
|
+
const steps = metadata?.steps ? this.getSteps(metadata.steps, metadata.stepAttachments ?? {}) : [];
|
|
155
|
+
// support for cucumber steps and metadata
|
|
156
|
+
if (metadata?.cucumberSteps && metadata.cucumberSteps.length > 0) {
|
|
157
|
+
steps.push(...this.convertCypressMessages(metadata.cucumberSteps, test.state ?? 'failed'));
|
|
158
|
+
if (test.parent) {
|
|
159
|
+
const file = this.getFile(test.parent);
|
|
160
|
+
if (file) {
|
|
161
|
+
const tags = (0, tagParser_1.extractTags)(file, test.title);
|
|
162
|
+
ids.push(...this.extractQaseIds(tags));
|
|
163
|
+
}
|
|
145
164
|
}
|
|
146
|
-
message += test.err.message;
|
|
147
165
|
}
|
|
148
166
|
const result = {
|
|
149
167
|
attachments: attachments ?? [],
|
|
@@ -156,7 +174,7 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
|
|
|
156
174
|
relations: relations,
|
|
157
175
|
run_id: null,
|
|
158
176
|
signature: this.getSignature(test, ids),
|
|
159
|
-
steps:
|
|
177
|
+
steps: steps,
|
|
160
178
|
id: (0, uuid_1.v4)(),
|
|
161
179
|
execution: {
|
|
162
180
|
status: test.state
|
|
@@ -196,6 +214,18 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
|
|
|
196
214
|
}
|
|
197
215
|
return signature;
|
|
198
216
|
}
|
|
217
|
+
getTestFileName(test) {
|
|
218
|
+
if (!test.parent) {
|
|
219
|
+
return '';
|
|
220
|
+
}
|
|
221
|
+
const file = this.getFile(test.parent);
|
|
222
|
+
if (!file) {
|
|
223
|
+
return '';
|
|
224
|
+
}
|
|
225
|
+
const pathParts = file.split('/');
|
|
226
|
+
const fileName = pathParts[pathParts.length - 1];
|
|
227
|
+
return fileName ? fileName : '';
|
|
228
|
+
}
|
|
199
229
|
/**
|
|
200
230
|
* @param {Suite} suite
|
|
201
231
|
* @private
|
|
@@ -221,6 +251,44 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
|
|
|
221
251
|
}
|
|
222
252
|
return title;
|
|
223
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* Extracts numbers from @qaseid tags, regardless of case.
|
|
256
|
+
* @param tags - An array of tags to process.
|
|
257
|
+
* @returns An array of numbers extracted from the tags.
|
|
258
|
+
*/
|
|
259
|
+
extractQaseIds(tags) {
|
|
260
|
+
const qaseIdRegex = /@qaseid\((\d+(?:,\d+)*)\)/i;
|
|
261
|
+
const qaseIds = [];
|
|
262
|
+
for (const tag of tags) {
|
|
263
|
+
const match = qaseIdRegex.exec(tag);
|
|
264
|
+
if (match) {
|
|
265
|
+
const ids = match[1]?.split(',').map(id => parseInt(id, 10));
|
|
266
|
+
if (ids) {
|
|
267
|
+
qaseIds.push(...ids);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return qaseIds;
|
|
272
|
+
}
|
|
273
|
+
convertCypressMessages(messages, testStatus) {
|
|
274
|
+
const result = [];
|
|
275
|
+
const lastIndex = messages.length - 1;
|
|
276
|
+
for (const message of messages) {
|
|
277
|
+
const step = new qase_javascript_commons_1.TestStepType(qase_javascript_commons_1.StepType.TEXT);
|
|
278
|
+
step.id = message.id;
|
|
279
|
+
step.execution.status = qase_javascript_commons_1.StepStatusEnum.passed;
|
|
280
|
+
step.execution.start_time = message.timestamp;
|
|
281
|
+
step.data = {
|
|
282
|
+
action: message.name,
|
|
283
|
+
expected_result: null,
|
|
284
|
+
};
|
|
285
|
+
if (lastIndex === messages.indexOf(message) && testStatus !== 'passed') {
|
|
286
|
+
step.execution.status = qase_javascript_commons_1.StepStatusEnum.failed;
|
|
287
|
+
}
|
|
288
|
+
result.push(step);
|
|
289
|
+
}
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
224
292
|
getSteps(steps, attachments) {
|
|
225
293
|
const result = [];
|
|
226
294
|
const stepMap = new Map();
|
|
@@ -263,15 +331,3 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
|
|
|
263
331
|
}
|
|
264
332
|
}
|
|
265
333
|
exports.CypressQaseReporter = CypressQaseReporter;
|
|
266
|
-
/**
|
|
267
|
-
* @type {RegExp}
|
|
268
|
-
*/
|
|
269
|
-
CypressQaseReporter.qaseIdRegExp = /\(Qase ID:? ([\d,]+)\)/;
|
|
270
|
-
/**
|
|
271
|
-
* @type {Record<CypressState, TestStatusEnum>}
|
|
272
|
-
*/
|
|
273
|
-
CypressQaseReporter.statusMap = {
|
|
274
|
-
failed: qase_javascript_commons_1.TestStatusEnum.failed,
|
|
275
|
-
passed: qase_javascript_commons_1.TestStatusEnum.passed,
|
|
276
|
-
pending: qase_javascript_commons_1.TestStatusEnum.skipped,
|
|
277
|
-
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts tags for a given scenario name from a Gherkin feature file.
|
|
3
|
+
* @param filePath - Path to the feature file.
|
|
4
|
+
* @param scenarioName - Name of the scenario to search for.
|
|
5
|
+
* @returns An array of tags found for the specified scenario.
|
|
6
|
+
*/
|
|
7
|
+
export declare function extractTags(filePath: string, scenarioName: string): string[];
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.extractTags = void 0;
|
|
27
|
+
const fs = __importStar(require("fs"));
|
|
28
|
+
/**
|
|
29
|
+
* Extracts tags for a given scenario name from a Gherkin feature file.
|
|
30
|
+
* @param filePath - Path to the feature file.
|
|
31
|
+
* @param scenarioName - Name of the scenario to search for.
|
|
32
|
+
* @returns An array of tags found for the specified scenario.
|
|
33
|
+
*/
|
|
34
|
+
function extractTags(filePath, scenarioName) {
|
|
35
|
+
// Read the file content
|
|
36
|
+
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
|
37
|
+
// Split the content into lines
|
|
38
|
+
const lines = fileContent.split('\n');
|
|
39
|
+
let tags = [];
|
|
40
|
+
for (let i = 0; i < lines.length; i++) {
|
|
41
|
+
const trimmedLine = lines[i]?.trim(); // Ensure line exists and trim it
|
|
42
|
+
// Check if the line is a Scenario line and matches the provided name
|
|
43
|
+
if (trimmedLine?.startsWith('Scenario:') && trimmedLine === `Scenario: ${scenarioName}`) {
|
|
44
|
+
// Collect tags from preceding lines
|
|
45
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
46
|
+
const previousLine = lines[j]?.trim(); // Ensure line exists and trim it
|
|
47
|
+
if (previousLine?.startsWith('@')) {
|
|
48
|
+
tags = previousLine.split(/\s+/).filter(tag => tag.startsWith('@'));
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
else if (previousLine === '' || previousLine?.startsWith('Feature:')) {
|
|
52
|
+
// Stop searching if an empty line or the start of a feature is reached
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
break; // Stop processing further as the scenario is found
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return tags;
|
|
60
|
+
}
|
|
61
|
+
exports.extractTags = extractTags;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cypress-qase-reporter",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.5",
|
|
4
4
|
"description": "Qase Cypress Reporter",
|
|
5
5
|
"homepage": "https://github.com/qase-tms/qase-javascript",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"./package.json": "./package.json",
|
|
14
14
|
"./plugin": "./dist/plugin.js",
|
|
15
15
|
"./metadata": "./dist/metadata.js",
|
|
16
|
-
"./hooks": "./dist/hooks.js"
|
|
16
|
+
"./hooks": "./dist/hooks.js",
|
|
17
|
+
"./cucumber": "./dist/cucumber.js"
|
|
17
18
|
},
|
|
18
19
|
"typesVersions": {
|
|
19
20
|
"*": {
|
|
@@ -25,6 +26,9 @@
|
|
|
25
26
|
],
|
|
26
27
|
"reporter": [
|
|
27
28
|
"./dist/reporter.d.ts"
|
|
29
|
+
],
|
|
30
|
+
"cucumber": [
|
|
31
|
+
"./dist/cucumber.d.ts"
|
|
28
32
|
]
|
|
29
33
|
}
|
|
30
34
|
},
|