cypress-qase-reporter 2.2.4 → 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 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
- ```js
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,9 @@
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
+
1
7
  # cypress-qase-reporter@2.2.4
2
8
 
3
9
  ## What's new
@@ -0,0 +1 @@
1
+ export declare const enableCucumberSupport: () => void;
@@ -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
+ };
@@ -1 +1,2 @@
1
1
  export { qase } from './mocha';
2
+ export { enableCucumberSupport } from './cucumber';
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;
@@ -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
@@ -62,4 +62,10 @@ module.exports = function (on) {
62
62
  return null;
63
63
  },
64
64
  });
65
+ on('task', {
66
+ qaseCucumberStepStart(value) {
67
+ manager_1.MetadataManager.addCucumberStep(value);
68
+ return null;
69
+ },
70
+ });
65
71
  };
@@ -1,6 +1,6 @@
1
1
  /// <reference types="cypress" />
2
2
  import { MochaOptions, reporters, Runner } from 'mocha';
3
- import { ConfigLoader, TestStatusEnum, FrameworkOptionsType } from 'qase-javascript-commons';
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'> & {
@@ -71,6 +71,13 @@ export declare class CypressQaseReporter extends reporters.Base {
71
71
  * @private
72
72
  */
73
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;
74
81
  private getSteps;
75
82
  }
76
83
  export {};
package/dist/reporter.js CHANGED
@@ -12,12 +12,25 @@ const qase_javascript_commons_1 = require("qase-javascript-commons");
12
12
  const configSchema_1 = require("./configSchema");
13
13
  const manager_1 = require("./metadata/manager");
14
14
  const fileSearcher_1 = require("./fileSearcher");
15
+ const tagParser_1 = require("./utils/tagParser");
15
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[]}
@@ -27,6 +40,18 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
27
40
  const [, ids] = title.match(CypressQaseReporter.qaseIdRegExp) ?? [];
28
41
  return ids ? ids.split(',').map((id) => Number(id)) : [];
29
42
  }
43
+ /**
44
+ * @type {string | undefined}
45
+ * @private
46
+ */
47
+ screenshotsFolder;
48
+ /**
49
+ * @type {ReporterInterface}
50
+ * @private
51
+ */
52
+ reporter;
53
+ testBeginTime = Date.now();
54
+ options;
30
55
  /**
31
56
  * @param {Runner} runner
32
57
  * @param {CypressQaseOptionsType} options
@@ -34,7 +59,6 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
34
59
  */
35
60
  constructor(runner, options, configLoader = new qase_javascript_commons_1.ConfigLoader(configSchema_1.configSchema)) {
36
61
  super(runner, options);
37
- this.testBeginTime = Date.now();
38
62
  const { reporterOptions } = options;
39
63
  const config = configLoader.load();
40
64
  const { framework, ...composedOptions } = (0, qase_javascript_commons_1.composeOptions)(reporterOptions, config);
@@ -58,6 +82,7 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
58
82
  runner.on(EVENT_TEST_FAIL, (test) => this.addTestResult(test));
59
83
  runner.on(EVENT_TEST_BEGIN, () => {
60
84
  this.testBeginTime = Date.now();
85
+ manager_1.MetadataManager.clear();
61
86
  });
62
87
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
63
88
  runner.once(EVENT_RUN_END, () => {
@@ -82,9 +107,9 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
82
107
  return;
83
108
  }
84
109
  const ids = CypressQaseReporter.getCaseId(test.title);
85
- const testFile = this.getTestFileName(test);
110
+ const testFileName = this.getTestFileName(test);
86
111
  const files = this.screenshotsFolder ?
87
- fileSearcher_1.FileSearcher.findFilesBeforeTime(path_1.default.join(this.screenshotsFolder, testFile), new Date(this.testBeginTime))
112
+ fileSearcher_1.FileSearcher.findFilesBeforeTime(path_1.default.join(this.screenshotsFolder, testFileName), new Date(this.testBeginTime))
88
113
  : [];
89
114
  const attachments = files.map((file) => ({
90
115
  content: '',
@@ -122,15 +147,21 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
122
147
  },
123
148
  };
124
149
  }
125
- let message = null;
126
- if (metadata?.comment) {
127
- message = metadata.comment;
128
- }
150
+ let message = metadata?.comment ?? '';
129
151
  if (test.err?.message) {
130
- if (message) {
131
- message += '\n\n';
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
+ }
132
164
  }
133
- message += test.err.message;
134
165
  }
135
166
  const result = {
136
167
  attachments: attachments ?? [],
@@ -143,7 +174,7 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
143
174
  relations: relations,
144
175
  run_id: null,
145
176
  signature: this.getSignature(test, ids),
146
- steps: metadata?.steps ? this.getSteps(metadata.steps, metadata.stepAttachments ?? {}) : [],
177
+ steps: steps,
147
178
  id: (0, uuid_1.v4)(),
148
179
  execution: {
149
180
  status: test.state
@@ -220,6 +251,44 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
220
251
  }
221
252
  return title;
222
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
+ }
223
292
  getSteps(steps, attachments) {
224
293
  const result = [];
225
294
  const stepMap = new Map();
@@ -262,15 +331,3 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
262
331
  }
263
332
  }
264
333
  exports.CypressQaseReporter = CypressQaseReporter;
265
- /**
266
- * @type {RegExp}
267
- */
268
- CypressQaseReporter.qaseIdRegExp = /\(Qase ID:? ([\d,]+)\)/;
269
- /**
270
- * @type {Record<CypressState, TestStatusEnum>}
271
- */
272
- CypressQaseReporter.statusMap = {
273
- failed: qase_javascript_commons_1.TestStatusEnum.failed,
274
- passed: qase_javascript_commons_1.TestStatusEnum.passed,
275
- pending: qase_javascript_commons_1.TestStatusEnum.skipped,
276
- };
@@ -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.4",
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
  },