cypress-qase-reporter 3.1.2 → 3.2.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/README.md CHANGED
@@ -259,6 +259,12 @@ module.exports = cypress.defineConfig({
259
259
  Check out the example of configuration for multiple reporters in the
260
260
  [demo project](../examples/cypress/cypress.config.js).
261
261
 
262
+ ### Multi-Project Support
263
+
264
+ Qase Cypress Reporter supports sending test results to multiple Qase projects simultaneously. You can specify different test case IDs for each project using `qase.projects(mapping, it(...))`.
265
+
266
+ For detailed information, configuration, and examples, see the [Multi-Project Support Guide](docs/MULTI_PROJECT.md).
267
+
262
268
  ## Cucumber/Gherkin Integration
263
269
 
264
270
  If you use Cucumber with Gherkin feature files in your Cypress tests, Qase reporter provides full support for both the legacy `cypress-cucumber-preprocessor` and the modern `@badeball/cypress-cucumber-preprocessor`.
package/changelog.md CHANGED
@@ -1,3 +1,9 @@
1
+ # cypress-qase-reporter@3.2.0
2
+
3
+ ## What's new
4
+
5
+ - Added support for multi-project support.
6
+
1
7
  # cypress-qase-reporter@3.1.2
2
8
 
3
9
  ## What's new
package/dist/mocha.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import { Test } from 'mocha';
2
+ /** Project code → test case IDs for multi-project (testops_multi) mode. */
3
+ export type ProjectMapping = Record<string, number[]>;
2
4
  export declare const qase: {
3
5
  (caseId: number | string | number[] | string[], test: Test): Test;
6
+ projects(mapping: ProjectMapping, nameOrTest: string | Test): string | Test;
4
7
  title(value: string): Cypress.Chainable<JQuery<void>>;
5
8
  fields(values: Record<string, string>): Cypress.Chainable<JQuery<void>>;
6
9
  ignore(): Cypress.Chainable<JQuery<void>>;
package/dist/mocha.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.qase = void 0;
4
+ const qase_javascript_commons_1 = require("qase-javascript-commons");
4
5
  const qase = (caseId, test) => {
5
6
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
6
7
  if (!test?.title) {
@@ -11,6 +12,24 @@ const qase = (caseId, test) => {
11
12
  return test;
12
13
  };
13
14
  exports.qase = qase;
15
+ /**
16
+ * Build test title with multi-project markers (for testops_multi mode).
17
+ * Supports two usages:
18
+ * - it(qase.projects({ PROJ1: [100], PROJ2: [200] }, 'Login flow'), () => { ... }) — returns formatted title string.
19
+ * - qase.projects({ PROJ1: [100], PROJ2: [200] }, it('Login flow', () => { ... })) — mutates test.title and returns the test.
20
+ * @param mapping — e.g. { PROJ1: [1, 2], PROJ2: [3] }
21
+ * @param nameOrTest — test title string or Mocha Test (when passing it(...)); the test title is updated with markers so the reporter can parse testops_project_mapping.
22
+ */
23
+ function isTestObject(nameOrTest) {
24
+ return Boolean(nameOrTest && typeof nameOrTest === 'object' && 'title' in nameOrTest && typeof nameOrTest.title === 'string');
25
+ }
26
+ exports.qase.projects = (mapping, nameOrTest) => {
27
+ if (isTestObject(nameOrTest)) {
28
+ nameOrTest.title = (0, qase_javascript_commons_1.formatTitleWithProjectMapping)(nameOrTest.title, mapping);
29
+ return nameOrTest;
30
+ }
31
+ return (0, qase_javascript_commons_1.formatTitleWithProjectMapping)(String(nameOrTest), mapping);
32
+ };
14
33
  /**
15
34
  * Set a title for the test case
16
35
  * @param {string} value
@@ -13,17 +13,12 @@ export declare class CypressQaseReporter extends reporters.Base {
13
13
  /**
14
14
  * @type {RegExp}
15
15
  */
16
+ /** @deprecated Use parseProjectMappingFromTitle from qase-javascript-commons for multi-project support. */
16
17
  static qaseIdRegExp: RegExp;
17
18
  /**
18
19
  * @type {Record<CypressState, TestStatusEnum>}
19
20
  */
20
21
  static statusMap: Record<CypressState, TestStatusEnum>;
21
- /**
22
- * @param {string} title
23
- * @returns {number[]}
24
- * @private
25
- */
26
- private static getCaseId;
27
22
  /**
28
23
  * @type {string | undefined}
29
24
  * @private
@@ -114,12 +109,6 @@ export declare class CypressQaseReporter extends reporters.Base {
114
109
  * @private
115
110
  */
116
111
  private removeQaseIdsFromTitle;
117
- /**
118
- * Extracts numbers from @qaseid tags, regardless of case.
119
- * @param tags - An array of tags to process.
120
- * @returns An array of numbers extracted from the tags.
121
- */
122
- private extractQaseIds;
123
112
  private convertCypressMessages;
124
113
  private getSteps;
125
114
  }
package/dist/reporter.js CHANGED
@@ -23,6 +23,7 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
23
23
  /**
24
24
  * @type {RegExp}
25
25
  */
26
+ /** @deprecated Use parseProjectMappingFromTitle from qase-javascript-commons for multi-project support. */
26
27
  static qaseIdRegExp = /\(Qase ID:? ([\d,]+)\)/;
27
28
  /**
28
29
  * @type {Record<CypressState, TestStatusEnum>}
@@ -32,15 +33,6 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
32
33
  passed: qase_javascript_commons_1.TestStatusEnum.passed,
33
34
  pending: qase_javascript_commons_1.TestStatusEnum.skipped,
34
35
  };
35
- /**
36
- * @param {string} title
37
- * @returns {number[]}
38
- * @private
39
- */
40
- static getCaseId(title) {
41
- const [, ids] = title.match(CypressQaseReporter.qaseIdRegExp) ?? [];
42
- return ids ? ids.split(',').map((id) => Number(id)) : [];
43
- }
44
36
  /**
45
37
  * @type {string | undefined}
46
38
  * @private
@@ -196,7 +188,9 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
196
188
  const end_time = Date.now();
197
189
  const duration = 0; // Skipped tests have no duration
198
190
  const start_time = this.testBeginTime || Date.now();
199
- const ids = CypressQaseReporter.getCaseId(test.title);
191
+ const fromTitle = (0, qase_javascript_commons_1.parseProjectMappingFromTitle)(test.title);
192
+ const legacyIds = [...fromTitle.legacyIds];
193
+ const projectMapping = { ...fromTitle.projectMapping };
200
194
  const testFileName = this.getTestFileName(test);
201
195
  const files = this.screenshotsFolder ?
202
196
  fileSearcher_1.FileSearcher.findFilesBeforeTime(this.screenshotsFolder, testFileName, new Date(start_time))
@@ -230,9 +224,14 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
230
224
  const file = this.getFile(test.parent);
231
225
  if (file) {
232
226
  const tags = (0, tagParser_1.extractTags)(file, test.title);
233
- ids.push(...this.extractQaseIds(tags));
227
+ const fromTags = (0, qase_javascript_commons_1.parseProjectMappingFromTags)(tags);
228
+ legacyIds.push(...fromTags.legacyIds);
229
+ for (const [code, idsFromTag] of Object.entries(fromTags.projectMapping)) {
230
+ projectMapping[code] = [...(projectMapping[code] ?? []), ...idsFromTag];
231
+ }
234
232
  }
235
233
  }
234
+ const hasProjectMapping = Object.keys(projectMapping).length > 0;
236
235
  const result = {
237
236
  attachments: attachments,
238
237
  author: null,
@@ -243,7 +242,7 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
243
242
  group_params: {},
244
243
  relations: relations,
245
244
  run_id: null,
246
- signature: this.getSignature(test, ids, {}),
245
+ signature: this.getSignature(test, hasProjectMapping ? [] : legacyIds, {}),
247
246
  steps: [],
248
247
  id: (0, uuid_1.v4)(),
249
248
  execution: {
@@ -254,8 +253,11 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
254
253
  stacktrace: null,
255
254
  thread: null,
256
255
  },
257
- testops_id: ids.length > 0 ? ids : null,
258
- title: this.removeQaseIdsFromTitle(test.title),
256
+ testops_id: !hasProjectMapping && legacyIds.length > 0
257
+ ? (legacyIds.length === 1 ? legacyIds[0] : legacyIds)
258
+ : null,
259
+ testops_project_mapping: hasProjectMapping ? projectMapping : null,
260
+ title: fromTitle.cleanedTitle || this.removeQaseIdsFromTitle(test.title),
259
261
  preparedAttachments: [],
260
262
  };
261
263
  void this.reporter.addTestResult(result);
@@ -276,7 +278,9 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
276
278
  manager_1.MetadataManager.clear();
277
279
  return;
278
280
  }
279
- const ids = CypressQaseReporter.getCaseId(test.title);
281
+ const fromTitle = (0, qase_javascript_commons_1.parseProjectMappingFromTitle)(test.title);
282
+ const legacyIds = [...fromTitle.legacyIds];
283
+ const projectMapping = { ...fromTitle.projectMapping };
280
284
  const testFileName = this.getTestFileName(test);
281
285
  const files = this.screenshotsFolder ?
282
286
  fileSearcher_1.FileSearcher.findFilesBeforeTime(this.screenshotsFolder, testFileName, new Date(this.testBeginTime))
@@ -340,10 +344,15 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
340
344
  const file = this.getFile(test.parent);
341
345
  if (file) {
342
346
  const tags = (0, tagParser_1.extractTags)(file, test.title);
343
- ids.push(...this.extractQaseIds(tags));
347
+ const fromTags = (0, qase_javascript_commons_1.parseProjectMappingFromTags)(tags);
348
+ legacyIds.push(...fromTags.legacyIds);
349
+ for (const [code, idsFromTag] of Object.entries(fromTags.projectMapping)) {
350
+ projectMapping[code] = [...(projectMapping[code] ?? []), ...idsFromTag];
351
+ }
344
352
  }
345
353
  }
346
354
  }
355
+ const hasProjectMapping = Object.keys(projectMapping).length > 0;
347
356
  const result = {
348
357
  attachments: attachments,
349
358
  author: null,
@@ -354,7 +363,7 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
354
363
  group_params: metadata?.groupParams ?? {},
355
364
  relations: relations,
356
365
  run_id: null,
357
- signature: this.getSignature(test, ids, metadata?.parameters ?? {}),
366
+ signature: this.getSignature(test, hasProjectMapping ? [] : legacyIds, metadata?.parameters ?? {}),
358
367
  steps: steps,
359
368
  id: (0, uuid_1.v4)(),
360
369
  execution: {
@@ -365,8 +374,11 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
365
374
  stacktrace: test.err?.stack ?? null,
366
375
  thread: null,
367
376
  },
368
- testops_id: ids.length > 0 ? ids : null,
369
- title: metadata?.title ?? this.removeQaseIdsFromTitle(test.title),
377
+ testops_id: !hasProjectMapping && legacyIds.length > 0
378
+ ? (legacyIds.length === 1 ? legacyIds[0] : legacyIds)
379
+ : null,
380
+ testops_project_mapping: hasProjectMapping ? projectMapping : null,
381
+ title: metadata?.title ?? (fromTitle.cleanedTitle || this.removeQaseIdsFromTitle(test.title)),
370
382
  preparedAttachments: [],
371
383
  };
372
384
  void this.reporter.addTestResult(result);
@@ -428,25 +440,6 @@ class CypressQaseReporter extends mocha_1.reporters.Base {
428
440
  }
429
441
  return title;
430
442
  }
431
- /**
432
- * Extracts numbers from @qaseid tags, regardless of case.
433
- * @param tags - An array of tags to process.
434
- * @returns An array of numbers extracted from the tags.
435
- */
436
- extractQaseIds(tags) {
437
- const qaseIdRegex = /@qaseid\((\d+(?:,\d+)*)\)/i;
438
- const qaseIds = [];
439
- for (const tag of tags) {
440
- const match = qaseIdRegex.exec(tag);
441
- if (match) {
442
- const ids = match[1]?.split(',').map(id => parseInt(id, 10));
443
- if (ids) {
444
- qaseIds.push(...ids);
445
- }
446
- }
447
- }
448
- return qaseIds;
449
- }
450
443
  convertCypressMessages(messages, testStatus) {
451
444
  const result = [];
452
445
  const lastIndex = messages.length - 1;
@@ -0,0 +1,107 @@
1
+ # Multi-Project Support in Cypress
2
+
3
+ Qase Cypress Reporter supports sending test results to multiple Qase projects simultaneously. This feature allows you to report the same test execution to different projects with different test case IDs, which is useful when:
4
+
5
+ * You need to report the same test to different projects
6
+ * Different projects track the same functionality with different test case IDs
7
+ * You want to maintain separate test runs for different environments or teams
8
+
9
+ ## Configuration
10
+
11
+ For detailed configuration options, refer to the [qase-javascript-commons README](../../qase-javascript-commons/README.md#multi-project-support).
12
+
13
+ ### Basic Multi-Project Configuration
14
+
15
+ To enable multi-project support, set the mode to `testops_multi` in your Cypress reporter options (e.g. in `cypress.config.js` or `qase.config.json`):
16
+
17
+ ```json
18
+ {
19
+ "mode": "testops_multi",
20
+ "testops": {
21
+ "api": { "token": "<token>", "host": "qase.io" },
22
+ "batch": { "size": 100 }
23
+ },
24
+ "testops_multi": {
25
+ "default_project": "PROJ1",
26
+ "projects": [
27
+ {
28
+ "code": "PROJ1",
29
+ "run": { "title": "PROJ1 Cypress Run", "complete": true }
30
+ },
31
+ {
32
+ "code": "PROJ2",
33
+ "run": { "title": "PROJ2 Cypress Run", "complete": true }
34
+ }
35
+ ]
36
+ }
37
+ }
38
+ ```
39
+
40
+ ## Using `qase.projects()`
41
+
42
+ The `qase.projects(mapping, nameOrTest)` helper lets you map a test to one or more projects and case IDs.
43
+
44
+ ### Pass the test (recommended)
45
+
46
+ Pass the result of `it()` as the second argument so the test title is updated with markers; the reporter will parse them and set `testops_project_mapping`:
47
+
48
+ ```javascript
49
+ const { qase } = require('cypress-qase-reporter');
50
+
51
+ describe('Suite', () => {
52
+ qase.projects({ PROJ1: [1], PROJ2: [2] }, it('A test reported to two projects', () => {
53
+ cy.visit('https://example.cypress.io');
54
+ cy.contains('type').click();
55
+ }));
56
+
57
+ qase.projects(
58
+ { PROJ1: [10, 11], PROJ2: [20] },
59
+ it('Multiple cases per project', () => {
60
+ cy.visit('https://example.cypress.io');
61
+ }),
62
+ );
63
+ });
64
+ ```
65
+
66
+ ### Pass the title string
67
+
68
+ You can also pass a title string; the helper returns the formatted title for use as the first argument to `it()`:
69
+
70
+ ```javascript
71
+ it(qase.projects({ PROJ1: [100], PROJ2: [200] }, 'Login flow'), () => {
72
+ cy.visit('/login');
73
+ });
74
+ ```
75
+
76
+ ### Combining with other Qase methods
77
+
78
+ Use `qase.projects()` together with other Qase methods (e.g. `qase.title()`, `qase.attach()`) inside the test.
79
+
80
+ ## Tests Without Project Mapping
81
+
82
+ If a test does not use `qase.projects()` and has no `(Qase PROJ: ids)` markers in the title, it is sent to the `default_project` from your configuration. If the test also has no `(Qase ID: …)` legacy ID, the result is sent to the default project without linking to a test case.
83
+
84
+ ## Important Notes
85
+
86
+ 1. **Project codes must match**: The project codes in `qase.projects({ PROJ1: [1], ... })` must exactly match the codes in `testops_multi.projects[].code`.
87
+
88
+ 2. **Mode requirement**: Set `mode` to `testops_multi` in your reporter config. Single-project mode (`testops`) does not use project mapping.
89
+
90
+ 3. **Cucumber/BDD**: When using Cypress with Cucumber (e.g. `@badeball/cypress-cucumber-preprocessor`), use tags in feature files: `@qaseid.PROJ1(1) @qaseid.PROJ2(2)`. See [Cucumber documentation](cucumber.md).
91
+
92
+ ## Examples
93
+
94
+ See the [multi-project Cypress example](../../examples/multiProject/cypress/) for a complete runnable setup.
95
+
96
+ ## Troubleshooting
97
+
98
+ ### Results not appearing in projects
99
+
100
+ * Ensure `mode` is `testops_multi` in reporter options.
101
+ * Verify project codes in `qase.projects()` match `testops_multi.projects[].code`.
102
+ * For Cypress, ensure you pass the test to `qase.projects(mapping, it(...))` so the title is updated with markers, or use a title that already contains `(Qase PROJ: ids)`.
103
+
104
+ ### Results sent to wrong project
105
+
106
+ * Check the `default_project` setting for tests without explicit mapping.
107
+ * Project codes are case-sensitive and must match exactly.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cypress-qase-reporter",
3
- "version": "3.1.2",
3
+ "version": "3.2.0",
4
4
  "description": "Qase Cypress Reporter",
5
5
  "homepage": "https://github.com/qase-tms/qase-javascript",
6
6
  "sideEffects": false,
@@ -51,7 +51,7 @@
51
51
  "author": "Qase Team <support@qase.io>",
52
52
  "license": "Apache-2.0",
53
53
  "dependencies": {
54
- "qase-javascript-commons": "~2.4.14",
54
+ "qase-javascript-commons": "~2.5.0",
55
55
  "uuid": "^9.0.1"
56
56
  },
57
57
  "peerDependencies": {