jest-qase-reporter 2.0.2 → 2.0.4

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
@@ -59,7 +59,6 @@ module.exports = {
59
59
  Now, run the Jest tests as usual.
60
60
  Test results will be reported to a new test run in Qase.
61
61
 
62
-
63
62
  ```console
64
63
  $ npx jest
65
64
  Determining test suites to run...
@@ -79,32 +78,49 @@ and suites from your test data.
79
78
  But if necessary, you can independently register the ID of already
80
79
  existing test cases from TMS before the executing tests. For example:
81
80
 
82
- ```typescript
83
- const { qase } = require("jest-qase-reporter/jest");
81
+ ### Metadata
84
82
 
85
- describe('My First Test', () => {
86
- test(qase([1,2], 'Several ids'), () => {
87
- expect(true).toBe(true);
88
- })
83
+ - `qase.title` - set the title of the test case
84
+ - `qase.fields` - set the fields of the test case
85
+ - `qase.suite` - set the suite of the test case
86
+ - `qase.comment` - set the comment of the test case
87
+ - `qase.parameters` - set the parameters of the test case
88
+ - `qase.groupParameters` - set the group parameters of the test case
89
+ - `qase.ignore` - ignore the test case in Qase. The test will be executed, but the results will not be sent to Qase.
90
+ - `qase.step` - create a step in the test case
91
+ - `qase.attach` - attach a file to the test case
89
92
 
90
- test(qase(3, 'Correct test'), () => {
91
- expect(true).toBe(true);
92
- })
93
-
94
- test.skip(qase("4", 'Skipped test'), () => {
95
- expect(true).toBe(true);
96
- })
93
+ ```typescript
94
+ const { qase } = require('jest-qase-reporter/jest');
97
95
 
98
- test(qase(["5", "6"], 'Failed test'), () => {
99
- expect(true).toBe(false);
100
- })
96
+ describe('My First Test', () => {
97
+ test(qase([1, 2], 'Several ids'), () => {
98
+ expect(true).toBe(true);
99
+ });
100
+
101
+ test(qase(3, 'Correct test'), () => {
102
+ qase.title('Title');
103
+ expect(true).toBe(true);
104
+ });
105
+
106
+ test.skip(qase('4', 'Skipped test'), () => {
107
+ expect(true).toBe(true);
108
+ });
109
+
110
+ test(qase(['5', '6'], 'Failed test'), () => {
111
+ expect(true).toBe(false);
112
+ });
101
113
  });
102
114
  ```
115
+
103
116
  To run tests and create a test run, execute the command (for example from folder examples):
117
+
104
118
  ```bash
105
119
  QASE_MODE=testops npx jest
106
120
  ```
121
+
107
122
  or
123
+
108
124
  ```bash
109
125
  npm test
110
126
  ```
@@ -125,10 +141,9 @@ Reporter options (* - required):
125
141
 
126
142
  - `mode` - `testops`/`off` Enables reporter, default - `off`
127
143
  - `debug` - Enables debug logging, default - `false`
128
- - `environment` - To execute with the sending of the envinroment information
129
- - *`testops.api.token` - Token for API access, you can find more information
130
- [here](https://developers.qase.io/#authentication)
131
- - *`testops.project` - Qase project code, for example, in https://app.qase.io/project/DEMO the code is `DEMO`
144
+ - `environment` - To execute with the sending of the envinroment information
145
+ - *`testops.api.token` - Token for API access, you can generate it [here](https://developers.qase.io/#authentication).
146
+ - *`testops.project` - [Your project's code](https://help.qase.io/en/articles/9787250-how-do-i-find-my-project-code)
132
147
  - `testops.run.id` - Qase test run ID, used when the test run was created earlier using CLI or API call.
133
148
  - `testops.run.title` - Set custom Run name, when new run is created
134
149
  - `testops.run.description` - Set custom Run description, when new run is created
@@ -167,7 +182,7 @@ Supported ENV variables:
167
182
 
168
183
  - `QASE_MODE` - Same as `mode`
169
184
  - `QASE_DEBUG` - Same as `debug`
170
- - `QASE_ENVIRONMENT` - Same as `environment`
185
+ - `QASE_ENVIRONMENT` - Same as `environment`
171
186
  - `QASE_TESTOPS_API_TOKEN` - Same as `testops.api.token`
172
187
  - `QASE_TESTOPS_PROJECT` - Same as `testops.project`
173
188
  - `QASE_TESTOPS_RUN_ID` - Pass Run ID from ENV and override reporter option `testops.run.id`
@@ -176,7 +191,8 @@ Supported ENV variables:
176
191
 
177
192
  ## Requirements
178
193
 
179
- We maintain the reporter on LTS versions of Node. You can find the current versions by following the [link](https://nodejs.org/en/about/releases/)
194
+ We maintain the reporter on LTS versions of Node. You can find the current versions by following
195
+ the [link](https://nodejs.org/en/about/releases/)
180
196
 
181
197
  `jest >= 28.0.0`
182
198
 
package/changelog.md CHANGED
@@ -1,9 +1,52 @@
1
+ # jest-qase-reporter@2.0.4
2
+
3
+ ## What's new
4
+
5
+ Improved test name processing: Qase IDs are now automatically removed when uploading results
6
+
7
+ # jest-qase-reporter@2.0.3
8
+
9
+ ## What's new
10
+
11
+ Added the ability to specify a test metadata in tests:
12
+
13
+ - `qase.title` - set the test title
14
+ - `qase.fields` - set the test fields
15
+ - `qase.suite` - set the test suite
16
+ - `qase.comment` - set the test comment
17
+ - `qase.parameters` - set the test parameters
18
+ - `qase.groupParameters` - set the test group parameters
19
+ - `qase.ignore` - ignore the test in Qase
20
+ - `qase.attach` - attach a file to the test
21
+ - `qase.steps` - add the test steps
22
+
23
+ ```ts
24
+ const { qase } = require('jest-qase-reporter/jest');
25
+
26
+ test('test', () => {
27
+ qase.title('Title');
28
+ qase.fields({ custom_field: 'value' });
29
+ qase.suite('Suite');
30
+ qase.comment('Comment');
31
+ qase.parameters({ param01: 'value' });
32
+ qase.groupParameters({ param02: 'value' });
33
+ qase.ignore();
34
+ qase.attach({ name: 'attachment.txt', content: 'Hello, world!', type: 'text/plain' });
35
+
36
+ qase.step('Step 1', () => {
37
+ expect(true).toBe(true);
38
+ });
39
+
40
+ expect(true).toBe(true);
41
+ });
42
+ ```
43
+
1
44
  # jest-qase-reporter@2.0.1
2
45
 
3
46
  ## What's new
4
47
 
5
48
  Fixed a bug when a test was marked as skipped.
6
- This reporter has uploaded this test as blocked.
49
+ This reporter has uploaded this test as blocked.
7
50
  Right now the reporter will upload this test as skipped.
8
51
 
9
52
  # jest-qase-reporter@2.0.0
@@ -0,0 +1,23 @@
1
+ import { JestQaseReporter } from './reporter';
2
+ import { Attachment, TestStepType } from 'qase-javascript-commons';
3
+ declare global {
4
+ namespace NodeJS {
5
+ interface Global {
6
+ Qase: Qase;
7
+ }
8
+ }
9
+ }
10
+ export declare class Qase {
11
+ private reporter;
12
+ constructor(reporter: JestQaseReporter);
13
+ title(title: string): void;
14
+ ignore(): void;
15
+ comment(value: string): void;
16
+ suite(value: string): void;
17
+ fields(values: Record<string, string>): void;
18
+ parameters(values: Record<string, string>): void;
19
+ groupParams(values: Record<string, string>): void;
20
+ step(step: TestStepType): void;
21
+ attachment(attachment: Attachment): void;
22
+ }
23
+ export {};
package/dist/global.js ADDED
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Qase = void 0;
4
+ class Qase {
5
+ reporter;
6
+ constructor(reporter) {
7
+ this.reporter = reporter;
8
+ }
9
+ title(title) {
10
+ this.reporter.addTitle(title);
11
+ }
12
+ ignore() {
13
+ this.reporter.addIgnore();
14
+ }
15
+ comment(value) {
16
+ this.reporter.addComment(value);
17
+ }
18
+ suite(value) {
19
+ this.reporter.addSuite(value);
20
+ }
21
+ fields(values) {
22
+ const stringRecord = {};
23
+ for (const [key, value] of Object.entries(values)) {
24
+ stringRecord[String(key)] = String(value);
25
+ }
26
+ this.reporter.addFields(stringRecord);
27
+ }
28
+ parameters(values) {
29
+ const stringRecord = {};
30
+ for (const [key, value] of Object.entries(values)) {
31
+ stringRecord[String(key)] = String(value);
32
+ }
33
+ this.reporter.addParameters(stringRecord);
34
+ }
35
+ groupParams(values) {
36
+ const stringRecord = {};
37
+ for (const [key, value] of Object.entries(values)) {
38
+ stringRecord[String(key)] = String(value);
39
+ }
40
+ this.reporter.addGroupParams(stringRecord);
41
+ }
42
+ step(step) {
43
+ this.reporter.addStep(step);
44
+ }
45
+ attachment(attachment) {
46
+ this.reporter.addAttachment(attachment);
47
+ }
48
+ }
49
+ exports.Qase = Qase;
package/dist/jest.d.ts CHANGED
@@ -1 +1,102 @@
1
- export declare const qase: (caseId: number | string | number[] | string[], name: string) => string;
1
+ import { StepFunction } from './step';
2
+ export declare const qase: {
3
+ (caseId: number | string | number[] | string[], name: string): string;
4
+ /**
5
+ * Set a title for the test case
6
+ * @param {string} value
7
+ * @example
8
+ * test('test', () => {
9
+ * qase.title("Title");
10
+ * expect(true).toBe(true);
11
+ * });
12
+ */
13
+ title(value: string): void;
14
+ /**
15
+ * Ignore the test case
16
+ * @example
17
+ * test('test', () => {
18
+ * qase.ignore();
19
+ * expect(true).toBe(true);
20
+ * });
21
+ */
22
+ ignore(): void;
23
+ /**
24
+ * Add a comment to the test case
25
+ * @param {string} value
26
+ * @example
27
+ * test('test', () => {
28
+ * qase.comment("Comment");
29
+ * expect(true).toBe(true);
30
+ * });
31
+ */
32
+ comment(value: string): void;
33
+ /**
34
+ * Set a suite for the test case
35
+ * @param {string} value
36
+ * @example
37
+ * test('test', () => {
38
+ * qase.suite("Suite");
39
+ * expect(true).toBe(true);
40
+ * });
41
+ */
42
+ suite(value: string): void;
43
+ /**
44
+ * Set fields for the test case
45
+ * @param {Record<string, string>} values
46
+ * @example
47
+ * test('test', () => {
48
+ * qase.fields({field: "value"});
49
+ * expect(true).toBe(true);
50
+ * });
51
+ */
52
+ fields(values: Record<string, string>): void;
53
+ /**
54
+ * Set parameters for the test case
55
+ * @param {Record<string, string>} values
56
+ * @example
57
+ * test('test', () => {
58
+ * qase.parameters({param: "value"});
59
+ * expect(true).toBe(true);
60
+ * });
61
+ */
62
+ parameters(values: Record<string, string>): void;
63
+ /**
64
+ * Set group params for the test case
65
+ * @param {Record<string, string>} values
66
+ * @example
67
+ * test('test', () => {
68
+ * qase.groupParameters({param: "value"});
69
+ * expect(true).toBe(true);
70
+ * });
71
+ */
72
+ groupParameters(values: Record<string, string>): void;
73
+ /**
74
+ * Add a step to the test case
75
+ * @param name
76
+ * @param body
77
+ * @example
78
+ * test('test', () => {
79
+ * qase.step("Step", () => {
80
+ * expect(true).toBe(true);
81
+ * });
82
+ * expect(true).toBe(true);
83
+ * });
84
+ */
85
+ step(name: string, body: StepFunction): Promise<void>;
86
+ /**
87
+ * Add an attachment to the test case
88
+ * @param attach
89
+ * @example
90
+ * test('test', () => {
91
+ * qase.attach({ name: 'attachment.txt', content: 'Hello, world!', type: 'text/plain' });
92
+ * qase.attach({ paths: ['/path/to/file', '/path/to/another/file']});
93
+ * expect(true).toBe(true);
94
+ * });
95
+ */
96
+ attach(attach: {
97
+ name?: string;
98
+ type?: string;
99
+ content?: string;
100
+ paths?: string[];
101
+ }): void;
102
+ };
package/dist/jest.js CHANGED
@@ -1,8 +1,152 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.qase = void 0;
7
+ const step_1 = require("./step");
8
+ const path_1 = __importDefault(require("path"));
9
+ const qase_javascript_commons_1 = require("qase-javascript-commons");
10
+ const uuid_1 = require("uuid");
4
11
  const qase = (caseId, name) => {
5
12
  const caseIds = Array.isArray(caseId) ? caseId : [caseId];
6
13
  return `${name} (Qase ID: ${caseIds.join(',')})`;
7
14
  };
8
15
  exports.qase = qase;
16
+ /**
17
+ * Set a title for the test case
18
+ * @param {string} value
19
+ * @example
20
+ * test('test', () => {
21
+ * qase.title("Title");
22
+ * expect(true).toBe(true);
23
+ * });
24
+ */
25
+ exports.qase.title = (value) => {
26
+ global.Qase.title(value);
27
+ };
28
+ /**
29
+ * Ignore the test case
30
+ * @example
31
+ * test('test', () => {
32
+ * qase.ignore();
33
+ * expect(true).toBe(true);
34
+ * });
35
+ */
36
+ exports.qase.ignore = () => {
37
+ global.Qase.ignore();
38
+ };
39
+ /**
40
+ * Add a comment to the test case
41
+ * @param {string} value
42
+ * @example
43
+ * test('test', () => {
44
+ * qase.comment("Comment");
45
+ * expect(true).toBe(true);
46
+ * });
47
+ */
48
+ exports.qase.comment = (value) => {
49
+ global.Qase.comment(value);
50
+ };
51
+ /**
52
+ * Set a suite for the test case
53
+ * @param {string} value
54
+ * @example
55
+ * test('test', () => {
56
+ * qase.suite("Suite");
57
+ * expect(true).toBe(true);
58
+ * });
59
+ */
60
+ exports.qase.suite = (value) => {
61
+ global.Qase.suite(value);
62
+ };
63
+ /**
64
+ * Set fields for the test case
65
+ * @param {Record<string, string>} values
66
+ * @example
67
+ * test('test', () => {
68
+ * qase.fields({field: "value"});
69
+ * expect(true).toBe(true);
70
+ * });
71
+ */
72
+ exports.qase.fields = (values) => {
73
+ global.Qase.fields(values);
74
+ };
75
+ /**
76
+ * Set parameters for the test case
77
+ * @param {Record<string, string>} values
78
+ * @example
79
+ * test('test', () => {
80
+ * qase.parameters({param: "value"});
81
+ * expect(true).toBe(true);
82
+ * });
83
+ */
84
+ exports.qase.parameters = (values) => {
85
+ global.Qase.parameters(values);
86
+ };
87
+ /**
88
+ * Set group params for the test case
89
+ * @param {Record<string, string>} values
90
+ * @example
91
+ * test('test', () => {
92
+ * qase.groupParameters({param: "value"});
93
+ * expect(true).toBe(true);
94
+ * });
95
+ */
96
+ exports.qase.groupParameters = (values) => {
97
+ global.Qase.groupParams(values);
98
+ };
99
+ /**
100
+ * Add a step to the test case
101
+ * @param name
102
+ * @param body
103
+ * @example
104
+ * test('test', () => {
105
+ * qase.step("Step", () => {
106
+ * expect(true).toBe(true);
107
+ * });
108
+ * expect(true).toBe(true);
109
+ * });
110
+ */
111
+ exports.qase.step = async (name, body) => {
112
+ const runningStep = new step_1.QaseStep(name);
113
+ // eslint-disable-next-line @typescript-eslint/require-await
114
+ await runningStep.run(body, async (step) => global.Qase.step(step));
115
+ };
116
+ /**
117
+ * Add an attachment to the test case
118
+ * @param attach
119
+ * @example
120
+ * test('test', () => {
121
+ * qase.attach({ name: 'attachment.txt', content: 'Hello, world!', type: 'text/plain' });
122
+ * qase.attach({ paths: ['/path/to/file', '/path/to/another/file']});
123
+ * expect(true).toBe(true);
124
+ * });
125
+ */
126
+ exports.qase.attach = (attach) => {
127
+ if (attach.paths) {
128
+ for (const file of attach.paths) {
129
+ const attachmentName = path_1.default.basename(file);
130
+ const contentType = (0, qase_javascript_commons_1.getMimeTypes)(file);
131
+ global.Qase.attachment({
132
+ file_path: file,
133
+ size: 0,
134
+ id: (0, uuid_1.v4)(),
135
+ file_name: attachmentName,
136
+ mime_type: contentType,
137
+ content: '',
138
+ });
139
+ }
140
+ return;
141
+ }
142
+ if (attach.content) {
143
+ global.Qase.attachment({
144
+ file_path: null,
145
+ size: attach.content.length,
146
+ id: (0, uuid_1.v4)(),
147
+ file_name: attach.name ?? 'attachment',
148
+ mime_type: attach.type ?? 'application/octet-stream',
149
+ content: attach.content,
150
+ });
151
+ }
152
+ };
@@ -0,0 +1,12 @@
1
+ import { Attachment, TestStepType } from 'qase-javascript-commons';
2
+ export interface Metadata {
3
+ title: string | undefined;
4
+ ignore: boolean;
5
+ comment: string | undefined;
6
+ suite: string | undefined;
7
+ fields: Record<string, string>;
8
+ parameters: Record<string, string>;
9
+ groupParams: Record<string, string>;
10
+ steps: TestStepType[];
11
+ attachments: Attachment[];
12
+ }
package/dist/models.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,6 +1,6 @@
1
1
  import { Config, Reporter, Test, TestResult } from '@jest/reporters';
2
- import { Status } from '@jest/test-result';
3
- import { ConfigLoader, ConfigType, TestStatusEnum } from 'qase-javascript-commons';
2
+ import { Status, TestCaseResult } from '@jest/test-result';
3
+ import { Attachment, ConfigLoader, ConfigType, TestStatusEnum, TestStepType } from 'qase-javascript-commons';
4
4
  export type JestQaseOptionsType = ConfigType;
5
5
  /**
6
6
  * @class JestQaseReporter
@@ -17,7 +17,7 @@ export declare class JestQaseReporter implements Reporter {
17
17
  static qaseIdRegExp: RegExp;
18
18
  /**
19
19
  * @param {string} title
20
- * @returns {any}
20
+ * @returns {number[]}
21
21
  * @private
22
22
  */
23
23
  private static getCaseId;
@@ -26,6 +26,11 @@ export declare class JestQaseReporter implements Reporter {
26
26
  * @private
27
27
  */
28
28
  private reporter;
29
+ /**
30
+ * @type {Metadata}
31
+ * @private
32
+ */
33
+ private metadata;
29
34
  /**
30
35
  * @param {Config.GlobalConfig} _
31
36
  * @param {JestQaseOptionsType} options
@@ -37,6 +42,7 @@ export declare class JestQaseReporter implements Reporter {
37
42
  * @see {Reporter.onRunStart}
38
43
  */
39
44
  onRunStart(): void;
45
+ onTestCaseResult(test: Test, testCaseResult: TestCaseResult): void;
40
46
  /**
41
47
  * @param {Test} _
42
48
  * @param {TestResult} result
@@ -68,4 +74,32 @@ export declare class JestQaseReporter implements Reporter {
68
74
  * @private
69
75
  */
70
76
  private getCurrentTestPath;
77
+ addTitle(title: string): void;
78
+ addComment(comment: string): void;
79
+ addSuite(suite: string): void;
80
+ addFields(fields: Record<string, string>): void;
81
+ addParameters(parameters: Record<string, string>): void;
82
+ addGroupParams(groupParams: Record<string, string>): void;
83
+ addIgnore(): void;
84
+ addStep(step: TestStepType): void;
85
+ addAttachment(attachment: Attachment): void;
86
+ private cleanMetadata;
87
+ /**
88
+ * @param {AssertionResult} value
89
+ * @param {string} path
90
+ * @private
91
+ * @returns {TestResultType}
92
+ */
93
+ private convertToResult;
94
+ /**
95
+ * @returns {Metadata}
96
+ * @private
97
+ */
98
+ private createEmptyMetadata;
99
+ /**
100
+ * @param {string} title
101
+ * @returns {string}
102
+ * @private
103
+ */
104
+ private removeQaseIdsFromTitle;
71
105
  }
package/dist/reporter.js CHANGED
@@ -8,20 +8,47 @@ const lodash_has_1 = __importDefault(require("lodash.has"));
8
8
  const lodash_get_1 = __importDefault(require("lodash.get"));
9
9
  const uuid_1 = require("uuid");
10
10
  const qase_javascript_commons_1 = require("qase-javascript-commons");
11
+ const global_1 = require("./global");
11
12
  /**
12
13
  * @class JestQaseReporter
13
14
  * @implements Reporter
14
15
  */
15
16
  class JestQaseReporter {
17
+ /**
18
+ * @type {Record<Status, TestStatusEnum>}
19
+ */
20
+ static statusMap = {
21
+ passed: qase_javascript_commons_1.TestStatusEnum.passed,
22
+ failed: qase_javascript_commons_1.TestStatusEnum.failed,
23
+ skipped: qase_javascript_commons_1.TestStatusEnum.skipped,
24
+ disabled: qase_javascript_commons_1.TestStatusEnum.disabled,
25
+ pending: qase_javascript_commons_1.TestStatusEnum.skipped,
26
+ todo: qase_javascript_commons_1.TestStatusEnum.disabled,
27
+ focused: qase_javascript_commons_1.TestStatusEnum.passed,
28
+ };
29
+ /**
30
+ * @type {RegExp}
31
+ */
32
+ static qaseIdRegExp = /\(Qase ID: ([\d,]+)\)/;
16
33
  /**
17
34
  * @param {string} title
18
- * @returns {any}
35
+ * @returns {number[]}
19
36
  * @private
20
37
  */
21
38
  static getCaseId(title) {
22
39
  const [, ids] = title.match(JestQaseReporter.qaseIdRegExp) ?? [];
23
40
  return ids ? ids.split(',').map((id) => Number(id)) : [];
24
41
  }
42
+ /**
43
+ * @type {ReporterInterface}
44
+ * @private
45
+ */
46
+ reporter;
47
+ /**
48
+ * @type {Metadata}
49
+ * @private
50
+ */
51
+ metadata;
25
52
  /**
26
53
  * @param {Config.GlobalConfig} _
27
54
  * @param {JestQaseOptionsType} options
@@ -36,6 +63,8 @@ class JestQaseReporter {
36
63
  frameworkName: 'jest',
37
64
  reporterName: 'jest-qase-reporter',
38
65
  });
66
+ global.Qase = new global_1.Qase(this);
67
+ this.metadata = this.createEmptyMetadata();
39
68
  }
40
69
  /**
41
70
  * @see {Reporter.onRunStart}
@@ -43,49 +72,59 @@ class JestQaseReporter {
43
72
  onRunStart() {
44
73
  void this.reporter.startTestRun();
45
74
  }
75
+ onTestCaseResult(test, testCaseResult) {
76
+ if (this.metadata.ignore) {
77
+ this.cleanMetadata();
78
+ return;
79
+ }
80
+ const result = this.convertToResult(testCaseResult, test.path);
81
+ if (this.metadata.title) {
82
+ result.title = this.metadata.title;
83
+ }
84
+ if (this.metadata.comment) {
85
+ result.message = this.metadata.comment;
86
+ }
87
+ if (this.metadata.suite) {
88
+ result.relations = {
89
+ suite: {
90
+ data: [
91
+ {
92
+ title: this.metadata.suite,
93
+ public_id: null,
94
+ },
95
+ ],
96
+ },
97
+ };
98
+ }
99
+ if (Object.keys(this.metadata.fields).length > 0) {
100
+ result.fields = this.metadata.fields;
101
+ }
102
+ if (Object.keys(this.metadata.parameters).length > 0) {
103
+ result.params = this.metadata.parameters;
104
+ }
105
+ if (Object.keys(this.metadata.groupParams).length > 0) {
106
+ result.group_params = this.metadata.groupParams;
107
+ }
108
+ if (this.metadata.steps.length > 0) {
109
+ result.steps = this.metadata.steps;
110
+ }
111
+ if (this.metadata.attachments.length > 0) {
112
+ result.attachments = this.metadata.attachments;
113
+ }
114
+ this.cleanMetadata();
115
+ void this.reporter.addTestResult(result);
116
+ }
46
117
  /**
47
118
  * @param {Test} _
48
119
  * @param {TestResult} result
49
120
  */
50
121
  onTestResult(_, result) {
51
- console.log(result);
52
- result.testResults.forEach(({ title, fullName, ancestorTitles, status, duration, failureMessages, failureDetails, }) => {
53
- let error;
54
- if (status === 'failed') {
55
- error = new Error(failureDetails.map((item) => {
56
- if ((0, lodash_has_1.default)(item, 'matcherResult.message')) {
57
- return String((0, lodash_get_1.default)(item, 'matcherResult.message'));
58
- }
59
- return 'Runtime exception';
60
- }).join('\n\n'));
61
- error.stack = failureMessages.join('\n\n');
122
+ result.testResults.forEach((value) => {
123
+ if (value.status !== 'pending') {
124
+ return;
62
125
  }
63
- const ids = JestQaseReporter.getCaseId(title);
64
- const filePath = this.getCurrentTestPath(result.testFilePath);
65
- void this.reporter.addTestResult({
66
- attachments: [],
67
- author: null,
68
- execution: {
69
- status: JestQaseReporter.statusMap[status],
70
- start_time: null,
71
- end_time: null,
72
- duration: duration ?? 0,
73
- stacktrace: error?.stack ?? null,
74
- thread: null,
75
- },
76
- fields: {},
77
- message: error?.message ?? null,
78
- muted: false,
79
- params: {},
80
- group_params: {},
81
- relations: this.getRelations(filePath, ancestorTitles),
82
- run_id: null,
83
- signature: this.getSignature(filePath, fullName, ids),
84
- steps: [],
85
- testops_id: ids.length > 0 ? ids : null,
86
- id: (0, uuid_1.v4)(),
87
- title: title,
88
- });
126
+ const model = this.convertToResult(value, result.testFilePath);
127
+ void this.reporter.addTestResult(model);
89
128
  });
90
129
  }
91
130
  /**
@@ -146,21 +185,108 @@ class JestQaseReporter {
146
185
  const executionPath = process.cwd() + '/';
147
186
  return fullPath.replace(executionPath, '');
148
187
  }
188
+ addTitle(title) {
189
+ this.metadata.title = title;
190
+ }
191
+ addComment(comment) {
192
+ this.metadata.comment = comment;
193
+ }
194
+ addSuite(suite) {
195
+ this.metadata.suite = suite;
196
+ }
197
+ addFields(fields) {
198
+ this.metadata.fields = fields;
199
+ }
200
+ addParameters(parameters) {
201
+ this.metadata.parameters = parameters;
202
+ }
203
+ addGroupParams(groupParams) {
204
+ this.metadata.groupParams = groupParams;
205
+ }
206
+ addIgnore() {
207
+ this.metadata.ignore = true;
208
+ }
209
+ addStep(step) {
210
+ this.metadata.steps.push(step);
211
+ }
212
+ addAttachment(attachment) {
213
+ this.metadata.attachments.push(attachment);
214
+ }
215
+ cleanMetadata() {
216
+ this.metadata = this.createEmptyMetadata();
217
+ }
218
+ /**
219
+ * @param {AssertionResult} value
220
+ * @param {string} path
221
+ * @private
222
+ * @returns {TestResultType}
223
+ */
224
+ convertToResult(value, path) {
225
+ let error;
226
+ if (value.status === 'failed') {
227
+ error = new Error(value.failureDetails.map((item) => {
228
+ if ((0, lodash_has_1.default)(item, 'matcherResult.message')) {
229
+ return String((0, lodash_get_1.default)(item, 'matcherResult.message'));
230
+ }
231
+ return 'Runtime exception';
232
+ }).join('\n\n'));
233
+ error.stack = value.failureMessages.join('\n\n');
234
+ }
235
+ const ids = JestQaseReporter.getCaseId(value.title);
236
+ const filePath = this.getCurrentTestPath(path);
237
+ return {
238
+ attachments: [],
239
+ author: null,
240
+ execution: {
241
+ status: JestQaseReporter.statusMap[value.status],
242
+ start_time: null,
243
+ end_time: null,
244
+ duration: value.duration ?? 0,
245
+ stacktrace: error?.stack ?? null,
246
+ thread: null,
247
+ },
248
+ fields: {},
249
+ message: error?.message ?? null,
250
+ muted: false,
251
+ params: {},
252
+ group_params: {},
253
+ relations: this.getRelations(filePath, value.ancestorTitles),
254
+ run_id: null,
255
+ signature: this.getSignature(filePath, value.fullName, ids),
256
+ steps: [],
257
+ testops_id: ids.length > 0 ? ids : null,
258
+ id: (0, uuid_1.v4)(),
259
+ title: this.removeQaseIdsFromTitle(value.title),
260
+ };
261
+ }
262
+ /**
263
+ * @returns {Metadata}
264
+ * @private
265
+ */
266
+ createEmptyMetadata() {
267
+ return {
268
+ title: undefined,
269
+ ignore: false,
270
+ comment: undefined,
271
+ suite: undefined,
272
+ fields: {},
273
+ parameters: {},
274
+ groupParams: {},
275
+ steps: [],
276
+ attachments: [],
277
+ };
278
+ }
279
+ /**
280
+ * @param {string} title
281
+ * @returns {string}
282
+ * @private
283
+ */
284
+ removeQaseIdsFromTitle(title) {
285
+ const matches = title.match(/\(Qase ID: ([0-9,]+)\)$/i);
286
+ if (matches) {
287
+ return title.replace(matches[0], '').trimEnd();
288
+ }
289
+ return title;
290
+ }
149
291
  }
150
292
  exports.JestQaseReporter = JestQaseReporter;
151
- /**
152
- * @type {Record<Status, TestStatusEnum>}
153
- */
154
- JestQaseReporter.statusMap = {
155
- passed: qase_javascript_commons_1.TestStatusEnum.passed,
156
- failed: qase_javascript_commons_1.TestStatusEnum.failed,
157
- skipped: qase_javascript_commons_1.TestStatusEnum.skipped,
158
- disabled: qase_javascript_commons_1.TestStatusEnum.disabled,
159
- pending: qase_javascript_commons_1.TestStatusEnum.skipped,
160
- todo: qase_javascript_commons_1.TestStatusEnum.disabled,
161
- focused: qase_javascript_commons_1.TestStatusEnum.passed,
162
- };
163
- /**
164
- * @type {RegExp}
165
- */
166
- JestQaseReporter.qaseIdRegExp = /\(Qase ID: ([\d,]+)\)/;
package/dist/step.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { Attachment, TestStepType } from 'qase-javascript-commons';
2
+ export type StepFunction<T = any> = (this: QaseStep, step: QaseStep) => T | Promise<T>;
3
+ export declare class QaseStep {
4
+ name: string;
5
+ attachments: Attachment[];
6
+ steps: TestStepType[];
7
+ constructor(name: string);
8
+ attach(attach: {
9
+ name?: string;
10
+ type?: string;
11
+ content?: string;
12
+ paths?: string[] | string;
13
+ }): void;
14
+ step(name: string, body: StepFunction): Promise<void>;
15
+ run(body: StepFunction, messageEmitter: (step: TestStepType) => Promise<void>): Promise<void>;
16
+ }
package/dist/step.js ADDED
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.QaseStep = void 0;
7
+ const qase_javascript_commons_1 = require("qase-javascript-commons");
8
+ const uuid_1 = require("uuid");
9
+ const path_1 = __importDefault(require("path"));
10
+ // TODO: Move to common, because it's duplicated in qase-wdio/src/step.ts
11
+ class QaseStep {
12
+ name = '';
13
+ attachments = [];
14
+ steps = [];
15
+ constructor(name) {
16
+ this.name = name;
17
+ }
18
+ attach(attach) {
19
+ if (attach.paths) {
20
+ const files = Array.isArray(attach.paths) ? attach.paths : [attach.paths];
21
+ for (const file of files) {
22
+ const attachmentName = path_1.default.basename(file);
23
+ const contentType = 'application/octet-stream';
24
+ this.attachments.push({
25
+ id: (0, uuid_1.v4)(),
26
+ file_name: attachmentName,
27
+ mime_type: contentType,
28
+ content: '',
29
+ file_path: file,
30
+ size: 0,
31
+ });
32
+ }
33
+ return;
34
+ }
35
+ if (attach.content) {
36
+ const attachmentName = attach.name ?? 'attachment';
37
+ const contentType = attach.type ?? 'application/octet-stream';
38
+ this.attachments.push({
39
+ id: (0, uuid_1.v4)(),
40
+ file_name: attachmentName,
41
+ mime_type: contentType,
42
+ content: attach.content,
43
+ file_path: null,
44
+ size: attach.content.length,
45
+ });
46
+ }
47
+ }
48
+ async step(name, body) {
49
+ const childStep = new QaseStep(name);
50
+ // eslint-disable-next-line @typescript-eslint/require-await
51
+ await childStep.run(body, async (step) => {
52
+ this.steps.push(step);
53
+ });
54
+ }
55
+ async run(body, messageEmitter) {
56
+ const startDate = new Date().getTime();
57
+ const step = new qase_javascript_commons_1.TestStepType();
58
+ step.data = {
59
+ action: this.name,
60
+ expected_result: null,
61
+ };
62
+ try {
63
+ await body.call(this, this);
64
+ step.execution = {
65
+ start_time: startDate,
66
+ end_time: new Date().getTime(),
67
+ status: qase_javascript_commons_1.StepStatusEnum.passed,
68
+ duration: null,
69
+ };
70
+ step.attachments = this.attachments;
71
+ step.steps = this.steps;
72
+ await messageEmitter(step);
73
+ }
74
+ catch (e) {
75
+ step.execution = {
76
+ start_time: startDate,
77
+ end_time: new Date().getTime(),
78
+ status: qase_javascript_commons_1.StepStatusEnum.failed,
79
+ duration: null,
80
+ };
81
+ step.attachments = this.attachments;
82
+ step.steps = this.steps;
83
+ await messageEmitter(step);
84
+ throw e;
85
+ }
86
+ }
87
+ }
88
+ exports.QaseStep = QaseStep;
package/docs/usage.md ADDED
@@ -0,0 +1,209 @@
1
+ # Qase Syntax
2
+
3
+ > [**Click here**](../../examples/jest/test) to view Example tests for the following syntax.
4
+
5
+ Here is the complete list of syntax options available for the reporter:
6
+ - [Qase Id](#qase-id)
7
+ - [Qase Title](#qase-title)
8
+ - [Steps](#steps)
9
+ - [Fields](#fields)
10
+ - [Suite](#suite)
11
+ - [Parameters](#parameters)
12
+ - [Comment](#comment)
13
+ - [Attach](#attach)
14
+ - [Ignore](#ignore)
15
+
16
+ If you do not use any Qase syntax, the reporter uses the title from the `describe` and `test` functions as the Suite and Test case title respectively, when publishing results.
17
+
18
+ <br>
19
+
20
+ ### Import Statement
21
+ ---
22
+ Add the following statement at the beginning of your spec file, before any tests.
23
+
24
+ ```javascript
25
+ const { qase } = require("jest-qase-reporter/jest");
26
+ ```
27
+ <br>
28
+
29
+ ### Qase ID
30
+ ---
31
+
32
+ You can link one or more Qase Ids to a test.
33
+
34
+ ```javascript
35
+ test(qase(1, "A test with Qase Id"), () => {
36
+ ..
37
+
38
+ test(qase(['2', '3'], "A test with multiple Qase Ids"), () => {
39
+ ..
40
+ ```
41
+
42
+ <br>
43
+
44
+ ### Qase Title
45
+ ---
46
+
47
+ The `qase.title()` method is used to set the title of a test case, both when creating a new test case from the result, and when updating the title of an existing test case - *if used with `qase.id()`.*
48
+
49
+ ```javascript
50
+ test("This won't appear in Qase", () => {
51
+ qase.title("This text will be the title of the test, in Qase");
52
+ // Test logic here
53
+ });
54
+ ```
55
+
56
+ If you don’t explicitly set a title using this method, the title specified in the `test(..)` function will be used for creating new test cases. However, if this method is defined, it always takes precedence and overrides the title from the `test(..)` function.
57
+
58
+ <br>
59
+
60
+ ### Steps
61
+ ---
62
+
63
+ The reporter uses the title from the `test.step` function as the step title. By providing clear and descriptive step names, you make it easier to understand the test’s flow when reviewing the test case.
64
+
65
+ Additionally, these steps get their own result in the Qase Test run, offering a well-organized summary of the test flow. This helps quickly identify the cause of any failures.
66
+
67
+ ```javascript
68
+ test('A Test case with steps, updated from code', async () => {
69
+ await test.step('Initialize the environment', async () => {
70
+ // Set up test environment
71
+ });
72
+ await test.step('Test Core Functionality of the app', async () => {
73
+ // Exercise core functionality
74
+ });
75
+
76
+ await test.step('Verify Expected Behavior of the app', async () => {
77
+ // Assert expected behavior
78
+ });
79
+ });
80
+ ```
81
+ <br>
82
+
83
+ ### Fields
84
+ ---
85
+
86
+ You can define the `description`, `pre-conditions`, `post-conditions`, and fields such as `severity`, `priority`, and `layer` using this method, which enables you to specify and maintain the context of the case directly within your code.
87
+
88
+ ```javascript
89
+ test('Maintain your test meta-data from code', async () => {
90
+ await qase.fields({
91
+ severity: 'high',
92
+ priority: 'medium',
93
+ layer: 'api',
94
+ precondition: 'add your precondition',
95
+ postcondition: 'add your postcondition',
96
+ description: `Code it quick, fix it slow,
97
+ Tech debt grows where shortcuts go,
98
+ Refactor later? Ha! We know.`
99
+ });
100
+ // test logic here
101
+ });
102
+ ```
103
+
104
+ <br>
105
+
106
+
107
+ ### Suite
108
+ ---
109
+
110
+ You can use this method to nest the resulting test cases in a particular suite. There's something to note here – suites, unlike test cases, are not identified uniquely by the Reporter. Therefore, when defining an existing suite - the title of the suite is used for matching.
111
+
112
+ ```js
113
+ test("Test with a defined suite", () => {
114
+ qase.suite("Suite defined with qase.suite()");
115
+ /*
116
+ * Or, nest multiple levels of suites.
117
+ * `\t` is used for dividing each suite name.
118
+ */
119
+
120
+ test("Test with a nested suite", () => {
121
+ qase.suite("Application\tAuthentication\tLogin\tEdge_case");
122
+ // test logic here
123
+ });
124
+ ```
125
+ <br>
126
+
127
+ ### Parameters
128
+ ---
129
+ Parameters are a great way to make your tests more dynamic, reusable, and data-driven. By defining parameters in this method, you can ensure only one test case with all the parameters is created in your Qase project, avoiding duplication.
130
+
131
+
132
+ ```javascript
133
+ const testCases = [
134
+ { browser: "Chromium", username: "@alice", password: "123" },
135
+ { browser: "Firefox", username: "@bob", password: "456" },
136
+ { browser: "Webkit", username: "@charlie", password: "789" },
137
+ ];
138
+
139
+ testCases.forEach(({ browser, username, password, }) => {
140
+ test(`Test login with ${browser}`, async () => {
141
+ qase.title("Verify if page loads on all browsers");
142
+
143
+ qase.parameters({ Browser: browser }); // Single parameter
144
+ // test logic
145
+
146
+ testCases.forEach(({ username, password }) => {
147
+ test(`Test login with ${username} using qase.groupParameters`, () => {
148
+ qase.title("Verify if user is able to login with their username.");
149
+
150
+ qase.groupParameters({ // Group parameters
151
+ Username: username,
152
+ Password: password,
153
+ });
154
+ // test logic
155
+ ```
156
+ <br>
157
+
158
+ ### Comment
159
+ ---
160
+ In addition to `test.step()`, this method can be used to provide any additional context to your test, it helps maintiain the code by clarifying the expected result of the test.
161
+
162
+ ```js
163
+ test("A test case with qase.comment()", () => {
164
+ /*
165
+ * Please note, this comment is added to a Result, not to the Test case.
166
+ */
167
+ qase.comment("This comment is added to the result");
168
+ // test logic here
169
+ });
170
+ ```
171
+ <br>
172
+
173
+ ### Attach
174
+ ---
175
+ This method can help attach one, or more files to the test's result. You can also add the file's contents directly from code. For example:
176
+
177
+ ```js
178
+ test('Test result with attachment', async () => {
179
+
180
+ test("Test result with attachment", async () => {
181
+
182
+ // To attach a single file
183
+ await qase.attach({
184
+ paths: "./attachments/test-file.txt",
185
+ });
186
+
187
+ // Add multiple attachments.
188
+ await qase.attach({ paths: ['/path/to/file', '/path/to/another/file'] });
189
+
190
+ // Upload file's contents directly from code.
191
+ await qase.attach({
192
+ name: "attachment.txt",
193
+ content: "Hello, world!",
194
+ contentType: "text/plain",
195
+ });
196
+ // test logic here
197
+ });
198
+ ```
199
+ <br>
200
+
201
+ ### Ignore
202
+ ---
203
+ If this method is added, the reporter will exclude the test’s result from the report sent to Qase. While the test will still executed by jest, its result will not be considered by the reporter.
204
+
205
+ ```js
206
+ test("This test is executed by jest; however, it is NOT reported to Qase", () => {
207
+ qase.ignore();
208
+ // test logic here
209
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jest-qase-reporter",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "Qase TMS Jest Reporter",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -45,7 +45,7 @@
45
45
  "dependencies": {
46
46
  "lodash.get": "^4.4.2",
47
47
  "lodash.has": "^4.5.2",
48
- "qase-javascript-commons": "~2.2.0",
48
+ "qase-javascript-commons": "~2.2.1",
49
49
  "uuid": "^9.0.0"
50
50
  },
51
51
  "devDependencies": {